はじめに
こんにちは、SSTでWeb脆弱性診断を担当しているTです。
去年8月に「Oracle Java SE8 Silver」資格に挑戦し、無事合格しました。そこで、その際に勉強した内容について共有させて頂きます。
Java SE8 Silverは「上級者の指示の下で指定された実装が行えるレベル(初級Javaプログラマ向け)」を対象としたOracle様の認定資格です。 詳しくは以下の公式サイトを参考にしてください。
ちなみに、同時期に受験し合格された方の受験体験記も宜しければ参考にしてみて下さい。
受験勉強
前提
個人的なお話になってしまいますが、筆者のプログラミング歴はそれまで
- 学生時代の講義で習ったC言語
- 学生時代のC++、Pythonを用いた数値計算・最適化アルゴリズムの研究
程度で、本格的かつ大規模な開発は未経験でした。入社後Javaに興味を持ち、Javaの入門書を読み終えたところでさらなるスキルアップを目指していたところ、こちらの資格のおすすめして頂きました。
勉強内容
期間:2020年の7月~8月上旬 資格勉強用に以下の2冊を購入し勉強しました。
1の教科書は学習項目に合わせて「全8章+模試」の構成で、2.の問題集は「全9章+模試」の構成でした。 自分の場合は試験までに1と2をそれぞれ2~3周し、最後に模試を解いて実力試しをしてから試験に臨みました。(正直この作戦はあまりよくなかったです)
これは受験テク的な話になってしまい学習の本質から逸れてしまいますが、「1と2に付属している模試は実力試しのためではなく、反復学習用の教材として使う」ことをお勧めします。 というのも自分の反省点の一つとして「もっと実践向けの問題でならせばよかった」ことがあげられます。各章の章末問題でも知識の定着を確かめられますが、模試の方が本番の一問あたりで問われている知識量をうまく再現して作問されていると感じました。 もちろん1,2の各章の問題を何度もやることも重要ですが、より実践形式に近い模試を解いて全問について解答とその根拠について話せる状態を目指すのが良いかと思われます。
内容について
勉強を進めていく上で自分が特に大事だと思った内容についていくつかお話しします。
クラスの継承とキャストについて
以下のようなクラスParent、Childがあったとします。 ChildはParentを継承しており、両者はmethodA()をメソッドとして持っています。
public class Parent { public void methodA() { System.out.println("Parent:methodA()"); } }
ChildはParentを継承しており、methodA()をオーバーライドしています。
public class Child extends Parent { @Override public void methodA() { System.out.println("Child:methodA()"); } }
この時以下のコードを実行すると、3行目で実行されるmethodA()はParentのものかChildのものかどちらになるでしょうか?
Child child = new Child();
Parent parent = child;
parent.methodA();
実行結果は以下になります。
Child:methodA()
実行結果から、実行されたのはChildのmethodA()であることが分かります。 上の例の判断をする際に意識するべきことは「メソッドを実行している実体が何であるか」です。 子クラスのインスタンスが親クラスの型にダウンキャストされても、メソッドを実行している実体が子クラスのインスタンスであることに変わりはありません。 そのため、実行されるのは子クラスで定義されているメソッドになります。
試験では継承関係にあるクラス間でのオブジェクトのキャストが頻繁に行われます。座学も大事ですが、そういった場合にどのメソッドが呼ばれるのか、それともコンパイルエラーになるのか、または例外が投げられるのかといったことを一度は実際のコードを動かして確かめることをお勧めします。
メソッドのオーバーライドと例外の条件について
以下のようなクラスParent、Childがあったとします。 ChildはParentを継承しており、両者はmethodA()、methodB()、methodC()をメソッドとして持っています。
public class Parent { public void methodA() throws Exception { } public void methodB() throws Exception { } public void methodC() { } }
public class Child extends Parent { @Override public void methodA() { } @Override public void methodB() throws IOException { } @Override public void methodC() throws RuntimeException { } }
メソッドのオーバーライドと例外については大きく分けて3つの条件があります。それぞれのメソッドを使ってその条件についてお話しします。 まず、methodA()についてですが、Parentでthrows Exceptionが記述されているにもかかわらず、Childでは特にthrowsは記述されていません。このように
- 親クラスのメソッドにthrowsがあっても、子クラスでオーバーライドしたメソッドでthrowsは記述しないことができる
ことがわかります。
次に、methodB()についてですが、Parentではthrows Exceptionが記述されていますが、Childではthrows IOExceptionが記述されています。このように
- 子クラスでオーバーライドしたメソッドがthrowsに記述できる例外は、親クラスのメソッドのthrowsに記述した例外かその子クラスに当たる例外
という制約があります。
最後に、methodC()についてですが、Parentではthrowsは記述されていませんが、Childではthrows RuntimeExceptionが記述されています。このように
- RuntimeExceptionとその子クラスに当たる例外は、親クラスのメソッドでのthrowsの記述にかかわらず子クラスでオーバーライドしたメソッドで記述できる
といった条件があります。 もしメソッドのオーバーライドと例外が絡んだ問題が出た場合は、以上3点について注意してみるといいかもしれません。
クラスの継承とコンストラクタの呼び出しについて
以下のようなクラスParent、Childがあったとします。 ChildはParentを継承しています。ParentとChildは引数を取らないコンストラクタと、int型の引数を取るコンストラクタを持ちます。
public class Parent { public Parent() { System.out.println("Parent Constructor:Parent()"); } public Parent(int i) { System.out.println("Parent Constructor:Parent(int i)"); } }
public class Child extends Parent { public Child() { System.out.println("Child Constructor:Child()"); } public Child(int i) { System.out.println("Child Constructor:Child(int i)"); } }
この時、以下のコードを実行します。
Child child = new Child(0);
すると、実行結果は以下のようになります。
Parent Constructor:Parent() Child Constructor:Child(int i)
以上からわかる通り、子クラスのコンストラクタを呼び出すと直前で暗黙的に親クラスのコンストラクタ(引数なし)が呼び出されます。 逆に、子クラスのコンストラクタを呼び出すときに直前に呼び出される親クラスのコンストラクタを明示的に指示したい場合(この例で言うと、Parentの引数ありのコンストラクタを明示的に呼びたい場合)、以下のようにChildのコンストラクタでsuper(i);を追加する修正をする必要があります。
public Child(int i) { super(i); System.out.println("Child Constructor:Child(int i)"); }
また、たまに問題で問われるパターンとして、「親クラスで引数を取るコンストラクタ(この例だとpublic Parent(int i))は定義されている一方で、引数を取らないコンストラクタ(この例だとpublic Parent())が定義されていないものがあります。その場合、暗黙的に呼び出される親クラスのコンストラクタが存在しないことになり、子クラスのコンストラクタでsuper()を使って明示的に引数を取るコンストラクタを呼び出さないとコンパイルエラーになります。
StringクラスとStringBuilderクラスについて
まずはStringクラスについてです。以下のように生成したStringクラスのインスタンスを比較した場合、出力結果はどうなるでしょうか?
String s1 = new String("String"); String s2 = new String("String"); String s3 = s1; String s4 = "String"; String s5 = "String"; System.out.println(s1 == s2); //...1 System.out.println(s1.equals(s2)); //...2 System.out.println(s1 == s3); //...3 System.out.println(s1.equals(s3)); //...4 System.out.println(s4 == s5); //...5 System.out.println(s4.equals(s5)); //...6
出力結果は以下になります。
false true true true true true
1~6のパターンについて解説します。
- ==演算子がこの場合比較するのは各インスタンスの参照になります。この場合s1とs2は個別に生成されたStringクラスのオブジェクトのため、参照元が異なりfalseが返ります。
- s1のequals()メソッドを使ってs1とs2を比較しています。Stringクラスのequals()メソッドは値を比較するようにオーバーライドされています。よってこの場合s1とs2が同じ"String"の値を持つためtrueが返ります。
- s1とs3は同じ参照元を示すため、==演算子で比較するとtrueが返ります。
- s1とs3は同じ参照元を示すため、値も同じとなりtrueが返ります。
- Javaにはコンスタントプールという仕組みが存在し、String s4 = "String";のような形式で同じ値のStringクラスのインスタンスを生成した場合、オブジェクトの参照を使いまわす場合があります。これによりs4とs5の参照は同値となるため、trueが返ります。
- s4とs5は同じ値であるため、trueが返ります。
次に、StringBuilderクラスについてです。以下のように生成したStringBuilderクラスのインスタンスを比較した場合、出力結果はどうなるでしょうか?
StringBuilder sb1 = new StringBuilder("StringBuilder"); StringBuilder sb2 = new StringBuilder("StringBuilder"); System.out.println(sb1 == sb2); //...1 System.out.println(sb1.equals(sb2)); //...2 System.out.println(sb1.toString().equals(sb2.toString())); //...3
出力結果は以下になります。
false false true
1~3のパターンについて解説します。
- Stringクラスの1のパターンと同様、StringBuilderクラスの異なるインスタンスの参照を比較しているので、falseが返ります。
- StringBuilderクラスのequals()メソッドは内部で保持されている文字列の値を比較するようにオーバーライドされていないので、falseが返ります。
- StringBuilderクラスのtoString()メソッドで生成したStringクラスのオブジェクト2つの値を比較するため、trueが返ります。StringBuilderクラスで生成した文字列を比較したい場合このように一度Stringクラスを生成してから比較してあげる必要があります。
まとめ
以上、Java SE8 Silverについて個人的に重要だと思う点をいくつかご紹介しました。大事な点はまだまだありますが、これから受験される方の一助になれば幸いです。
余談
筆者は2020年8月にテストセンターで試験を受けました。(リモートでの受験も視野に入れましたが受験環境の制約が厳しく断念しました)コロナ禍の影響と会場の設備の都合により、真夏に空調の効いていない部屋でマスクを装備した状態での受験でした。そういった環境での受験になると流石に集中力にも影響が出るため、受験時期を調整したりテストセンターの環境や設備の情報を前もって調べておく等の工夫はした方がいいかもしれません。