#この記事について
2020/5/16にJava SE 11 Programmer II試験を受けて合格し、Java Programmer, Gold SE 11の資格を取得しましたので、これから受験する方の一助になればと思い、この記事を書くことにしました。
自分自身新機能についてそこまで理解があったわけではないので、その復習も兼ねています。
前提としてJava Programmer, Silverの資格まで持っていて(Java SE 11 Programmer IIの受験資格がある)、Java SE 8 Goldの知識についてもある程度学習が進んでいる想定で話を進めて行きますので、ラムダ式やStream APIなどのCore APIの話はあまりせず、SE 8から追加された新要素についてメインに書きたいと思います。当記事では簡単な解説にとどめますので、詳しくは各項目に記載したリンク先を見ていただけると幸いです。
#はじめに
自分が勤めている会社には資格手当があり、Javaの学習と資格手当の取得を兼ねて資格勉強を行っていました。Java SE 7/8 Bronze、java SE 8 Silver(Java SE 8 Programmer I)はいずれも2~3週間ほど黒本を解けば比較的容易に取れました。
Goldについても2カ月ほど勉強して、SE 8なら受かるレベルまでにはなりましたが、せっかく最上位の資格を受けるならなら最新の資格を取りたいと思い、SE 11のほうを受験することにしました。しかし、参考書が2020/5/16現在出版されておらず、まだまだ出版までは時間がかかりそうな見通しです。なのでSE 11からの新分野は参考書などに頼らずに学習しなければならず、これがなかなか大変でした。
大まかな情報はOracleが開催した無料のオンラインセミナーを受講して情報を集めました。出題者の方が講師をやっており、話も分かりやすいので、大変有意義でした。もし機会があれば受講して損はないと思います。
また、5/31までに受験することで、再受験が無料になるキャンペーンをおこなっているので、迷っている方は一度受けてみるのも良いと思います。詳細は以下のリンクに記載されています。
[キャンペーン :: Oracle :: ピアソンVUE]
(https://www.pearsonvue.co.jp/Clients/Oracle/Special-Offers/Retake2020.aspx)
#試験概要
まず、試験の概要から見ていきます。
||Java SE 11 Programmer II|Java SE 8 Programmer II|
|:-:|:-:|:-:|
|試験名|Java SE 11 Programmer II|Java SE 8 Programmer II|
|試験番号|1Z0-816|1Z0-809|
|受験料(税抜)|¥26,600|¥26,600|
|出題形式|選択問題|選択問題|
|試験時間|180分|150分|
|出題数|80問|85問|
|合格ライン|63%|65%|
問題が少し減り、試験時間が伸びました。問題文のコードが全体的に少し長くなった影響だと思われます。合格ラインも微妙に減っていますが、これは新しめの資格だからなのかなと思います。本番では30分くらい余りましたので、焦って解く必要はそこまでないのかなと思います。
#出題範囲の相違点
###新規に追加されたトピック
- インタフェースのprivateメソッド
- ローカル変数型推論(var)の使用法
- モジュール機能
- セキュアコーディング
新規に追加されたトピックからは、モジュール機能とセキュアコーディングが重点的に出題されていました。
###重点的に出題されるトピック
- シリアライゼーション
- ジェネリクス
- JDBC
- ラムダ式
- Stream API
シリアライゼーションの比率が思ったより高いので、しっかり理解しておきましょう。
JDBCについては、セキュアコーディングと絡めたSQLインジェクション対策に関する問題も出たりします。
ラムダ式、Stream APIはSE 8と同様に最も重要なトピックで、出題比率もかなり高いです。
###出なくなったトピック
- Date and Time API
- Fork/Join フレームワーク
- デザインパターン
Date and Time APIは、セミナーで出てこないと言われたのですが、1問だけフォーマットの問題が出てきました。基本だけでも覚えたほうがよさそうです。
デザインパターンも、Singletonパターンは同期制御などのセキュアコーディングと絡めて出題されるかもしれません。
#Java SE 11の実行環境
Pleiades Java 11 標準搭載と Eclipse コードネーム終焉
リンク先からダウンロードできるPleiades All In Oneに一通りそろっているので、手っ取り早く使いたいならこれが一番です。
#インタフェースのprivateメソッド
Java 9 から、インタフェースにprivateメソッドを定義できるようになりました。
同じインタフェース内のstaticメソッドやdefaultメソッドで使用するために用いるもので、実装を持ち、継承や外部のクラス等からの呼び出しは出来ません。
public interface SampleInterface{
default void defaultMethod(){
privateMethod("太郎");
privateMethod("次郎");
}
static void staticMethod(){
privateStaticMethod("三郎");
privateStaticMethod("四郎");
}
private void privateMethod(String name){
System.out.println( "こんにちは、" + name + "さん");
}
private static void privateStaticMethod(String name){
System.out.println( "こんにちは、" + name + "さん");
}
}
public class SampleClass implements SampleInterface{
public static void main(String[] args) {
SampleClass sc = new SampleClass();
sc.defaultMethod();
SampleInterface.staticMethod();
}
}
こんにちは、太郎さん
こんにちは、次郎さん
こんにちは、三郎さん
こんにちは、四郎さん
このように、インタフェース内で似たような処理を記述するときに何回も同じコードを書かずに簡潔に記述することが出来ます。
#ローカル変数型推論(var)の使用法
Java 10から、ローカル変数時のvarを用いた型推論が使えるようになりました。変数を代入した際の右辺から型を自動的に推論します。
###使用例
public class LocalVariableType {
public static void printInt(int i) {
System.out.println(i + " is int type");
}
public static void printBoolean(boolean bl) {
System.out.println(bl + " is boolean type");
}
public static void printString(String str) {
System.out.println(str + " is String type");
}
public static void main(String[] args) {
var i = 1;
var bl = true;
var str = "String";
printInt(i);//int型の引数を取る関数
printBoolean(bl);//boolean型の引数を取る関数
printString(str);//String型の引数を取る関数
//Listやfor文でも使用可能
var list = List.of(i,bl,str);
for(var l :list) {
System.out.print(l + " ");
}
//Java 11から、ラムダ式の引数にvarが使用できるようになり、これによりアノテーションを付けられるようになった
Function<String,Integer> function = (@Annotation var a) -> a.length();
}
}
1 is int type
true is boolean type
String is String type
1 true String
JavaScriptなどと同様に、右辺から型推論が行えるようになったのが分かると思います。リストの型推論はジェネリクスの記述を省略できるので、コード量が少なくなって見やすくなると思います。
###コンパイルエラーになる例
右辺から型を推論できない場合は、コンパイルエラーになります。
public class LocalVariavleType2 {
public static void main(String[] args) {
var v;//宣言時に初期化を行わなければならない
var f1 = a -> a + 1; //ラムダ式は代入できない
var f2 = (Runnable)() -> new String("Hello");//関数型インタフェースをキャストすると初期化できる
var list1 = {1, 2, 3};//配列の初期化には使用できない
var list2 = new int[]{1, 2, 3};//型を指定すると初期化できる
var list3 = new ArrayList<>();//コンパイル可能だが、型はArrayList<Object>になる
var n = null;//nullは代入不可
}
}
varの使用法について直接聞いてきたのは2問ほどでしたが、当たり前のように問題文のコード中に書かれていたりするので、意味は分かるようにしておきましょう。
#モジュール機能
以下のOracleのページを参考に学習を進めました。
Java 9モジュールを理解する
###モジュールとは
Java 9から、モジュール機能が使えるようになりました。モジュールとは、パッケージの上位概念で、関連するパッケージ、リソースとモジュール記述子を一纏めにしたものです。
また、java.langなどの基本的なライブラリもjava.baseモジュールに格納されています。このjava.baseモジュールは、後述するrequiresキーワードなどのモジュール記述子を用いることなくアクセスできます。
###モジュール化のメリット
#####依存関係の簡潔化
モジュール単位に依存関係を記述することで、簡潔にパッケージ間の依存関係を記述できるようになりました。
#####強力なカプセル化
配下のパッケージに対するアクセス権限をより細かく指定できるので、より強力なカプセル化が可能になります。
#####実行環境をモジュールごとにカスタムできる
必要なモジュールのみでランタイムを作成することで、ランタイムのサイズ削減や、JVMによる最適化の効果がより向上します。
###モジュールの定義
ソースフォルダのトップに、次のようなモジュール定義ファイルを配置することで、モジュールの定義が出来ます。モジュール名は逆ドメイン式で記載し、一意でなければなりません(同じ名前を含むことはできない)。
module Java_SE_11_Gold {
//モジュール記述子などを記述
}
###モジュール記述子
#####requires
module A {
requires B;
}
そのモジュールが別のモジュールに依存することを示す記述子です。上記のコードの場合、モジュールAがモジュールBを読み取ります。
#####requires transitive
module A {
requires B;
}
module B{
requires transitive java.sql;
}
指定したモジュールを読み取りつつ、requires transitive記述子を記述したモジュールが読み取られる際に、指定したモジュールを読み取れるようにします。
上記のコードの場合、モジュールBはjava.sqlを読み取ります。モジュールAもモジュールBを読み取りますが,その際にモジュールAもjava.sqlを同時に読み取れるようにしています。モジュールBで単にrequiresのみを用いると、モジュールAはjava.sqlを読み込めません。
#####requires static
コンパイル時のみmoduleの存在が必要です。ランタイム時は必要ありません。あくまでも想像ですが、アノテーションとかに対して使うのかなと思います。
#####exports
module A{
exports samplePackage;
exports samplePackage2 to B,C;
}
モジュールは以下のパッケージは、デフォルトで外部からのアクセスができません。モジュールの外からのアクセスを許可するには、exports記述子を用います。exportsした場合、public型(およびネストされているpublic型、protected型)に対してアクセスを許可します。上記のコードでは、パッケージsamplePackageを外部からアクセス可能に指定しています。
また、exports...to記述子を用いることで、アクセスを許可する対象のパッケージを指定できます(カンマ区切りで複数指定可能)。上記のコードでは、モジュールBとモジュールCに対してパッケージsamplePackage2へのアクセスを許可しています。
#####opens
コンパイル時は参照できませんが、実行時は参照できるように制御することができます。
パッケージ単位で設定するopens...toと、モジュール全体を設定するopen moduleの2種類があります。
module Java_SE_11_Gold{
opens samplePackage;//指定したパッケージを全てのモジュールから参照できるようにする
opens aamplePackage2 to B,C;//カンマ区切りでopneする対象のモジュールを個別に複数指定できる
}
open module Java_SE_11_Gold{//配下のモジュールをすべて参照できるようにする
}
#####uses
モジュールが使用するサービスを指定します。サービスとは、既知のインタフェースおよびクラス(通常は抽象クラス)のセットです。詳しくは、以下のjavadocへのリンクを参照してください。
https://docs.oracle.com/javase/jp/8/docs/api/java/util/ServiceLoader.html
#####provides...with
モジュールがサービスの実装を提供することを指定します。providesの後にはusesに提供するインタフェースまたは中小クラスを指定して、withの後にはインタフェースを実装したかabstractクラスを継承したクラスの名前を指定します。
###無名モジュールと自動モジュール
無名モジュールと自動モジュールは、主にJava 8以前のコードにモジュールシステムを適用するための特殊なモジュールです。
ここでは試験にかかわりそうなところを説明しますので、詳しく知りたい方は以下のリンクを参照してください。
モジュールシステムを学ぶ
####無名モジュール
クラスパスから読み込まれたパッケージや型は、無名モジュールというモジュールに属するようになります。無名モジュールはモジュール定義 (module-info.java)を持つことができないため、 モジュール記述子を用いて依存関係を記述することはできませんが、暗黙的に次のように振る舞います。
- 無名モジュール内のすべてのパッケージがexportsされます。
- 無名モジュールは、モジュールグラフ上のすべてのモジュールをrequiresします。
####自動モジュール
非モジュールをモジュールとして扱うためのモジュールです。モジュール定義を持たないコードは、自動モジュールとして認識されます。無名モジュール同様、明示的にモジュール記述子を定義することはできず、以下のような暗黙的な振る舞いをします。
- 自動モジュール内の全てのパッケージがexportsされます。
- 自動モジュールは、モジュールグラフ上の全てのモジュールをrequiresします。
####モジュール間の参照可能性
名前付きモジュールは、モジュール定義が存在する通常のモジュールのことを指します。また、--add-moduleコマンドは、任意のモジュールをルートモジュールに追加することが出来るコマンドです。
以下の表で、(被参照)とついている側が参照されるモジュール、そうでない側が参照を行うモジュールの種類を表します。
名前付きモジュール(被参照) | 自動モジュール(被参照) | 無名モジュール(被参照) | 特長 | |
---|---|---|---|---|
名前付きモジュール | 参照可 | 参照可 | 参照不可 | モジュール定義あり、モジュールパスに配置 |
自動モジュール | --add-moduleコマンドを用いて参照可 | 参照可 | 参照可 | モジュール定義なし、モジュールパスに配置 |
無名モジュール | --add-moduleコマンドを用いて参照可 | --add-moduleコマンドを用いて参照可 | 参照可 | モジュール定義なし、クラスパスに配置 |
###ServiceLoaderクラス
SPI(Survice Provider Interface)と呼ばれる、第三者が実装を提供するための仕組みを扱うためのクラスです。ライブラリはSPIとなるサービス(インタフェースおよびクラス(通常は抽象クラス)のセット)を提供し、ServiceLoaderで第三者の実装を読み込みます。
詳しい仕様やメソッドは、以下のOracle docsを参照してください。
ServiceLoader (Java Platform SE 8 ) - Oracle Docs
###jdepsの使用法
jdepsコマンドを使うと、以下のようにjarファイル(またはclassファイル)がどのモジュールに依存しているか調べることが出来ます。
jdeps -s jarファイル
(jarファイル名) -> (依存しているモジュール名)
(jarファイル名) -> (依存しているモジュール名)
(jarファイル名) -> (依存しているモジュール名)
……
-sというのは、依存性のサマリーを出力するオプションです。オプションは、以下のOracle docsのページに詳しく記載されています。サマリーを出力する-sおよび-summary、依存関係を出力する-vおよび-verbose、指定したパッケージで依存関係を指定する-p<パッケージ名>および-package<パッケージ名>、指定された正規表現パターンに一致するパッケージで依存関係を検索する-e<正規表現パターン>および-regex<正規表現パターン>あたりが問題に出てきた記憶があります。
また、オプションを指定しなかった場合は、デフォルトでパッケージ間の依存関係を出力する、-verbose:packageオプションになります。
詳しくは、以下のOracle Docsを参照してください。
jdeps - Oracle Docs
各コマンドの具体的な動作例は、以下のリンクを参照するとわかりやすいでしょう。
Java 8から付属する、依存関係解析ツールjdepsを試してみる
#セキュアコーディング
セキュアコーディング分野では、クレジットカード番号などの重要なデータをやり取りする際にデータの漏洩や改ざんを防ぐための方法や、悪意のある攻撃者からSQLインジェクションなどの攻撃を防ぐための方法が問われます。常識的に考えればあまり知識を必要とせずに解ける問題もあり、新分野の中では比較的楽な部類に入るのではないかと思います。
Oracleのセミナーでは、以下のJPCERTのリンクの「00. 入力値検査とデータの無害化 (IDS)」、「12. 入出力 (FIO)」、「13. シリアライズ (SER)」、「14. プラットフォームのセキュリティ (SEC)」、「49. 雑則 (MSC)」から出題されるという情報がありました。
Java コーディングスタンダード CERT/Oracle 版
主にSQLインジェクション、カプセル化、シリアライゼーションに関する問題が出ます。上記リンクの適合コードと違反コードを見比べて、どこがいけないのか理解するようにすると良いでしょう。
また、前提としてWebサイトに対する基本的な攻撃の手法について理解していると良いと思います。以下のリンクに各攻撃方法についての説明が記載されています。
サイバー攻撃の種類とWebセキュリティ
###入力値検査
入力値検査を行うことで、SQLインジェクションやXSS(クロスサイトスクリプティング)を防ぐことが出来ます。例えば以下のようなクエリを考えます。
SELECT * FROM db_user WHERE username='<USERNAME>' AND password='<PASSWORD>'
usernameとして「ユーザー名' or '1'='1」を与えれば、このクエリは以下のようになります。
SELECT * FROM db_user WHERE username='ユーザー名' or '1'='1' AND password='<PASSWORD>'
本来はユーザー名とパスワードの両方のチェックを行いますが、クエリをこのように書き換えることによって「username='ユーザー名'」か「'1'='1' AND password=''」のどちらかが真であればレコードにアクセスできるようになります。つまり、この場合はユーザー名を入力するだけでパスワードの入力値にかかわらずレコードにアクセスできるようになります。
これを防ぐ方法としては、入力値に対して特定の記号や文字などが入っていないか、必要以上に長くないかどうかのチェックを行う必要があります。SQLインジェクション対策を行う場合は、PreparedStatementを用いてSQLを組み立てる方法もあります。
###ファイル入出力
基本的にはファイルがちゃんとクローズされているかどうかが問われます。ファイルがクローズされていないと、メモリリークを起こし、リソースを消費します。ファイルをクローズする方法としては、try-with-resource文を用いる方法のほか、close()メソッドを用いる方法があります。
また、ファイルを出力する場合は、読み取り専用のディープコピーを作成して元データが変更されないようにすると良いでしょう。
###シリアライゼーション
シリアライズとは、Javaのインスタンスをバイト列として出力して保存しすることで、逆にバイト列をJavaのインスタンスにする操作はデシリアライズと呼ばれます。主にJavaのインスタンスをファイルの保存して、インスタンスの情報を永続化させるのに用います。SE 8の範囲なので、詳しい説明は割愛してセキュアコーディングにかかわるところのみを説明したします。
#####シリアライゼーションを行う際の互換性
シリアライズしたクラスのフィールドなどを変更した際、シリアライズを復元した際に復元されるインスタンスと変更後のクラスの間に矛盾が生じる恐れがあります。これを防ぐために、serialVersionUIDに値を与えて、変更があった場合はこの値を毎回変えます。serialVersionUIDには、値が変更されないようにfinal修飾子をつけておきます。また、static修飾子やtransient修飾子を用いることで、シリアライズを行わないフィールドを事前に指定することが出来ます。これはクラス等に変更を加えた際の互換性の向上に役立ちます。
また、シリアライズを行うデータにクレジットカード情報などの重要な情報を入れる場合は、事前に暗号化を行うなどの処理を行わないと、悪意ある改ざんを受ける可能性があります。
###並列処理における排他制御
並行処理を行った際に、スレッドセーフかどうか、デッドロックやライブロックが発生するかどうか問われる問題が出ました。並行処理については、SE 8でも範囲になっているので詳しい説明は省きますが、デッドロック・ライブロックについては本番でも2~3問ほど問われたのでこれについて説明したいと思います。
#####デッドロック
1つのスレッドが1つの排他的なリソースを利用する場合は、ほかのスレッドがそのリソースを解放するまで待機状態にすれば、問題なく並列処理できます。1つのスレッドが2つ以上のリソースを必要とするときに、並列処理を行うと別々のスレッドが排他的なリソースを取り合って動作しなくなる状態がデッドロックです。
対策としては、ロックに順序をつける(例えば、あるスレッドが2番目以降のリソースのロックを獲得しようとしたときに、1番目のリソースのロックを先に獲得するように試みる)などの方法があります。
図解、コード例などはこちらのサイトに詳しく記載されているので、是非参考にしてください。
デッドロックについて - Java入門 - IT専科
#####ライブロック
デッドロック回避のために複数のスレッドが共有資源の獲得と解放を行っているが、待ち時間が同じ、次の処理に進むためのスレッドの優先権が他のスレッドに比べて低いなどの理由で進まない処理を繰り返すことをライブロックといいます。
回避するためにはスレッドの待ち時間に乱数を用いる、ほかのスレッドに優先権を与える(yieldメソッドなど)を用いるなどの方法があります。
コード例などはこちらのサイトに詳しく記載されているので、是非参考にしてください。
ライブロック について - ねぇうしくんうしくん
#最後に
SE 11 Goldに関しては、参考書がない状態でも旧来の範囲をしっかり学習すればモジュール機能など全く新しい要素以外はそこまで苦戦することもないのかなと思います。
また、内容の不備や漏れなどがありましたらコメントで指摘していただけると幸いです。
#参考リンク
Java SE 11 Programmer II | Oracle University
Java 9モジュールを理解する
モジュールシステムを学ぶ
ServiceLoader (Java Platform SE 8 ) - Oracle Docs
jdeps - Oracle Docs
Java 8から付属する、依存関係解析ツールjdepsを試してみる
Java コーディングスタンダード CERT/Oracle 版
サイバー攻撃の種類とWebセキュリティ
デッドロックについて - Java入門 - IT専科
ライブロック について - ねぇうしくんうしくん