はじめに
表題の通り2025年11月、Java Golg Programmer SE11に合格しました。
SE17ではなくSE11なのかというと、Silver取得は4年前に遡り、当時はSE17の参考資料がなくSE11を選択するしかありませんでした。Silver SE11を受験している場合、GoldもSE11しか選べないみたいです。まぁ、章の内容に変更があるもののSE11とSE17でそこまで技術的な違いはないかなと思っています。
今回、4年越しのJavaの資格試験勉強だったので基本的な部分も掘り下げていたり、
実務に通用する知識取得を想定しながら学習していて( 実は開発未経験です...。 )、その際に書き溜めたメモを備忘録としてここでまとめることにしました。
学習は黒本 + 深堀で対話型AIを利用していたメモになるので、情報に誤りがあるかもしれませんがそこは参考資料として留めていただき学習の助けになればと思っております。章立てについては以下リンク(Oracle公式のGold SE11案内ページ)の「2. 試験内容チェックリスト」ベースで進めます。※黒本ベースの章立てだと著作権との兼ね合いもあるので...。
すべてをこの記事でまとめる予定ですが、すきま時間にちょこちょこ書いてます。そうすると公開するまでに相当時間を要するかもしれないので公開しておいて書き増ししていく方針としています。記載も順追ってではなくまとめやすい所から着手していきます。
着手状況
以下に記載日を取りまとめていますのでご確認ください。
1.Javaの基礎
2.例外処理とアサーション
3.Javaのインタフェース
4.汎用とコレクション
5.関数型インタフェースとラムダ式
6.JavaストリームAPI
7.組込み関数型インタフェース
8.ストリームに対するラムダ演算
9.モジュール型アプリケーションに移行する
10.モジュール型アプリケーションにおけるサービス
11.並列処理
12.並列ストリーム
13.I/O(基本およびNIO2)
14.Java SEアプリケーションにおけるセキュア・コーディング
15.JDBCによるデータベース・アプリケーション
16.ローカライズ 2025/04/27_済
17.アノテーション 2025/04/25_済
1. Javaの基礎
・finalクラスを作成および使用する
・内部クラス、ネストしたクラス、および無名クラスを作成および使用する
・列挙を作成および使用する
2. 例外処理とアサーション
try-with-resources構造を使用する
カスタム例外クラスを作成および使用する
アサーションを使用して不変量をテストする
3. Javaのインタフェース
デフォルト・メソッドによってインタフェースを作成および使用する
プライベート・メソッドによってインタフェースを作成および使用する
4. 汎用とコレクション
ラッパー・クラス、オート・ボクシングおよびオート・アンボクシングを使用する ダイアモンド表記とワイルドカードを使用して汎用クラスおよびメソッドを作成および使用する コレクション・フレームワークについて説明する。主要なコレクション・インタフェースを使用する ComparatorインタフェースとComparableインタフェースを使用する コレクション用の便利なメソッドを作成および使用する
5. 関数型インタフェースとラムダ式
関数型インタフェースを定義および記述する
ラムダ・パラメータ用のローカル変数とステートメント形式のラムダが含まれているラムダ式を作成および使用する
6. JavaストリームAPI
ストリーム・インタフェースとパイプラインについて説明する
ラムダ式とメソッド参照を使用する
7. 組込み関数型インタフェース
java.util.functionパッケージのインタフェースを使用する
Predicate、Consumer、Function、Supplierなどのコア関数型インタフェースを使用する
java.util.functionパッケージのベース・インタフェースのバイナリ・バリエーションとプリミティブを使用する
9. ストリームに対するラムダ演算
map、peekおよびflatMapメソッドを使用してストリーム・データを抽出する
findFirst、findAny、anyMatch、allMatchおよびnoneMatchメソッドによる検索を使用してストリーム・データを検索する
オプション・クラスを使用する
ストリームに対するcount、max、min、averageおよびsum演算を使用して計算を実行する
ラムダ式を使用してコレクションをソートする
ストリームに対してCollectorsを使用する(groupingByおよびpartitioningBy演算を含む)
10. モジュール型アプリケーションに移行する
移行用のモジュールにJava SE 8アプリケーションを分割して、SE 9~SE 11より前のバージョンのJavaを使用して開発したアプリケーションを移行する(トップダウン移行とボトムアップ移行を含む)
classpathとmodulepathに対してモジュール型アプリケーションを実行する
jdepsを使用して依存関係を調べ、循環的な依存関係に対処する方法を識別する
11. モジュール型アプリケーションにおけるサービス
ディレクティブなど、サービスのコンポーネントについて説明する
サービス・タイプを設計する。ServiceLoaderを使用してサービスをロードする。コンシューマ・モジュールとプロバイダ・モジュールが含まれているサービスの依存関係をチェックする
抽象クラスとインタフェースを比較する
12. 並列処理
RunnableとCallableを使用してワーカー・スレッドを作成する。ExecutorServiceを使用してタスクを並列実行する
CyclicBarrierやCopyOnWriteArrayListなど、java utilの並列コレクションおよびクラスを使用する
スレッド・セーフなコードを記述する
デッドロックやライブロックなど、スレッド化の問題を識別する
13. 並列ストリーム
並列ストリームを使用するコードを開発する
ストリームで分解と縮小を実装する
14. I/O(基本およびNIO2)
I/Oストリームを使用してコンソールおよびファイル・データに対するデータの読取りと書込みを行う
I/Oストリームを使用してファイルの読取りと書込みを行う
シリアライゼーションを使用してオブジェクトの読取りと書込みを行う
Pathインタフェースを使用してファイルおよびディレクトリ・パスを操作する
Filesクラスを使用してファイルまたはディレクトリのチェック、削除、コピー、移動を行うファイルに対してストリームAPIを使用する
目次
java.nio.charset
Charsetクラスは、Java I/Oやネットワーク通信など、バイトと文字データの変換が必要なあらゆる場面で文字コードの指定と処理を安全に行うための中心的な役割を担う。
Charset.forName()やStandardCharsetsなどの静的メソッドで取得する。
実際のエンコード/デコードは、String.getBytes(Charset)やnew String(byte[], Charset)などを通じて行われる。
Charsetクラス
Charset.forName(String charsetName)
指定した文字コード名に対応するCharsetオブジェクトを取得
指定された名前の文字コードがサポートされていない場合に
「UnsupportedCharsetException」または「IllegalCharsetNameException」
Charset utf8 = Charset.forName("UTF-8");
Charset sjis = Charset.forName("Shift_JIS");
Charset bat_cs = Charset.forName("UTF-999"); // 未対応:UnsupportedCharsetException
Charset bat_cs2 = Charset.forName("UTF 8"); // 名前不正(スペースなど):IllegalCharsetNameException
※bat_csのようなケースがある為今は非推奨の書き方。StandardCharsetsを代替として利用する
StandardCharsetsクラス:
Java 7以降では、標準的な文字コードがjava.nio.charset.StandardCharsetsという定数として提供。これによりタイプミスによるエラーを防ぐ。
Charset utf8_std = StandardCharsets.UTF_8;
Charset ascii_std = StandardCharsets.US_ASCII;
エンコード/デコードの操作例
String original = "Hello こんにちは";
// Encode(String → byte[])
byte[] bytes = original.getBytes(StandardCharsets.UTF_8);
// Decode(byte[] → String)
String decoded = new String(bytes, StandardCharsets.UTF_8);
System.out.println("元: " + original);
System.out.print("byte列: ");
for (byte b : bytes) {
System.out.print(b + " ");
}
System.out.println();
System.out.println("復元: " + decoded);
元: Hello こんにちは
byte列: 72 101 108 108 111 32 -29 -127 -109 -29 -126 -109 -29 -127 -85 -29 -127 -95 -29 -127 -81
復元: Hello こんにちは
=========================================================
java.util.Propertiesクラス
・キーと値のペアを扱うためのハッシュテーブルの一種
・設定ファイルなどのテキストベースのデータを読み書きするのに便利
Propertiesクラスは、主に以下の2つの特徴を持ちます。
文字列のキーと値:
Propertiesは、★キーも値も文字列が必須。
これは、テキストファイルにデータを保存する用途に適しています。
永続性のサポート:
Propertiesクラスには、データをファイルに保存したり(store()メソッド)、
ファイルから読み込んだり(load()メソッド)するためのメソッドを用意している。
これにより、設定情報を簡単に永続化できます。
★プロパティファイルの作成
ファイル:config.properties
アプリケーションの設定ファイル
database.url=jdbc:mysql://localhost/testdb
database.user=root
database.password=password123
★プロパティファイルの読み込み
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
public class PropertiesExample {
public static void main(String[] args) {
Properties properties = new Properties();
try (FileInputStream fis = new FileInputStream("config.properties")) {
// ★ファイルからプロパティを読み込む
properties.load(fis);
// ★キーを指定して値を取得
String dbUrl = properties.getProperty("database.url");
String dbUser = properties.getProperty("database.user");
String dbPassword = properties.getProperty("database.password");
System.out.println("DB URL: " + dbUrl);
System.out.println("DB User: " + dbUser);
System.out.println("DB Password: " + dbPassword);
} catch (IOException e) {
e.printStackTrace();
}
}
}
★プロパティファイルの書き込み
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Properties;
public class SavePropertiesExample {
public static void main(String[] args) {
Properties properties = new Properties();
★ properties.setProperty("app.version", "1.0");
★ properties.setProperty("app.name", "My App");
try (FileOutputStream fos = new FileOutputStream("app.properties")) {
// プロパティをファイルに保存
// 第2引数はコメント
★ properties.store(fos, "Application settings");
} catch (IOException e) {
e.printStackTrace();
}
}
}
メソッド
load():
入力ストリームからデータを読み込み、プロパティをハッシュテーブルに格納
try (FileInputStream fis = new FileInputStream("config.properties")) {
// ファイルからプロパティを読み込む
properties.load(fis);
getProperty():
指定したキーに対応する値を取得します。キーが存在しない場合はnullを返す。
// キーを指定して値を取得
String dbUrl = properties.getProperty("database.url");
String dbUser = properties.getProperty("database.user");
String dbPassword = properties.getProperty("database.password");
setProperty():
キーと値のペアを追加
properties.setProperty("app.version", "1.0");
store():
出力ストリームにプロパティを書き込みます。
try (FileOutputStream fos = new FileOutputStream("app.properties")) {
// プロパティをファイルに保存
// 第2引数はコメント
properties.store(fos, "Application settings");
15. Java SEアプリケーションにおけるセキュア・コーディング
JavaアプリケーションにおけるDoSの防止
Javaアプリケーションにおける機密情報の保護
データ整合性ガイドラインの実装(インジェクションおよびインクルードと入力検証)
アクセス可能性と拡張性の制限、適切な入力検証処理、および変更可能性によるコードの外部攻撃の防止
機密オブジェクトの構築の保護
シリアライゼーションとデシリアライゼーションの保護
16. JDBCによるデータベース・アプリケーション
JDBCのURLおよびDriverManagerを使用してデータベースに接続する
PreparedStatementを使用してCRUD操作を実行する
PreparedStatementおよびCallableStatement APIを使用してデータベース操作を実行する
17. ローカライズ
・Localeクラスを使用する
・リソース・バンドルを使用する
・Javaでメッセージ、日付および数値の形式を指定する
ローカライズとは
アプリケーションを異なる言語や地域(ロケール)に合わせて調整するための機能。ユーザーの言語設定に応じて、日付、通貨、メッセージなどを自動的に切り替える。ローカライズは主に以下の3つの要素を使って行う。
ロケール (Locale):
特定の言語と地域を表すオブジェクト。例えばLocale.JAPANは日本語と日本を、
new Locale("en", "US")は英語とアメリカを表す。Javaのローカライズ機能は、このLocaleオブジェクトを基に動作する。Localeオブジェクトは、他のローカライズ関連クラスのメソッドに引数として渡すことで特定の地域に合わせた処理を実行させます。例えば、ResourceBundle.getBundle()やDateFormat.getDateInstance()メソッドにLocaleオブジェクトを渡すことで、適切なリソースバンドルを読み込んだり、地域に合わせた日付の書式設定を行ったりできます。
リソースバンドル (ResourceBundle):
ローカライズされるテキストやメッセージを、言語や地域ごとに外部ファイル(プロパティファイル)としてまとめたもの。
Messages.properties: デフォルト(共通)のメッセージを格納する。これらのファイルはプログラムのコードから独立しているため、翻訳が必要になったときもコードを修正する必要はない。
フォーマットクラス:
日付、時刻、数値、通貨などをロケールに合わせて整形するためのクラス。
代表的なものにjava.textパッケージ内のDateFormatとNumberFormat,java.time.formatのDateTimeFormatterがある。
ロケール生成とローカライズメソッドの説明
以下ソースコードで指定がなければデフォルトロケールは日本で説明しています。
Locale
オブジェクトの生成
コンストラクタ生成
new Locale(String language)
new Locale(String language, String country)
new Locale(String language, String country, String variant)
引数(言語、国、バリアント(国内での方言を指定)) 関西弁、九州弁みたいなイメージ
静的ファクトリメソッド
Locale.forLanguageTag(String languageTag)
new Locale("ja", "JP", "Kansai")
Locale.forLanguageTag("ja-JP-x-kansai")
「"ja-JP","en-US"」などのBCP47形式で指定する。追加情報(従来のvariantに相当)は、プライベート拡張(例: "ja-JP-x-kansai")として指定可能。※従来のLocaleのvariantとは内部的な扱いが異なる
Local.Builderクラス
Locale builtLocale = new Locale.Builder()
.setLanguage("zh") //言語:中国語
.setRegion("TW") //地域:台湾
.setScript("Hant") // 繁体字
.build();
より複雑なロケールを生成する場合に、メソッドチェーンで複数の要素を簡潔に設定
メソッド
デフォルト情報の取得と変更
getDefault()
JVMのデフォルトロケールを返します。通常はOSに依存
setDefault(Locale newLocale)
JVMのデフォルトロケールを明示的に変更する
Locale.getAvailableLocales()
利用可能なロケールを一覧で取得
Java Runtime Environment (JRE) がサポートしているすべてのロケールの配列で返す。
備忘録:
JVM → Javaバイトコードを実行するエンジン
JRE → JVM + 標準ライブラリ(Locale等も含む)
JVM依存と思いきや、実質的にはJRE環境依存
表示名の取得
getLanguage()
ロケールの言語コードを返却。例: ja
getCountry()
ロケールの国(地域)コードを返却。例: JP
System.out.println(locale.getLanguage()); // ja
System.out.println(locale.getCountry()); // JP
getDisplayName()
ロケールを人間が読める形式の名前で返却。例: 日本語 (日本)
Locale frenchInGermany = new Locale("fr", "DE");
System.out.println(frenchInGermany.getDisplayName()); //依存するロケールで表示
System.out.println(frenchInGermany.getDisplayName(Locale.JAPAN)); // フランス語 (ドイツ)
System.out.println(frenchInGermany.getDisplayName(Locale.US)); //French (Germany)
ResourceBundle
ローカライズ機能では、プロパティファイルはResourceBundleクラスと連携して使用する。
そのために各ロケール(言語や地域)に対応するプロパティファイルを作成する。
各ロケールファイルを準備することでプログラムのコードを変更することなく表示メッセージを切替可能にする。
Propertiesは単一のファイルを扱うのに対し、ResourceBundleは複数のプロパティファイルをロケールに基づいて自動的に選択・管理してくれる高レベルな抽象化を提供する。
Propertiesの場合
Properties p = new Properties();
p.load(new FileInputStream("Messages_ja.properties"));
String msg = p.getProperty("greeting");
if (locale.equals(Locale.JAPAN)) {
load("Messages_ja.properties");
} else {
load("Messages_en.properties");
}
自らファイルを指定したり、プロパティファイルのハンドリングをする必要がある
ResourceBundleの場合
ResourceBundle rb = ResourceBundle.getBundle("Messages");
String msg = rb.getString("greeting");
ファイル名を指定でユーザーのOS設定やJVMのデフォルトロケール(Locale.getDefault())ファイルが選択される。
Locale.setDefault(Locale.US);
ResourceBundle rb = ResourceBundle.getBundle("Messages", Locale.JAPAN);
String msg = rb.getString("greeting");
明示的にロケールを指定することも可能。デフォルトロケールはUSを設定しているが、明示的にJAPANを指定すればJAPANが優先される動きになる。
※「Messages_ja_JP → Messages_ja → Messages」の順で探索される。該当するファイルが見つからない場合は、デフォルトロケールに基づいた(例:Messages_en_US → Messages_en → Messages)別ルートの探索が行われる。
ロケールの設定と情報の取得方法
//messages_ja_JP.propertiesに「greeting=こんにちは」が設定されている
//デフォルトロケールは「ja_JP」である
import java.util.ResourceBundle;
import java.util.Locale;
public class Main {
public static void main(String[] args) {
// ユーザーのOS設定から自動的にロケールを取得
Locale currentLocale = Locale.getDefault();
// 取得したロケールに基づいて、ResourceBundleを自動選択
ResourceBundle messages = ResourceBundle.getBundle("messages", currentLocale);
// キーを指定して、適切なメッセージを取得
String greeting = messages.getString("greeting");
System.out.println("現在のロケール: " + currentLocale.getDisplayName());
System.out.println("メッセージ: " + greeting);
}
}
「現在のロケール: 日本語(日本)」 「メッセージ: こんにちは」
java.text.NumberFormatクラス
ロケールに基づいて数値・通貨・パーセントなどの表示形式を自動調整し、文字列として整形するクラス
getInstance(Locale locale)
通常の数値フォーマットの場合
getIntegerInstance(Locale locale)
整数型数値の場合
getCurrencyInstance(Locale locale)
通貨フォーマットの場合
getPercentInstance(Locale locale)
パーセント標記の場合
double number = 12345.678;
double ratio = 0.256;
// ロケールを変数に代入
Locale localeJP = Locale.JAPAN;
Locale localeUS = Locale.US;
// ===== 日本ロケール =====
NumberFormat nfJP = NumberFormat.getInstance(localeJP);
NumberFormat intJP = NumberFormat.getIntegerInstance(localeJP);
NumberFormat curJP = NumberFormat.getCurrencyInstance(localeJP);
NumberFormat percentJP = NumberFormat.getPercentInstance(localeJP);
System.out.println("=== JAPAN ===");
System.out.println("通常: " + nfJP.format(number));
System.out.println("整数: " + intJP.format(number));
System.out.println("通貨: " + curJP.format(number));
System.out.println("パーセント: " + percentJP.format(ratio));
// ===== USロケール =====
NumberFormat nfUS = NumberFormat.getInstance(localeUS);
NumberFormat intUS = NumberFormat.getIntegerInstance(localeUS);
NumberFormat curUS = NumberFormat.getCurrencyInstance(localeUS);
NumberFormat percentUS = NumberFormat.getPercentInstance(localeUS);
System.out.println("=== US ===");
System.out.println("通常: " + nfUS.format(number));
System.out.println("整数: " + intUS.format(number));
System.out.println("通貨: " + curUS.format(number));
System.out.println("パーセント: " + percentUS.format(ratio));
通常: 12,345.678
整数: 12,346
通貨: ¥12,346
パーセント: 26%
=== US ===
通常: 12,345.678
整数: 12,346
通貨: $12,346.00
パーセント: 26%
プロパティファイルについて
この記述ルールはPropertiesファイルの仕様( I / O の章に該当)でありResourceBundleでも同様に利用される為こちらでも記載する。
・各ロケール(言語や地域)に対応するプロパティファイルを作成する
ファイル名の命名規則
基本名_言語コード_国コード.properties
※国コードの省略は可能だが、言語コードの省略は不可
Messages_ja_JP.properties //日本語(日本)用のファイル
Messages_en.properties //英語用のファイル
Messages.properties //デフォルト(共通)のファイル
記述に関するルール
・キーと値: 各行に「キー = 値」または「キー : 値」の形式で記述する。
greeting=Hello
farewell:Goodbye
name=Java Gold
コメント: #または!で記載する
# これはコメント
! これもコメント
特殊文字のエスケープ : 「=」,「:」,「#」,「!」などの特殊文字をキーや値に含めたい場合は、バックスラッシュ( \ )でエスケープする。バックスラッシュはバックスラッシュ自体でエスケープする。
# = や : を含める
message=key\=value と key\:value の例
# # や ! を含める
note=これは\#ハッシュと\!エクスクラメーション
# バックスラッシュ自体
path=C:\\Users\\test
改行 : バックスラッシュ\を行末に記述することで、1つの設定値を複数行にわたって記述できます。
longMessage=これは長い文章です。\
改行しても1つの値として扱われます。\
さらに続きます。
出力 : これは長い文章です。改行しても1つの値として扱われます。さらに続きます。
アノテーション
アノテーションの説明
クラス、メソッド、フィールドなどにメタデータ(付加的な情報)を付与するための仕組みでコンパイラや開発ツール、または実行時にJVM(Java仮想マシン)がその情報を利用する。また、アノテーション自体はプログラムに影響を与ることはなく(※1)アノテーションを付与した情報を利用してJVM等が処理を行う際に真価を発揮する。
※1 余談:Springでよく利用するバリデーションアノテーション(@NotNull)を例に、設定するだけでバリデーションが働きプログラムの挙動に変化が生じるため影響しているように感じるが、これはアノテーションによる影響ではなく、それを読み取る仕組み(フレームワーク)により挙動が変化している。
基本ルール
・アノテーションは、インタフェースに似た構文で定義する。
・インタフェースの前に@を付け、@interfaceキーワードを利用
・引数のないメソッドとして宣言
・要素にはデフォルト値も設定可能
・利用クラスでは注釈パラメータを指定する(デフォルト値は省略可)
・要素を持たないアノテーションはマーカーアノテーションと呼ぶ。(例1)
マーカーインタフェース(※1)と同じ役割 (※1については後述)
//例1:単純なアノテーションの例
public @interface MyAnnotation {}
public @interface Author_ex1 {
String name();
int year() default 2025;
}
//例2:要素が一つの例
public @interface Author_ex1 {
String name();
}
//例3:複数値を持つアノテーションの例
public @interface Author_ex2 {
String name();
int year() default 2025;
}
//利用クラス
@Author_ex1("Taro") //要素名の省略可能
class Sample_ex1{}
@Author_ex2(name = "Taro" year = 2026 )
@Author_ex2(name = "Taro" ) //デフォルト値は省略可
class Sample_ex2{}
・付与されたアノテーションの情報を取得したい場合は、リフレクションAPIを使用。
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME) //実行時に値を取得する際に必要
@interface Author_ex1 {
String name();
int year() default 2026;
}
//利用クラス
@Author_ex3("Taro")
class Sample_ex1 {}
//実行クラス
public class Main {
public static void main(String[] args) {
// クラス情報を取得
Class<Sample_ex1> clazz = Sample_ex1.class;
// アノテーション取得
Author_ex1 author = clazz.getAnnotation(Author_ex1.class);
// 値を取得
System.out.println("名前: " + author.value() + " 年 : " + author.year() );
}
}
実行結果:名前: Taro 年 : 2026
要素指定のルール
「アノテーションの要素はコンパイル時に確定できるリテラルまたは定数式でなければならない」としており、以下に取り扱いルールを記載する。
要素として指定可能な型:プリミティブ型、String、Class、Enum、他アノテーション型、
要素として指定可能な型の配列
指定不可な型:Object、List、Map、任意のクラスインスタンス
※ Newが必要なもの
//定数指定 ✅ OK
static final String S = "Hello";
@Sample(text = S)
// リテラル指定 ✅ OK
@Sample(num = 10)
@Sample(text = "Hello")
@Sample(clazz = String.class)
//他アノテーション要素指定 ✅ OK
@interface Info { String value(); }
@interface Author { Info info(); } // ← 他アノテーション型を要素として持つ
@Author(info = @Info("Taro"))
class Sample {}
//変数指定 ❌ NG 変数は実行時に代入される
String s = "Hello";
@Sample(text = s)
//new指定 インスタンス生成(=実行時でコンパイル時に値が確定しない) ❌
@Sample(obj = new Object())
@Sample(list = new ArrayList<>())
メタ・アノテーション
アノテーションを定義するためのアノテーション
@Target(ElementType.~~~)
定義するアノテーションをどこに適用できるかを指定
クラスにだけ適用できるようにしたり、メソッドにだけ適用できるようにしたり
.TYPE :クラス / インタフェース / enum
.METHOD:メソッド
.FIELD:フィールド
.PARAMETER
.CONSTRUCTOR
.LOCAL_VARIABLE
.ANNOTATION_TYPE
サンプルコード
@Target(ElementType.TYPE)
@interface MyAnnotation {}
@MyAnnotation
class Sample {}
@MyAnnotation
interface Test {}
@MyAnnotation
enum Color { RED, BLUE }
@Target(ElementType.METHOD)
@interface MyAnnotation {}
class Sample {
@MyAnnotation
void test() {}
}
@Target(ElementType.FIELD)
@interface MyAnnotation {}
class Sample {
@MyAnnotation
int age;
}
//Springの @NotNull などはここでよく使われる
@Target(ElementType.PARAMETER)
@interface MyAnnotation {}
class Sample {
void test(@MyAnnotation String name) {}
}
@Target(ElementType.CONSTRUCTOR)
@interface MyAnnotation {}
class Sample {
@MyAnnotation
Sample() {}
}
//実務ではほぼ使わないが、仕様として存在 ローカル変数に利用
@Target(ElementType.LOCAL_VARIABLE)
@interface MyAnnotation {}
class Sample {
void test() {
@MyAnnotation
int x = 10;
}
}
//「アノテーション定義」に付ける(メタアノテーション)
@Target(ElementType.ANNOTATION_TYPE)
@interface MyMetaAnnotation {}
@MyMetaAnnotation
@interface MyAnnotation {}
@Retention(RetentionPolicy.~~~)
定義するアノテーションがいつまで保持されるかを指定
コンパイル時に破棄されるか、実行時にも情報が保持されるかを決定する。
.CLASS(デフォルト) : クラスファイルに残るが、実行時には利用不可
.SOURCE : コンパイル時に破棄
.RUNTIME : 実行時にも情報を保持、リフレクションでアクセス可能
※リフレクションとは...
Javaのプログラムが自身の構造を実行時に調べたり変更したりできる機能
通常、コードはコンパイル時にクラス、メソッド、フィールドが確定しますが、
リフレクションを使うと、これらの情報を実行時に動的に取得し操作可能にする。
@Documented
Javadocに含めるべきかを指定できる
@Inherited
定義するアノテーションがサブクラスに継承されるかを指定
このアノテーションが付与されたクラスにアノテーションを適用すると、
そのクラスを継承したサブクラスにも自動的に同じアノテーションが適用されます。
※クラスに付与したアノテーションのみサブクラスに継承される(メソッド・フィールドは対象外)
@Repeatable(Container.class)
定義するアノテーションが同じ要素に複数回適用可能かを指定。繰り返し可能なアノテーションを定義する場合に利用する。利用する場合にはコンテナアノテーションが必要になる。
コンテナアノテーションサンプルコード
import java.lang.annotation.*;
@Repeatable(Authors.class)
@interface Author {
String value();
}
//コンテナアノテーション
@interface Authors {
Author[] value();
}
@Author("Taro")
@Author("Jiro")
class Sample {}
@Deprecatedアノテーション
特定のプログラム要素(クラス、メソッド、フィールドなど)が非推奨(廃止予定)であることを示すためのマーカーアノテーション。ライブラリ開発者向けの機能。
段階的な廃止プロセス:
@Deprecatedを使用することで、ライブラリやフレームワークの開発者は、
既存のユーザーのコードの修正に猶予を与え、安全に古いAPIから切り替えを可能にする。ユーザーは警告を通じて新しいAPIへの移行を計画する時間を得られる。
サンプルコード
class OldApi {
/**
* @deprecated このメソッドは非推奨です。
* 代わりに newMethod() を使用してください。
*/
@Deprecated
public void oldMethod() {
System.out.println("old method");
}
public void newMethod() {
System.out.println("new method");
}
}
public class Main {
public static void main(String[] args) {
OldApi api = new OldApi();
api.oldMethod(); // ⚠ コンパイル時に警告が出る
api.newMethod();
}
}
コードの品質向上:
開発者が非推奨のAPIの使用を避けるよう促すことで、コードが最新で効率的なAPIに準拠するよう誘導し全体的な品質が向上を図る。
サンプルコード
class OldApi {
/**
* @deprecated 将来的に削除予定
*/
@Deprecated(since = "9", forRemoval = true)
public void oldMethod() {
System.out.println("old method");
}
}
コミュニケーションの明確化:
@Deprecatedは、その要素がなぜ非推奨なのか、そしてどの代替手段を使うべきかといった情報をJavadocで提供することが推奨される。これにより、他の開発者への意図が明確に伝えることが可能。
サンプルコード
//============================ (提供側) ================================
class Library {
/**
* @deprecated Use calculateNew() instead.
*/
@Deprecated
public int calculateOld(int a, int b) {
return a + b;
}
public int calculateNew(int a, int b) {
return Math.addExact(a, b); // より安全な実装
}
}
//============================ (ライブラリ利用側) ================================
public class User {
public static void main(String[] args) {
Library lib = new Library();
int result = lib.calculateOld(1, 2); // ⚠ 警告
System.out.println(result);
}
}
マーカーインタフェースについて
・名前の通り何も持たない(マークする)インタフェース
・特定のカテゴリに属している
・特別な振る舞いをすることを明示する役割
標準APIには、いくつか代表的なマーカーインタフェースがある。
java.lang.Cloneable
Objectクラスのclone()メソッドを使用してオブジェクトのコピーを作成可能にする。
Cloneableはclone()の実行を許可するマーカーであり、Object.clone()内でinstanceof演算子によるチェックが行われ、未実装の場合は例外が発生する。また、clone()はprotectedのため、オーバーライドして公開する必要がある。
サンプルコード
class User implements Cloneable {
int age = 20;
@Override
public User clone() {
try {
return (User) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
public class Main {
public static void main(String[] args) {
User u1 = new User();
User u2 = u1.clone();
System.out.println(u1.age); // 20
System.out.println(u2.age); // 20
}
}
java.io.Serializable
このインタフェースを実装したクラスは、Javaの直列化機構を使ってオブジェクトの状態をバイトストリームに変換し、ファイルに保存したりネットワーク経由で送信が可能。
→APIが instanceof で判定しており、目印として認識する為そのような動きが可能になる。
transientキーワード
直列化したくないフィールドがある場合は、そのフィールドにtransientキーワードを付与するとtransientが付いたフィールドは直列化の対象外となり、非直列化される際にそのフィールドはデフォルト値(参照型ならnull、プリミティブ型なら0など)に戻ります。
public class User implements Serializable {
private String name;
private int age;
private transient String password; // このフィールドは直列化されない
// ... コンストラクタや他のメソッド
}
アノテーションとマーカーインタフェース比較
アノテーションは、マーカーインタフェースに比べ、柔軟かつより多くの情報を付加できる。引数を設定できたり、その目印に関する具体的な情報の付与が可能。このため、Java 5以降では、特別な振る舞いを付与するためにアノテーションを推奨することが多くなった。なので全てアノテーションを利用すればよいというわけでもなく、マーカーインタフェースには「型」として扱えるという明確な利点がある。型として扱えると、コンパイル時にinstanceof演算子やジェネリクスなどで型チェックを行うことができ、より厳密な型安全性を確保できる場合があるため、どちらを使うかは、要件に応じて適切に判断することが重要。