Edited at

[備忘録]Upgrade to Java SE 8 OCP ( Java SE 6 and all prior versions)を受験しよう「言語の拡張」編

More than 1 year has passed since last update.

この記事はJavaの資格「Upgrade to Java SE 8 OCP ( Java SE 6 and all prior versions)」(1Z0-813)を取得するまでの勉強内容について備忘録としたものである。


試験詳細

「Upgrade to Java SE 8 OCP ( Java SE 6 and all prior versions)」(1Z0-813)はJava6以前の資格を持っている人がJava8にアップグレードするための試験。

なお、初めてJava資格を受験する人は「1Z0-808」(Silver)取って、「1Z0-809」(Gold)を目指す。

・試験時間  130分

・出題数   60問

・合格ライン 63%


出題範囲


  1. 言語の拡張

  2. 同時実行性

  3. ローカライズ

  4. JavaのファイルI/O(NIO.2)

  5. ラムダ式

  6. Javaコレクション

  7. Javaストリーム


言語の拡張

JavaSE7から言語仕様が一部変更された。試験はJava6以前からのアップグレードなので、JavaSE7も出題範囲:point_up:

公式サイトにある出題トピックスは以下。


•switch文での文字列の使用および下線文字がある数値リテラルと2進リテラルを使用する。

•try-with-resourcesを使用する。

•例外処理で複数のcatchを使用する。

•インタフェースのデフォルト・メソッドを定義および使用する。デフォルト・メソッドの継承ルールについて説明する。



switch文での文字列の使用

switch文のcase文に使用できる型として文字列が使えるようになった。これまでは、int, short, char, byte, enumに限られていた。

switch (str) {

case "文字列1":
break;
case "文字列2":
break;
}

文字列の値がnullの場合、NullPointerExceptionがスローされる。


2進数リテラル

2進数をリテラルとして書けるようになった。これまで整数を表すリテラルは、10進数、8進数、16進数であった。

2進数を表現する場合は先頭に「0b」もしくは「0B」を付けて記述する。

int a = 10;     //10進数

int b = 012; //8進数
int c = 0xA; //16進数
int d = 0b1010; //2進数
int e = 0B1010; //2進数
int f = 0b1234; //コンパイルエラー

2進数リテラルに0、1以外の数字がある場合はコンパイルエラーとなる。


数値リテラルの区切り文字

桁数の大きな数値リテラルの可読性を高めるため区切り文字が使えるようになった。

使える区切り文字は「_」(アンダースコア)


ルール


  • 先頭、末尾には使用できない

  • 小数点の前後には使用できない

  • floatやlongを表現するFやLの前には使用できない

  • 16進数や2進数を表現する0x、0bの前後や途中では使用できない

//OKケース

int a = 1_23_456; //10進数
int b = 0xFF_FF_FF; //16進数
int c = 0b1000_1000; //2進数
int d = 0_123 //8進数
double e = 1_000.0; //浮動小数
int f = 1____2____3; //区切り文字連続使用

//NGケース(コンパイルエラー)
int z = _1234; //先頭はNG
int y = 1234_; //最後はNG
int x = _0xFF; //16進数でも先頭はNG
int w = _0b1010; //2進数でも先頭はNG
int v = 0x_FF; //0xの直後はNG
int u = 0b_1000; //0bの直後はNG
int t = 0_xFF; //0xの間はNG
float s = 3_.14; //小数点の前はNG
float r = 3._14; //小数点の後はNG
long q = 999_999_L //Lの前はNG


マルチキャッチ

複数の例外を1つのcatch文でまとめてキャッチできるようになった。

以下のようにcatch文をたくさん書くのがJava6までの書き方。

try {

Class cls = Class.forName("hogehoge");
cls.newInstance();
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
} catch (InstantiationException ex) {
ex.printStackTrace();
} catch (IllegalAccessException ex) {
ex.printStackTrace();
}

以下がマルチキャッチの書き方。「|」で例外クラスを区切る。コードがすっきり。

try {

Class cls = Class.forName("hogehoge");
cls.newInstance();
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException ex) {
ex.printStackTrace();
}


ルール


  • 継承関係にある例外クラスはマルチキャッチできない。

  • キャッチした参照変数は暗黙的にfinal

以下のようにIOExceptionとそのサブクラスであるFileNotFountExceptionはマルチキャッチできない。

try {

BufferedReader br = new BufferedReader(new FileReader("hogehoge"));
String str = br.readLine();
} catch (FileNotFoundException | IOException ex) {
//コンパイルエラー
}


リソースの自動クローズ

try-with-resources文でファイルやデータベースの煩わしいclose処理を明示的に書く必要がなくなった。

自動クローズしたいリソースの変数をtry文後のカッコ内に定義する。

以下の例では変数brのリソースは使い終わった後に自動でクローズされる。

try (BufferedReader br = new BufferedReader(new FileReader("hogehoge"))) {

//読み込み処理
} catch (IOException ex) {
//例外処理
}

複数リソースの自動クローズもできる。

try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("hogehogeI"));

BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream("hogehogeO"))){
//読み書き処理
} catch (IOException ex) {
//例外処理
}


ルール


  • try-with-resources文ではtryのみの使用ができる。

  • tryのカッコ内に記述できるのは、java.lang.AutoCloseablejava.io.Closeableインタフェースの実装クラス。


処理順の検証

自動クローズがいつ実行されるのか以下のサンプルコードで検証

public class Sample {

public static void main(String[] args) {
try (SampleResource sr1 = new SampleResource("sr1");
SampleResource sr2 = new SampleResource("sr2")) {
System.out.println("tryの処理");
throw new Exception();
} catch (Exception ex) {
System.out.println("catchの処理");
} finally {
System.out.println("finallyの処理");
}
}
}

class SampleResource implements AutoCloseable {

private String msg;

public SampleResource(String msg) {
this.msg = msg;
}

@Override
public void close() throws Exception {
System.out.println("close() :" + msg);
}
}

以下が実行結果

tryの処理

close() :sr2
close() :sr1
catchの処理
finallyの処理


ポイント


  • try内で例外をスローしているが、catchには入らずクローズ処理を先に実行している。

  • リソースのクローズ順はリソース取得順の逆。


インタフェースのデフォルトメソッド

インタフェースに処理を記述したメソッド(具象メソッド)を定義できるようになった。

実装クラスで具象化する必要がないから、既存のインタフェースにメソッドを追加しやすくなる。

interface Car {

//デフォルトメソッド
default void stop() {
System.out.print("STOP!");
}
}

public class Foo {

public static void main(String[] args) {
Car car = new Car() {}; //匿名クラスオブジェクト生成
car.stop(); //デフォルトメソッドコール
}
}


ルール


  • 指定できる修飾子はpublicのみ。指定しない場合は暗黙的にpublic。


  • java.lang.Objectにあるequals()hashCode()toString()の3つはデフォルトメソッドとして定義できない。コンパイルエラー。

  • デフォルトメソッドだけのインタフェースも可。

  • オーバーライドしなくてはいけないメソッドが存在しなくても実装クラスは必要。


デフォルトメソッドの再抽象化

以下のようにサブインタフェースでデフォルトメソッドを再抽象化できる。

interface Car {

default void stop() {
System.out.print("STOP!");
}
}

interface NewCar extends Car {
@Override
void stop(); //抽象化
}

public class Foo {
public static void main(String[] args) {
Car car = new NewCar() {
//抽象化したので実装が必要
@Override
public void stop() {
System.out.print("STOP!!");
}
};
car.stop();
}
}


デフォルトメソッドのオーバーライド

以下のようにデフォルトメソッドをデフォルトメソッドでオーバーライドできる。

interface Car {

default void stop() {
System.out.print("STOP!");
}
}

interface NewCar extends Car {
//デフォルトメソッドをオーバーライド
@Override
default void stop() {
System.out.print("STOP!!");
}
}

public class Foo {
public static void main(String[] args) {
Car car = new NewCar() {};
car.stop();
}
}


デフォルトメソッドの多重継承

Javaでは単一継承にすることで実装を含むクラスの多重継承を避けていた。しかし、インタフェースは多重継承することができる。

そこにデフォルトメソッドの実装を含めることできるようになったため、多重継承問題が発生する。多重継承の問題とは同じシグネチャのメソッド実装が複数存在すること。

デフォルトメソッドではそれを避けるために、インタフェースで同じシグネチャのメソッド実装が複数ある場合、それを実装するクラスではそのメソッドをオーバーライドしなくてはいけない。

以下はその例。

interface Car{

void stop();
}

interface NewCar extends Car{
@Override
default void stop(){
System.out.print("STOP!");
}
}

interface OldCar extends Car{
@Override
default void stop(){
System.out.print("STOP");
}
}

public class Foo implements OldCar,NewCar{
//同じシグネチャのデフォルトメソッドを継承しているため
//オーバーライドする必要がある。
@Override
public void stop(){
System.out.print("STOP!!");
}
}

同じシグネチャのメソッド実装が複数ある場合は常にクラスの実装が優先される。

以下のサンプルコードもその例。

interface Car{

void stop(String msg);
}

class NewCar implements Car{
@Override
public void stop(String msg) {
System.out.println("NewCar STOP : "+msg);
}
}

interface OldCar extends Car{
@Override
default void stop(String msg){
System.out.println("OldCar STOP : "+msg);
}
}

class SUVCar extends NewCar implements OldCar{}

public class Foo {
public static void main(String[] args) {
Car car = new SUVCar();
car.stop("SUV!");
}
}

実行結果

NewCarクラスのメソッド実装が実行される。

NewCar STOP : SUV!

これまでsuperで親のメソッド実装をコールすることができたが、デフォルトメソッドの導入により実装が複数になる。

superだけではコールするメソッドを一意に決めることができない場合があるため、クラス名またはインタフェース名をsuperの前に追記してメソッドを指定する。

interface Car{

void stop();
}

interface NewCar extends Car{
@Override
default void stop(){
System.out.print("STOP!");
}
}

interface OldCar extends Car{
@Override
default void stop(){
System.out.print("STOP");
}
}

public class Foo implements OldCar,NewCar{
@Override
public void stop(){
NewCar.super.stop(); //NewCarインタフェースのstopメソッドをコール
}
}


おわりに

リソースの自動クローズやマルチキャッチは、これまでの煩わしいコードを書かなくて済むし、コードの可読性もよくなる。実業務でも活用できるよう押さえておきたい。:point_up:

次回は「同時実行性」編をお送りする。


参考文献