本記事は、Oracle Certified Professional, Java SE 11 Programmer (Java Gold) 試験の合格に向け、出題頻度の高い重要ポイントや、誤解しやすい仕様をコンパクトにまとめたものです。
(自身への復習用です。)
出題傾向と重量度
第1章:クラスとインタフェース (良く出る / A)
1. クラスと修飾子の仕様
1. インナークラスとstatic
インナークラスを扱う際は、static修飾子の有無をチェックすることが重要です。非staticなインナークラスをインスタンス化するには、外側のクラス(エンクロージングクラス)のインスタンスが必須です。一方で、staticなインナークラス(ネストされたスタティッククラス)は、外側クラスのインスタンスがなくても直接生成できます。
class Outer {
static class StaticInner {}
class Inner {}
}
// staticインナークラスの生成 (外側インスタンス不要)
Outer.StaticInner si = new Outer.StaticInner();
// 非staticインナークラスの生成 (外側インスタンスが必要)
Outer outer = new Outer();
Outer.Inner i = outer.new Inner();
2. defaultメソッド
defaultメソッドは、インタフェース内で具体的な処理を持つことができるメソッドです。メソッドの前にキーワード default を付けることで定義されます。
既存のインタフェースに**後方互換性**を保ちつつ、新しい機能を追加するために導入されました。
public interface Printer {
// 抽象メソッド(実装クラスでオーバーライド必須)
void print(String data);
// defaultメソッド(具体的な実装を持つ)
default void setup() {
System.out.println("プリンターの設定を完了しました。");
}
}
抑えるべき重要ポイント
defaultメソッドの動作と、継承・実装時のルールについて、以下の点を押さえておきましょう。
A. 後方互換性(最も重要な目的)
- 目的: 既存のインタフェースにメソッドを追加しても、そのインタフェースを既に実装している多数のクラス(既存のコード)を壊さないことです。
-
効果:
defaultメソッドを追加しても、実装クラスはそれを強制的にオーバーライドする必要はありません。既存の実装クラスは、自動的にそのデフォルト実装を継承します。
B. 継承とオーバーライド
-
継承: 実装クラスは、
defaultメソッドを自動的に継承します。 -
オーバーライド: 必要に応じて、実装クラスで
defaultメソッドをオーバーライドし、独自の処理を提供できます。この場合、defaultキーワードは使用しません。
C. 多重継承の解決ルール(ダイヤモンド問題)
実装クラスが複数のインタフェースを実装し、それらのインタフェースに同じシグネチャ(メソッド名、引数リスト)を持つdefaultメソッドが存在する場合、コンパイルエラーとなります。
-
解決ルール: この衝突を避けるためには、実装クラスでそのメソッドを必ずオーバーライドし、どちらの
defaultメソッドを使用するか明示的に指定するか、独自の処理を実装する必要があります。// 衝突時の解決例(特定のインタフェースのdefaultメソッドを呼び出す) @Override default void setup() { // InterfaceAのデフォルト実装を呼び出す InterfaceA.super.setup(); }
D. 抽象メソッドとの関係
-
defaultメソッドは抽象メソッドではありません。 - インタフェースに抽象メソッドが追加された場合、それを実装クラスがオーバーライドしないと、従来通りコンパイルエラーになります。これは
defaultメソッドとは大きく異なる点です。
E. 具象クラスの優先順位
もし、実装クラスが継承するクラス(具象クラスまたは抽象クラス)と、実装するインタフェースのdefaultメソッドで衝突が発生した場合、クラス側のメソッドが優先されます。
クラス優先ルール:
- 具象クラスで定義されたメソッドが最も優先される
- 次に、継承したクラス(抽象クラス)のメソッドが優先される
- 最後に、インタフェースの
defaultメソッドが適用される
第2章:関数型インタフェースとラムダ式 (非常に良く出る / S)
ラムダ式とメソッド参照
関数型インタフェースは、抽象メソッドを1つだけ持つインタフェースです。
Java8で導入された「ラムダ式」を扱うための特別なインターフェース。
より簡単に言うなら、「やることが一つだけに決まっているインターフェース」となる
Javaの関数型インターフェースは、抽象メソッドを一つだけ持つインターフェースであり、ラムダ式やメソッド参照のターゲット型として機能します。これらは主に java.util.function パッケージに定義されています。
以下に、主要な組み込み関数型インターフェースを機能別に一覧で示します。
主要な組み込み関数型インターフェース
| カテゴリ | インターフェース名 | 抽象メソッド (SAM) | 引数の型 | 戻り値の型 | 目的 |
|---|---|---|---|---|---|
| 述語 (判定) | Predicate<T> |
boolean test(T t) |
T |
boolean |
引数を受け取り、条件を判定する(真偽値を返す)。 |
| 関数 (変換) | Function<T, R> |
R apply(T t) |
T |
R |
引数を受け取り、異なる型の結果を返す(型変換)。 |
| 消費者 (実行) | Consumer<T> |
void accept(T t) |
T |
void |
引数を受け取り、何らかの処理を実行する(戻り値なし)。 |
| 供給者 (生成) | Supplier<T> |
T get() |
なし |
T |
結果を提供する(引数なし)。 |
| 単項演算子 | UnaryOperator<T> |
T apply(T t) |
T |
T |
引数と同じ型の結果を返す(同じ型の変換)。Functionの特殊化。 |
| 二項演算子 | BinaryOperator<T> |
T apply(T t1, T t2) |
T, T |
T |
2つの引数と同じ型の結果を返す(同じ型の結合)。BiFunctionの特殊化。 |
ラムダ式が外部のローカル変数を使用する場合、その変数は**事実上のfinal(effectively final)**である必要があります。
ラムダ式は、メソッド参照(例: String::length)として省略記述されることがあります。
// メソッド参照の例
List<String> list = Arrays.asList("a", "bb", "ccc");
// String::length は s -> s.length() と等価
List<Integer> lengths = list.stream()
.map(String::length)
.collect(Collectors.toList());
// lengths: [1, 2, 3]
第3章:並列処理 (良く出る / A)
並列処理の基本とタスクの定義
並列処理とは、複数の処理を同時に実行することで、プログラムのパフォーマンスを向上させる手法です。Javaでは主にスレッドを用いて実現されます。
1. タスクの定義: RunnableとCallable
並列実行したい処理の単位はタスクとして定義されます。Javaには主に2つのタスク定義インターフェースがあります。
-
Runnable:- 戻り値がないタスクを定義します
- 抽象メソッドは
void run()です - チェック済み例外をスローすることはできません
-
Callable<V>:-
戻り値があるタスクを定義します。型パラメータ
Vが戻り値の型です - 抽象メソッドは
V call() throws Exceptionです -
チェック済み例外をスローできる点が
Runnableとの大きな違いです
-
戻り値があるタスクを定義します。型パラメータ
import java.util.concurrent.Callable;
// 戻り値を持つタスクの定義
class SumTask implements Callable<Integer> {
private final int value;
public SumTask(int value) {
this.value = value;
}
// Integer型の結果を返す
@Override
public Integer call() throws Exception {
System.out.println("タスク実行中...");
return value + 100;
}
}
実行管理: ExecutorServiceとFuture
直接 Thread を生成して管理するのは煩雑であるため、Javaでは**ExecutorService**(スレッドプール)を使用してタスクの実行を管理するのが一般的です。
1. ExecutorService (スレッドプール)
ExecutorService は、タスクのキューイング、スレッドの再利用、実行結果の管理を一元的に行います。
-
execute(Runnable task):Runnableタスクを投入します(結果は取得しません)。 -
submit(Callable<V> task):Callableタスク、または結果を取得したいRunnableタスクを投入します。このメソッドは必ず**Future**オブジェクトを返します。
2. Future (実行結果の管理)
Future<V> オブジェクトは、非同期で実行されたタスクの結果を管理するオブジェクトです。
-
V get(): タスクの実行が完了するまで処理をブロックし(待機し)、結果を取得します。タスクが例外をスローした場合、ExecutionExceptionがスローされます。 -
boolean isDone(): タスクが完了したかどうかをチェックします。
import java.util.concurrent.*;
// ExecutorServiceとFutureを使用したタスク実行
ExecutorService executor = Executors.newSingleThreadExecutor();
SumTask task = new SumTask(50);
// Callableを投入し、Futureオブジェクトを取得
Future<Integer> future = executor.submit(task);
try {
// get() で結果を取得するまでブロック(待機)される
Integer result = future.get();
System.out.println("結果: " + result); // 結果: 150
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
} finally {
executor.shutdown(); // 処理が終わったらExecutorServiceを終了
}
排他制御と同期化
複数のスレッドが共通のリソース(変数やオブジェクトなど)に同時にアクセスすると、予期せぬ結果やデータ破壊が起こる**競合状態(Race Condition)が発生します。これを防ぐために同期化(Synchronization)**を行います。
1. synchronizedキーワード
最もシンプルな排他制御の方法です。メソッド全体または特定のコードブロックをロックし、一度に一つのスレッドしか実行できないようにします。
// synchronizedメソッドの例
public synchronized void increment() {
// このメソッドは同時に一つのスレッドしか実行できない
count++;
}
2. ReentrantLock(高度な排他制御)
java.util.concurrent.locks.ReentrantLock は、synchronized よりも柔軟で高度な機能(ロックの試行、タイムアウト設定など)を提供します。
-
lock()とunlock()を手動で呼び出す必要があります。 -
試験の最重要ポイント: 例外が発生した場合でもロックを確実に解放するため、
try-finallyブロック内でunlock()を呼び出すことが必須です。これを怠るとデッドロックを引き起こす可能性があります。
import java.util.concurrent.locks.ReentrantLock;
ReentrantLock lock = new ReentrantLock();
public void safeUpdate() {
lock.lock(); // ロックを取得
try {
// 共通リソースへの安全なアクセス
// ...
} finally {
// 処理の成功・失敗に関わらず、必ずロックを解放する
lock.unlock();
}
}
第4章:ストリームAPI (非常に良く出る / S)
ストリームの操作は、mapやfilterなどの中間操作と、collectやsumなどの終端操作に分かれます。中間操作は遅延評価され、終端操作が呼び出された時点で初めて実行されます。
基本データ型(int, longなど)を扱うIntStreamなどは、ボクシングのオーバーヘッドがなく効率的です。
最も重要な終端操作の一つがcollectです。特にCollectors.toMap()を使用する際、キーが重複するとIllegalStateExceptionが発生するため、衝突を避けるためのマージ関数(第3引数)の設定が必要です。
Stream APIの重要仕様
中間操作は遅延評価され、終端操作が呼ばれたときに初めて実行されます。
collectメソッドで要素をMapに変換するtoMap()を使う際、キーが重複するとIllegalStateExceptionが発生するため、衝突時の解決ロジック(マージ関数)を設定する必要があります。
// toMap()でのキー衝突解決
List<String> items = Arrays.asList("apple", "apricot", "banana");
Map<String, String> map = items.stream()
.collect(Collectors.toMap(
s -> s.substring(0, 1), // キー: 最初の文字 (a, a, b)
s -> s, // 値: フルーツ名
(oldValue, newValue) -> oldValue // マージ関数: 衝突時、oldValueを採用
));
// map: {a=apple, b=banana}
第5章:入出力(NIO.2) (出る / B)
ファイル操作のjava.nio.file.Filesメソッドは、例外発生の条件を正確に覚えます。Files.createFile()は、ターゲットにファイルが既に存在すると **FileAlreadyExistsExceptionをスローします。ディレクトリを作成する際、親ディレクトリがない場合に親ごと作成するにはFiles.createDirectories()**を使用します。
Pathの操作では、**resolve()がパスを結合し、relativize()**が相対パスを計算します。
import java.nio.file.Paths;
import java.nio.file.Path;
Path base = Paths.get("/a/b/c");
Path target = Paths.get("/a/x/y");
// resolve() による結合: /root/dir/file.txt
Path p1 = Paths.get("/root").resolve("dir/file.txt");
// relativize() による相対パス生成: /a/b/c から /a/x/y への移動
Path relativePath = base.relativize(target);
// 結果: ../../x/y
第6章:JDBCによるデータベース連携 (出る / B)
PreparedStatementはSQLインジェクション対策の基本です。パラメータの置き換え文字?に対応するインデックスは、1から始まります(0は実行時エラー)。また、SQL文中の全ての?に値を設定する必要があります。
// PreparedStatementのパラメータ設定
// SQL: INSERT INTO users (id, name) VALUES (?, ?)
// ps.setInt(0, 100); はエラーになる
ps.setInt(1, 100); // 1番目の ?
ps.setString(2, "Taro"); // 2番目の ?
検索結果を受け取るResultSetは、値を取得する前に、必ずnext()メソッドを呼び出してカーソルを先頭レコードに移動させる必要があります。ResultSetのカラムインデックスも1から始まります。
第7章:汎用とコレクション (良く出る / A)
Comparableはクラス自体に自然順序付け(1つだけ)を定義し、Comparatorは外部から複数のカスタム順序付けを定義できます。Comparatorは**thenComparing()**を使ってソート基準を連結できます。
コレクションの代入(例: List newList = oldList;)は、シャロー・コピーとなり、リスト本体は共有されます。
ジェネリクスのワイルドカードでは、 extends T>(上限境界)は読み出し専用と考え、 super T>(下限境界)は書き込み(追加)安全であると考えます。
// Comparatorの連結 (名前順、同名なら年齢順)
Comparator<Person> nameThenAge = Comparator.comparing(Person::getName)
.thenComparing(Person::getAge);
ComparableとComparatorの違い
ComparableとComparatorの違いは、Javaのコレクションをソートする際に非常に重要になります。どちらもオブジェクトの順序を定義しますが、ロジックをどこに実装するかという設計思想が異なります。
📊 Comparable vs Comparator
| 項目 | Comparable<T> |
Comparator<T> |
|---|---|---|
| 場所(ロジック) | 🗄️ ソート対象クラスの内部に実装 | 💡 クラスの外部(独立したクラス、またはラムダ式)に定義 |
| 定義する順序 | 🌲 自然順序付け (Natural Ordering) | 🛠️ カスタム順序付け (Custom Ordering) |
| メソッド | compareTo(T other) |
compare(T o1, T o2) |
| 変更の必要性 | ソート対象クラスの定義を変更する必要がある | ソート対象クラスの定義を変更する必要がない |
| 定義できる数 | 1つのみ | 複数定義できる |
🔍 使い分けのポイント
-
Comparableを使用するケース:- そのクラスのオブジェクトにとって、「これが唯一の、標準的な並び順だ」と定義したい場合
- 例:
Stringクラスはアルファベット順、Integerクラスは数値順という自然順序付けをComparableで実装しています
-
Comparatorを使用するケース:- ソート対象のクラスに手を加えずに外部からソート順を定義したい場合
- 複数のソート基準(例: 名前順、年齢順、登録日順)を設定したい場合
第8章:アノテーション (ほぼ出ない / C)
@Override、@Deprecatedといった組み込みアノテーションや、カスタムアノテーションの定義に使用するメタアノテーション(@Retention、@Target)の役割を把握します。
第9章:例外とアサーション (出る / B)
マルチキャッチ (catch (ExceptionA | ExceptionB e)) では、継承関係にある例外を同時に記載することはできません(コンパイルエラー)。
assert文は、条件が満たされない場合にAssertionErrorをスローします。このチェックはデフォルトで無効であり、JVM実行時に-enableassertions(または-ea)オプションで有効化する必要があります。
第10章:ローカライズ (出る / B)
Localeクラスのインスタンスは、getDefault()のほか、コンストラクタ、ロケール定数、**forLanguageTag()**など複数の方法で生成可能です。
リソースバンドルの解決では、プロパティファイルは最も具体的なロケール(例: BaseName_ja_JP)から検索され、該当がなければより抽象的なファイルへと検索が進みます。DateTimeFormatterの書式パターン(MとMM、Hとhなど)の違いを理解します。
第11章:モジュール・システム (良く出る / A)
モジュール定義ファイル(module-info.java)のディレクティブが重要です。
exports: 他のモジュールから利用可能なパッケージを宣言します。
requires: 依存関係を宣言します。
opens: リフレクションによるアクセスを許可します。
自動モジュールは、クラスパスに置かれたJARから自動生成され、自動的に全パッケージをエクスポートします。
第12章:セキュアコーディング (良く出る / A)
セキュアコーディングは、攻撃と対策の対応付けが中心です。
SQLインジェクションの対策はPreparedStatementの使用が基本です。
DoS攻撃(サービス拒否)の対策として、ファイルサイズやリクエスト数の制限があります。
整数オーバーフロー攻撃の対策としてアップキャストによる値の検証があります。
また、入力を検証する、最小権限の原則に従うなど、セキュアコーディングのトップ10原則も確認しておきましょう。
はい、セキュアコーディングのトップ10原則とは、セキュリティ上の脆弱性を防ぎ、信頼性の高いソフトウェアを開発するために、開発者が遵守すべき指針をまとめたものです。
これらは、特定の組織や規格(例:OWASPやCISQ)に基づいて推奨されることが多く、プログラムの設計から実装、テストに至るまで適用されます。
🛡️ セキュアコーディングのトップ10原則
-
入力を検証する(Validate input)
- 外部から受け取る全てのデータ(ユーザー入力、ファイル、ネットワークデータなど)を信頼せず、サイズ、型、形式が期待どおりであることを確認します。
-
コンパイラからの警告に注意を払う(Heed compiler warnings)
- コンパイラが出す警告(特にセキュリティ関連の潜在的な問題を指摘するもの)を無視せず、適切に対処します。
-
セキュリティポリシーを実装するように設計する(Design to implement a security policy)
- コードを書く前に、システム全体のセキュリティ要件(ポリシー)を明確にし、そのポリシーがコードベース全体で強制されるように設計します。
-
シンプルにする(Keep it simple)
- 複雑なコードはバグやセキュリティホールを生みやすいです。設計と実装をできる限りシンプルに保ち、監査しやすくします。
-
拒否をデフォルトにする(Default deny / Fail secure)
- アクセス制御において、許可されていないものは全て拒否するホワイトリスト方式を基本とします。すべてを許可してから例外を設ける(ブラックリスト方式)のは危険です。
-
最小権限の原則に従う(Adhere to the Principle of Least Privilege)
- ユーザー、プロセス、またはプログラムの各コンポーネントに、そのタスクを遂行するために必要最小限のアクセス権のみを与えます。
-
ほかのシステムに送信するデータを無害化する(Sanitize data sent to other systems)
- データベースやウェブブラウザなど、異なるシステムにデータを渡す前に、特殊文字のエスケープや無害化処理(サニタイズ)を行います。(例:SQLインジェクションやXSS対策)。
-
多重防御を実践する(Practice defense in depth)
- 一つの防御層が破られたとしても、システム全体が侵害されないように、複数のセキュリティ層を重ねて実装します。
-
効果的な品質保証テクニックを利用する(Use effective quality assurance techniques)
- 静的解析ツール、動的テスト、ピアレビュー(コードレビュー)など、セキュリティ欠陥を見つけるための体系的なテストと監査を実施します。
-
セキュアコーディング標準を採用する(Adopt secure coding standards)
- 組織や業界が定めた安全なコーディングルール(例:CERT Secure Coding Standards)を導入し、開発チーム全体で遵守します。
【補足】コード記載時のひな型
