0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

2025/11月にJava Golg Programmer SE11に合格したので書き溜めたメモを共有する。

0
Last updated at Posted at 2026-04-25

はじめに

表題の通り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の基礎       2026/05/02 済
2.例外処理とアサーション 2025/05/07 一部記載
3.Javaのインタフェース
4.汎用とコレクション
5.関数型インタフェースとラムダ式
6.JavaストリームAPI
7.組込み関数型インタフェース
8.ストリームに対するラムダ演算
9.モジュール型アプリケーションに移行する
10.モジュール型アプリケーションにおけるサービス
11.並列処理
12.並列ストリーム
13.I/O(基本およびNIO2)
14.Java SEアプリケーションにおけるセキュア・コーディング
15.JDBCによるデータベース・アプリケーション      2025/04/29_済
16.ローカライズ             2026/04/27_済
17.アノテーション           2026/04/25_済

1. Javaの基礎

・finalクラスを作成および使用する
・内部クラス、ネストしたクラス、および無名クラスを作成および使用する
・列挙を作成および使用する

1-1 finalクラスを作成および使用する

Final classの特徴
・extends してサブクラスを作れない
・クラスの設計を固定したいときに使う
・安全性・不変性を守りたいときに使われることが多い etc...

クラス宣言の前にfinal修飾子をつけることで継承不可のクラスを作成することができる

public final class Sample {
    public void hello() {System.out.println("Hello");} 
}

//Sampleを継承しようとするとコンパイルエラーが発生
class SubSample extends Sample{ }

1-2 内部クラス、ネストしたクラス、および無名クラスを作成および使用する

※以降は内部クラスをインナークラス、外側クラスをアウタークラスと呼びます。

※Java の正式用語では「ネストクラス=内部クラス+静的ネストクラス」の総称ですが、
 本記事では説明の便宜上、非static をインナークラス、static をネストクラスと呼びます。

内部クラス及びネストしたクラス

 インナークラスとネストしたクラスは両者ともにクラス内に定義されたクラスで、staticか非staticかでインスタンス生成の方法やアクセスの部分で異なってきます。

インナークラスの特徴
・アウタークラスのインスタンスが必須
 Outer.Inner inner = new Outer().new Inner();
・スタティックメンバのアクセス可能
・インナークラスは Outer のインスタンスに紐づくため、クラスに属する static メンバを持つことができない。

ネストクラスの特徴
・クラスの中に定義されたクラス(ネストクラス)にだけ static を付けられる
・アウタークラスのインスタンスが不要
  Outer.Nested nested = new Outer.Nested();
・非staticメンバへのアクセス不可 ( Outer.this を持たない )

ソースコード

class Outer {
    private int value = 10;
    static private int NestedValue = 10;

    //内部クラス(インナークラス)
    class Inner {
        void print() {
            // Outer.this.value にアクセス可能
            System.out.println(value);
            //インナークラスからスタティックメンバへのアクセス可能
            System.out.println(NestedValue);
        }

        //非staticなインナークラスではstaticメソッドを定義できない。コンパイルエラー
        //static void call(){  }

    }

    //ネストクラス
    static class Nested {

        static void call(){
            //メソッド内でインスタンスを生成することで非staticメソッドへのアクセスも可能
           new Outer.Nested().print();
        }

        static void nestedCall(){
            //staticメソッドからstaticメソッドへのアクセスの場合はインスタンス不要
            Outer.Nested.call();
        }

        void print() {
            //外側クラスの非 static メンバーにはアクセスできない。コンパイルエラー
//            System.out.println(value);
            System.out.println(NestedValue);
        }

    }
}



public class Main {
    public static void main(String[] args){
        Outer.Inner inner = new Outer().new Inner();
        inner.print();

        Outer.Nested nested = new Outer.Nested();
        nested.print();

        //ネストクラスのStaticメソッドにはインスタンス生成不要でアクセス可能
        Outer.Nested.nestedCall();
    }
}

staticフィールド及びメソッド / ネストクラスの概念についておさらい

static フィールド/static メソッド
・Outer クラスが持つ “クラスに属するメンバ”
 ※全インスタンスで共有される

class Outer {
    static int count;      // static フィールド
    static void hello() {} // static メソッド
}

ネストクラス
・Outer クラスの “メンバとして定義されたクラス”
・Outer のインスタンスに属さないだけで、普通のクラス
 ※static フィールドのように “static領域に置かれる” わけではない

class Outer {
    static class Nested { }
}

無名クラス( 匿名クラス )

匿名クラスで慣れているので、以下匿名クラスと呼ぶ

「匿名クラス(無名クラス)は、クラスの宣言とインスタンス化を同時に行うことを可能にする。ローカルクラスに似ているが、名前を持たない点が異なる。」

匿名クラス一度しか使わないクラスを作るときに便利。

① コールバック処理(イベントリスナー)
② スレッド処理(Runnable の一時実装)
③ Comparator の一時的な実装
④ テストコードでの一時的なモック実装

サンプルソース

Sample.java
//コンストラクタ、getter,setterを準備
public class Sample {
    private int num;
    public Sample(int num){this.num = num;}
    public int getNum() {return num;}
    public void setNum(int num) {this.num = num;}
}
public class Main {
    public static void main(String[] args){

//匿名クラス
  var sample = new Sample(10){
  //匿名クラスはコンストラクタを宣言できない為、
  //初期化時に処理を実装したい場合は,初期化子を利用する
  {
      System.out.println("インスタンス生成時に実行されます");
  }
  
//フィールド変数を持たせることもできる
    private String anonymousField = "これは匿名クラスのフィールドです";

//匿名クラス専用のメソッド
    void modify(int num){ setNum(num);}
  };

//sampleインスタンスのみ利用可能
  sample.modify(999);
  System.out.println(sample.getNum());

//匿名クラスを持たないインスタンス
  var sample2 = new Sample(10);
//  sample2.modify(20); //コンパイルエラー
    }
}

1.Sampleg型 sample(num=10) でインスタンス化。同時に匿名クラスmodifyを定義し,引数で受け取った値を「this.num」にセットする。 [num = 10]
2.sampleでmodify(999)を呼び出し。soutでnumを出力 [num = 999]
3.Sample型 sample2(10)でインスタンス化、匿名クラスを持たないsample2でmodify(20)を呼び出すが、定義したインスタンスのみ利用可能。コンパイルエラー


var宣言とクラス型宣言での挙動の変化

ソースコードを再確認していた際に発見。上記で生成したインスタンスsampleをvarではなくSample型で宣言し、メソッドを呼び出した場合どのような挙動になるか。

Sample sample = new Sample(10){
  private String anonymousField = "これは匿名クラスのフィールドです";
  void modify(int num){ setNum(num);}
};

以下のようにSample型にはmodify()というメソッドが存在していないと認識された
image.png

たしかにSample型にmodifyメソッドは存在していないので警告は理解できる。では実際に匿名クラスはどのように読み込まれているかを確認する為にMain.classファイルを確認することにした。

var型宣言の場合

<undefinedtype> sample = new Sample(10) {
private String anonymousField = "これは匿名クラスのフィールドです";
void modify(int num) {this.setNum(num);}
};

<undefinedtype>は利用するエディタで表示が変わるらしい。クラスファイル上は型情報を保持しているが、どのような型であるかは未定義状態であることが確認できる。

Sample型宣言の場合

Sample sample = new Sample(10) {
  private String anonymousField = "これは匿名クラスのフィールドです";
  void modify(int num) {
    this.setNum(num);
  }
};

純粋なSample型として定義がされる。そのためmodifyメソッドが存在しない為コンパイルーになることが理解できた。さらに内部でどのように扱われているかはMain$1.classファイルをjavapコマンドで確認したところ全貌が見えた。

var宣言の場合

class Main$1 extends Sample {
  private java.lang.String anonymousField;
  Main$1(int);
  void modify(int);
}

Sample宣言の場合

class Main$1 extends Sample {
  private java.lang.String anonymousField;
  Main$1(int);
  void modify(int);
}

MainS1.classの内容に差異はなかった。インスタンス変数 sample で定義した匿名クラス(変数及びメソッド)はMainで定義されたものと内部的に認識されていることが確認できた。


ユースケース

備忘録から匿名クラスとして新たな機能を追加したい場合はvar宣言を利用し、オーバーライドで上書きしたいメソッドがある場合はクラス型定義で使い分けられることが理解できた。

Sample.javaに一つメソッドを追加で定義する

Sample.java
public void call(){
  System.out.println("これはSampleクラスメソッドを呼び出しています");
  }
Main.java
Sample samples = new Sample(10);
samples.call(); //これはSampleクラスメソッドを呼び出しています

Sample sample = new Sample(10){
  @Override
  public void call(){
    System.out.println("これは匿名クラスのメソッドの呼び出し");
  }
};
sample.call(); //これは匿名クラスのメソッドの呼び出し

var sample2 = new Sample(20){
  public void newCall(){
    System.out.println("これは内部的にMainクラスメソッドの呼び出し");
    }
};
sample2.newCall(); //これは内部的にMainクラスメソッドの呼び出し

1-3 列挙を作成および使用する

列挙型(enum)は「決まった値の集合を型として安全に扱うための特別なクラス」

列挙型を使うメリット
・型安全性,可読性:月曜日を「today = 1」とするより「today = Day.MONDAY」した方が可読性及び入力ミスを事前に防ぐことができる。
・比較が高速で安全:== で比較できる(参照が1つに固定されているため)
・switch文と相性が良い
・メソッドやフィールドを持てる(普通のクラスのように拡張可能)

基本的な列挙型

public enum Day {
    MONDAY, TUESDAY, WEDNESDAY,
    THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

Day today = Day.MONDAY; Day型で値を取得する。

メソッド、フィールドを持つ列挙型

public enum Season {
    SPRING("春は花が咲きます"),
    SUMMER("夏は暑いです"),
    AUTUMN("秋は紅葉がきれい"),
    WINTER("冬は寒いです");

    private final String message;

    Season(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }
}

System.out.println(Season.SPRING.getMessage());
Seasonコンストラクタにて各フィールドが初期化され、Season.フィールド.getMessage()で値を取得したりすることができる。

組込メソッド
values():すべての列挙子を配列で取得
valueOf("文字列"):文字列から列挙子を取得
name():列挙子名を文字列で取得
ordinal():宣言順の番号を取得

public class SeasonSample {
    public static void main(String[] args) {

        // 1. values():すべての列挙子を配列で取得
        System.out.println("=== values() ===");
        for (Season s : Season.values()) {
            System.out.println(s + " → " + s.getMessage());
        }

        // 2. valueOf():文字列から列挙子を取得
        System.out.println("\n=== valueOf() ===");
        Season summer = Season.valueOf("SUMMER");
        System.out.println("valueOf(\"SUMMER\") → " + summer + " / " + summer.getMessage());

        // 3. name():列挙子名を文字列で取得
        System.out.println("\n=== name() ===");
        System.out.println("summer.name() → " + summer.name());

        // 4. ordinal():宣言順の番号を取得(0 始まり)
        System.out.println("\n=== ordinal() ===");
        System.out.println("SPRING.ordinal() → " + Season.SPRING.ordinal());
        System.out.println("SUMMER.ordinal() → " + Season.SUMMER.ordinal());
        System.out.println("AUTUMN.ordinal() → " + Season.AUTUMN.ordinal());
        System.out.println("WINTER.ordinal() → " + Season.WINTER.ordinal());
    }
}
=== values() ===
SPRING → 春は花が咲きます
SUMMER → 夏は暑いです
AUTUMN → 秋は紅葉がきれい
WINTER → 冬は寒いです

=== valueOf() ===
valueOf("SUMMER") → SUMMER / 夏は暑いです

=== name() ===
summer.name() → SUMMER

=== ordinal() ===
SPRING.ordinal() → 0
SUMMER.ordinal() → 1
AUTUMN.ordinal() → 2
WINTER.ordinal() → 3

EnumMapと抽象メソッド(おまけ)

試験にはここまで出てこないと思うので、以降はおまけでまとめています。

EnumMap
・キーが enum のときに最速・最軽量
・内部的に ordinal() を使った配列ベースで動く
・HashMap より高速でメモリ効率が良い
・季節・状態・モードなど「固定の選択肢」があるときに最適

サンプルソース
 EnumMap<Season, String> seasonMessages = new EnumMap<>(Season.class);

        seasonMessages.put(Season.SPRING, "花見の季節です");
        seasonMessages.put(Season.SUMMER, "海に行きたい季節です");
        seasonMessages.put(Season.AUTUMN, "紅葉がきれいな季節です");
        seasonMessages.put(Season.WINTER, "雪が降る季節です");

        // Season.values() と組み合わせると強力
        for (Season s : Season.values()) {
            System.out.println(s + " → " + seasonMessages.get(s));
        }

抽象メソッドを使った振る舞いの変化
・状態ごとに振る舞いが変わる処理
・Strategy パターンの簡易版
・State パターンの軽量版

サンプルソース
public enum Season {
    SPRING("春は花が咲きます") {
        @Override
        public void doAction() {
            System.out.println("花見に行きます");
        }
    },
    SUMMER("夏は暑いです") {
        @Override
        public void doAction() {
            System.out.println("海で泳ぎます");
        }
    },
    AUTUMN("秋は紅葉がきれい") {
        @Override
        public void doAction() {
            System.out.println("紅葉狩りに行きます");
        }
    },
    WINTER("冬は寒いです") {
        @Override
        public void doAction() {
            System.out.println("こたつでみかんを食べます");
        }
    };

    private final String message;

    Season(String message) {
        this.message = message;
    }

    public String getMessage() {
        return message;
    }

    // 季節ごとに振る舞いを変える抽象メソッド
    public abstract void doAction();
}

呼び出し側

public class SeasonActionSample {
    public static void main(String[] args) {
        for (Season s : Season.values()) {
            System.out.println(s + " → " + s.getMessage());
            s.doAction();  // 季節ごとに異なる処理が実行される
        }
    }
}

2. 例外処理とアサーション

・try-with-resources構造を使用する
・カスタム例外クラスを作成および使用する
・アサーションを使用して不変量をテストする

2-1 try-with-resources構造を使用する

 try-with-resourcesの構文は「リソースの確実な解放」が目的であり例外処理を記述する為に用いるものではない。 I/O・DB接続・ファイル操作など、close() が必要なものを安全に扱うための構文。try-with-resourcesを利用する場合はtry()内に開放したいリソースのインスタンスを宣言。(※具体的な部分は以下で解説)

特徴
 ・CloseableまたはAutoCloseableの実装クラスなら何でも使える
 ・catch、finallyどちらも省略可能
 ・リソースは final または実質的 finalであること
 ・抑制例外のハンドリングが必要

特徴について3つのクラスを用意のもと以下解説。


//AutoCloseableもCloseableも実装していないクラス
class NotImplResource {}

//AutoCloseableを実装したクラス
class AutoCloseImplResource implements AutoCloseable {
    @Override
    public void close() throws Exception {}
}

//Closeableを実装したクラス
class CloseImplResource implements Closeable {

    @Override
    public void close() throws IOException {}
}

「CloseableまたはAutoCloseableの実装クラスなら何でも使える」
NotImplResourceが自動閉機能を実装していない為、try()内でインスタンスを生成するとコンパイルエラーが発生。

try(
    NotImplResource notImplResource = new NotImplResource();
    AutoCloseImplResource autoCloseImplResource = new AutoCloseImplResource();
    CloseImplResource closeImplResource = new CloseImplResource();
        ){
            //処理を実行
        }

image.png

「catch、finallyどちらも省略可能」
 コンパイルエラー等は発生せず処理が可能。またtry-catch,try-finally,try-catch-finallyなど柔軟な記載が可能。

try(
  AutoCloseImplResource autoCloseImplResource = new AutoCloseImplResource();
  CloseImplResource closeImplResource = new CloseImplResource();
){
//処理を実行
}

「ソースは final または実質的 finalであること」
 基本的にはtry()内でインスタンスを宣言することができるが、変数を指定する場合は必ず実質的finalであること

public class Main{
    public static void main(String[] args) throws Exception {

        //明示的または実質的final変数
        final CloseImplResource finalCloseImplResource = new CloseImplResource();
        AutoCloseImplResource autoCloseImplResource = new AutoCloseImplResource();
        
        // try()外でインスタンス生成し、変数をtry()内を指定することは可能
        try(autoCloseImplResource; finalCloseImplResource) {}  
    }
}

実質的finalが崩れるパターン
 処理内外問わず、変数に再代入等を行う処理でコンパイルエラーが発生。

public class Main{
    public static void main(String[] args) throws Exception {

        AutoCloseImplResource autoCloseImplResource = new AutoCloseImplResource();

        try(autoCloseImplResource;) {
           // autoCloseImplResource = null;
        }
        
        autoCloseImplResource = null;
    }
}

image.png

「抑制例外のハンドリングが必要」
 ここが少し難しい所。AutoCloseImplResourceクラスのclose()内に例外処理を追記して強制的に例外を出力するようにして、

public class Main{
    public static void main(String[] args) throws Exception {

        AutoCloseImplResource autoCloseImplResource = new AutoCloseImplResource();

        try(autoCloseImplResource;) {
            throw new Exception("main exception");
        }catch (Exception e) {
            //Main側で発生した例外
            //(Mainで例外が発生していない場合は eは"close exception"を出力する。)
            System.out.println("Main: " + e);

            //Close側で発生した例外の取り出す時はgetSuppressed()で取り出す。
            //取り出してあげないと、内部で起きた例外をキャッチ出来ずMainだけ例外が発生している
            //ように誤認してしまう。
            for (Throwable suppressed : e.getSuppressed()) {
                System.out.println("Suppressed: " + suppressed);
            }
        }
    }
}


class AutoCloseImplResource implements AutoCloseable {
    @Override
    public void close() throws Exception {
        throw new Exception("close exception");
    }
}
Main: java.lang.Exception: main exception
Suppressed: java.lang.Exception: close exception

2-2 カスタム例外クラスを作成および使用する

独自の例外クラスを作成するにはExeptionのサブクラスである必要がある。
RuntimeExceptionの継承は任意

● RuntimeException を継承すべきケース
 呼び出し側が try-catch で必ず対処すべきではない
 事前条件違反(引数が不正など)
 ロジックの不整合(状態遷移が壊れているなど)

● Exception(チェック例外)を継承すべきケース
 呼び出し側が 対処しないと処理が成立しない
 I/O、外部 API、DB などの 外部要因

2-3 アサーションを使用して不変量をテストする

assertの利用用途とソースコード

3. Javaのインタフェース

・デフォルト・メソッドによってインタフェースを作成および使用する
・プライベート・メソッドによってインタフェースを作成および使用する

4. 汎用とコレクション

・ラッパー・クラス、オート・ボクシングおよびオート・アンボクシングを使用するダイアモンド表記とワイルドカードを使用して汎用クラスおよびメソッドを作成および使用するコレクション・フレームワークについて説明する

・主要なコレクション・インタフェースを使用する ComparatorインタフェースとComparableインタフェースを使用する コレクション用の便利なメソッドを作成および使用する

5. 関数型インタフェースとラムダ式

・関数型インタフェースを定義および記述する
・ラムダ・パラメータ用のローカル変数とステートメント形式のラムダが含まれているラムダ式を作成および使用する

6. JavaストリームAPI

・ストリーム・インタフェースとパイプラインについて説明する
・ラムダ式とメソッド参照を使用する

7. 組込み関数型インタフェース

・java.util.functionパッケージのインタフェースを使用する
・Predicate、Consumer、Function、Supplierなどのコア関数型インタフェースを使用する
・java.util.functionパッケージのベース・インタフェースのバイナリ・バリエーションとプリミティブを使用する

関数型インタフェースとは

抽象メソッドを1つだけ持つインタフェース(@FunctionalInterface が付いている)
覚えることが多くて少し混乱する所ですが紹介する覚え方で整理するとよいかもしれません。(あくまで私の覚え方です)

Suplier(供給者)

★覚え方★
サプライヤーは相手に与える[ get( ) ]します。
頂きもの(引数)は必要でなく見返り(戻り値)も求めない。

Consumer(消費者)

★覚え方★
お客さんはお金を払い(引数あり)
物を引き換えに取得(accept())します(戻り値あり)
あとは、コンシューマのメソッドは「c」が多いって覚える。

``

Predicate
判定者:鑑定する物(引数)を品定めして
本物か偽物かテスト(testメソッド)を行います。

function:apply
一つの引数を受け取り、異なる戻り値を指定できる。
異なる戻り値を指定できるから引数は2つ必要になる。
Function

BiFunction
2つの引数(異なりOK)を受け取り、異なる戻り値を指定できる
3つの引数の指定が必要
BiFunction

Functionのサブインタフェース
UnaryOperator
一つの引数を受け取り、同じ戻り値となる。
引数と戻り値が同じの為インスタンス生成時は1つでOK
UnaryOperator

BinaryOperator
2つの引数を受け取り、同じ戻り値型を返す
同様に引数の型指定のみ記載するだけなので、2つの指定でOK
BinaryOperator

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を使用してデータベース操作を実行する

※以下説明でSQLやTable(USER)が出現しますが詳細は割愛

16-1. 接続の確立

重要クラス: DriverManager と Connection
DriverManager が適切なドライバーを見つけ、Connection オブジェクトを生成
DriverManagerのおかげで、接続先DBを意識することなく接続を確立できる。(OracleDB,MySQLどちらもURLで適切な接続先を指定すると同じ記載で接続が確立する)

Connection conn = DriverManager.getConnection(String URL, String USER, String PASSWORD)

最も基本的なDB接続方法。DBパスとユーザー、パスワードを記載する

Connection conn = DriverManager.getConnection(String URL)

認証不要なDB or URL内にUSERとPASSWORDを入力する際は引数の省略可能。セキュリティの観点でよろしくない
 

Properties props = new Properties();
props.setProperty("user", "user1");
props.setProperty("password", "pass1");
props.setProperty("ssl", "true");
props.setProperty("loginTimeout", "10");

Connection conn = DriverManager.getConnection(String URL, Properties INFO)

ユーザー名・パスワード以外の設定を渡す必要がある場合にPropertiesを引数に指定する


16-2. ステートメントオブジェクトの作成とSQLの実行

重要クラス: Statement, PreparedStatement, CallableStatement
PreparedStatementは、セキュリティ(SQLインジェクション対策)とパフォーマンス(事前コンパイル)の観点から最も重要

Connectionクラスの以下メソッドにてStatementオブジェクトを生成する

createStatement() :SQL を保持しない「Statement」を作成
prepareStatement() :SQL を事前コンパイル。「PreparedStatement」を作成
prepareCall() :ストアドプロシージャ呼び出し専用。「CallableStatement」を作成

Statement

メソッド

※試験・実務で利用する主要なメソッドに絞って説明しています。
 

executeQuery(String sql) :SELECT 用、戻り値は ResultSet
executeUpdate(String sql) :INSERT / UPDATE / DELETE 用、戻り値は更新件数

execute(String sql):戻り値が boolean(ResultSet 存在有無確認)
          SELECT:"true" ( ResultSetは存在する )
          UPDATE/DELETE:"false" ( ResultSetが存在しない、更新件数が戻り値 )

execute(String sql, int autoGeneratedKeys)
executeUpdate(String sql, int autoGeneratedKeys) :主キー自動生成を取得したいとき(INSERT)。

実装例

executeQuery(String sql)

Connection conn = DriverManager.getConnection(URL,USER,PASSWORD);
Statement stm = conn.createStatement();
ResultSet result = stm.executeQuery("SELECT * FROM USERS");

while (result.next()) {
//ResultSetのnext()メソッドで値を取り出し処理を行う
);

Statementのインスタンス作成時に引数は指定せず、executeQuery()でSQLを渡す。
※Statement は SQL を保持しないため、createStatement() の時点では SQL を渡さない。 SQL は executeQuery(String sql) や executeUpdate(String sql) の引数として渡す。

executeUpdate(String sql) / execute(String sql)
※利用する場面がほぼないので省略

execute(String sql, int autoGeneratedKeys)`

CREATE TABLE USERS (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(50)
);
Statement stmt = conn.createStatement();
stmt.execute("INSERT INTO USERS(name) VALUES('Taro')", Statement.RETURN_GENERATED_KEYS);

ResultSet keys = stmt.getGeneratedKeys();
if (keys.next()) {
    int id = keys.getInt(1);
    System.out.println("生成されたID = " + id);
}

DB側のテーブルでIDにオートインクリメントが設定されている場合に上記のように設定が可能。ただし、そもそもAUTO_INCREMENT(IDENTITY / SERIAL)付きのカラムは INSERT 時に省略できる。これらを利用する場面としてはDB が自動生成した主キーを Java 側で取得したいときにStatement.RETURN_GENERATED_KEYS を指定する。そして、getGeneratedKeys() で取得するような操作が想定される。ここでexecuteUpdate() で更新を行うと戻り値で更新件数が返却されてしまうので、実際に設定されるIDを取得したい場合に使い分けを行う

PreparedStatement

メソッド

実行系
 Statementの引数なしバージョン、 内容はStatementと同様
 executeQuery()
 executeUpdate()
 execute()

セット系
 setXXX(int parameterIndex, 型 x)

 例:setString(int parameterIndex, String x)
   setInt(int parameterIndex, int x)
   setBoolean(int parameterIndex, boolean x) etc...

 setXxx() は基本的にDB のカラム型に合わせて値をセットする。ただし、JDBC が内部で型変換を行うため、完全に 1:1 対応ではない。例えばDB側で VARCHAR / CHAR / TEXT 各定義がされていた場合でもsetString(X,XXX)を選択することで実行可能。詳細は別途確認

実装例

executeQuery()

Connection conn = DriverManager.getConnection(URL,USER,PASSWORD);
PreparedStatement pstm = conn.prepareStatement("SELECT * FROM USERS WHERE name = ? AND password = ?")

pstm.setString(1,userData.get(0));
pstm.setString(2,userData.get(1));
ResultSet result = pstm.executeQuery();

if(result.next() != false){
//取得した値の処理
}

executeUpdate()

Connection conn = DriverManager.getConnection(URL,USER,PASSWORD);
PreparedStatement pstm = conn.prepareStatement("DELETE FROM USERS WHERE id = ?");

pstm.setInt(1, id);
int deleteCount = pstm.executeUpdate();

試験対策メモ
PreparedStatement及びCallableStatementはStatementのサブクラス
statement以外のSQL実行(executeQuery(),executeUpdate())はインスタンスを作成した時点でSQLを指定する為、実行時に引数は不要。実行の際に引数を渡すと上位クラスのStatementとJVMは認識し、コンパイルエラーではなく実行時例外として処理される点注意

execute()

String sql = "SELECT * FROM USERS WHERE id = ?";
try (
        Connection conn = DriverManager.getConnection(URL,USER,PASSWORD);
        PreparedStatement pstm = conn.prepareStatement(sql)
) {
    pstm.setInt(1, 1);

    boolean hasResultSet = pstm.execute(); 

    if (hasResultSet) {
        // SELECT の場合は true が返る
        ResultSet rs = pstm.getResultSet(); //
        while (rs.next()) {
           //処理
        }
    } else {
        // UPDATE/DELETE の場合は false が返る
        int updateCount = pstm.getUpdateCount();
        System.out.println("更新件数 = " + updateCount);
    }

} catch (SQLException e) {
    e.printStackTrace();
}

実行するSQLが検索系か更新系かわからない時に利用する。そんな状況があるのか利用場面が見えない。実作業でぶつかった時に考えるレベルで覚えておく

CallabredStatement

SQLストアド・プロシージャを実行するのに使用されるインタフェースです。 JDBC APIは、ストアド・プロシージャSQLエスケープ構文を提供します。これにより、すべてのRDBMSに対し標準の方法でストアド・プロシージャを呼び出せます。

CallableStatement は PreparedStatement を拡張した APIかつStatementをスーパークラスとするため、上記で説明済みのメソッドが利用可能。ここではストアドプロシージャ特有の操作について説明する

 メソッド

 registerOutParameter(int parameterIndex, int sqlType)
 registerOutParameter(int parameterIndex,int sqlType,int scale)

 実装例

IN / OUTをもつプロシージャの説明

CREATE PROCEDURE GET_USER_NAME(
    IN  p_id   INT,
    OUT p_name VARCHAR(50)
)

String sql = "{call GET_USER_NAME(?, ?)}";

try (
        Connection conn = DriverManager.getConnection(URL);
        CallableStatement cs = conn.prepareCall(sql)
) {
    // IN パラメータ
    cs.setInt(1, 10);

    // OUT パラメータを登録(2番目の ? に VARCHAR が返ってくる)
    cs.registerOutParameter(2, Types.VARCHAR);

    // 実行
    cs.execute();

    // OUT パラメータの取得
    String name = cs.getString(2);
    System.out.println("name = " + name);

} catch (SQLException e) {
    e.printStackTrace();
}

INOUTをもつプロシージャの説明

CREATE PROCEDURE ADD_PREFIX(
    INOUT p_name VARCHAR(50)
)
String sql = "{call ADD_PREFIX(?)}";

try (
        Connection conn = DriverManager.getConnection(URL);
        CallableStatement cs = conn.prepareCall(sql)
) {
    // IN 部分
    cs.setString(1, "Taro");

    // OUT 部分(INOUT なので OUT としても登録)
    cs.registerOutParameter(1, Types.VARCHAR);

    cs.execute();

    String result = cs.getString(1);
    System.out.println(result); // 例: "Mr. Taro"

} catch (SQLException e) {
    e.printStackTrace();
}


 

16-3. 結果の取得 (ResultSet)

重要クラス: ResultSet
取得した表の行を指し示すカーソルのようなもので、next() メソッドで次の行へ移動しながらデータを読み取る。( 「2. SQLの実行 (Statement)」で合わせて説明済み )

16-4. リソースの解放 (Close)

役割: 使用済みのデータベースリソース(ResultSet、Statement、Connection)を確実に閉じる

試験対策メモ
I/Oと同様にtry-with-resourcesが必須。リソースは開いた順とは逆の順序でクローズされる点は押さえておく(ResultSet → Statement → Connection)
★JDBCに関するクラスはAutoCloseableを実装している

try-with-resources を使った自動クローズ

String url = "jdbc:~~~~";

try (Connection conn = DriverManager.getConnection(url);
     Statement stmt = conn.createStatement();
     ResultSet rs = stmt.executeQuery("SELECT * FROM EMP")) {

    while (rs.next()) {
        System.out.println(rs.getString("NAME"));
    }

} catch (SQLException e) {
    e.printStackTrace();
}

try() 内に Connection / Statement / ResultSet を置くことで、AutoCloseable により自動的に close される。※すべてを置く必要はなく一部だけも可。その際は一部だけ手動クローズで対応する

try-with-resources を使わない手動クローズ

// 手動で close するパターン
String url = "jdbc:~~~";

Connection conn = null;
Statement stmt = null;
ResultSet rs = null;

try {
    conn = DriverManager.getConnection(url);
    stmt = conn.createStatement();
    rs = stmt.executeQuery("SELECT * FROM EMP");

    while (rs.next()) {
        System.out.println(rs.getString("NAME"));
    }

} catch (SQLException e) {
    e.printStackTrace();

} finally {
    // null チェックしながら手動で close
    try { if (rs != null) rs.close(); } catch (SQLException ignored) {}
    try { if (stmt != null) stmt.close(); } catch (SQLException ignored) {}
    try { if (conn != null) conn.close(); } catch (SQLException ignored) {}
}

それぞれ接続を確認して確実にクローズする必要がある

ユースケース
close()はfinally句内でなくてもよい。ただし、例外等が発生すると処理を中断され到達しないケースが発生する。finally句は例外等が発生した場合でも最後に呼び出される特別な領域なので基本的にはfinally句に記載する

 

17. ローカライズ

・Localeクラスを使用する
・リソース・バンドルを使用する
・Javaでメッセージ、日付および数値の形式を指定する

17-1 ローカライズとは

 アプリケーションを異なる言語や地域(ロケール)に合わせて調整するための機能。ユーザーの言語設定に応じて、日付、通貨、メッセージなどを自動的に切り替える。ローカライズは主に以下の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がある。

17-2 ロケール生成とローカライズメソッドの説明

 以下ソースコードで指定がなければデフォルトロケールは日本で説明しています。

Locale

オブジェクト生成方法

 「コンストラクタ」「静的ファクトリメソッド」「Local.Builderクラス」
上記の3パターンでオブジェクト生成を行う

コンストラクタ

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() :利用可能なロケールを一覧で取得。JRE がサポートしているすべてのロケールの配列で返す。

//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);


        //明示的にデフォルトロケールをUSに変更
        Locale.setDefault(Locale.US);
        Locale newLocale = Locale.getDefault();
        System.out.println(newLocale);//en_US
    }
}

「現在のロケール: 日本語(日本)」 「メッセージ: こんにちは」

備忘録
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.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)別ルートの探索が行われる(自動フォールバック)。ただし、Localeオブジェクトの生成時にnew Locale("ja")で国情報を渡さなかった場合は「Messages_ja → Messages」と「Messages_ja_JP」の探索は行われない点覚えておく


17-3 ロケールの設定と情報の取得方法

//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);


        //明示的にデフォルトロケールをUSに変更
        Locale.setDefault(Locale.US);
        Locale newLocale = Locale.getDefault();
        System.out.println(newLocale);//en_US
    }
}

「現在のロケール: 日本語(日本)」 「メッセージ: こんにちは」

17-4 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つの値として扱われます。さらに続きます。

18.アノテーション

・アノテーションの目的と一般的な使用パターンについて説明する
・アノテーションをクラスとメソッドに適用する
・よく使用されるJDKのアノテーションについて説明する
・カスタム・アノテーションを宣言する

18-1 アノテーションの説明

 クラス、メソッド、フィールドなどにメタデータ(付加的な情報)を付与するための仕組みでコンパイラや開発ツール、または実行時にJVM(Java仮想マシン)がその情報を利用する。また、アノテーション自体はプログラムに影響を与ることはなく(※1)アノテーションを付与した情報を利用してJVM等が処理を行う際に真価を発揮する。

※1 余談:Springでよく利用するバリデーションアノテーション(@​NotNull)を例に、設定するだけでバリデーションが働きプログラムの挙動に変化が生じるため影響しているように感じるが、これはアノテーションによる影響ではなく、それを読み取る仕組み(フレームワーク)により挙動が変化している。

18-2 基本ルール

・アノテーションは、インタフェースに似た構文で定義する。
・インタフェースの前に@を付け、@​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

18-3 要素指定のルール

 「アノテーションの要素はコンパイル時に確定できるリテラルまたは定数式でなければならない」としており、以下に取り扱いルールを記載する。

要素として指定可能な型:プリミティブ型、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<>())

18-4 メタ・アノテーション

 アノテーションを定義するためのアノテーション

@​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 {}

18-5 @​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);
    }
}

18-6 マーカーインタフェースについて

・名前の通り何も持たない(マークする)インタフェース
・特定のカテゴリに属している
・特別な振る舞いをすることを明示する役割

標準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; // このフィールドは直列化されない
	
   	 // ... コンストラクタや他のメソッド
	}

18-7 アノテーションとマーカーインタフェース比較

アノテーションは、マーカーインタフェースに比べ、柔軟かつより多くの情報を付加できる。引数を設定できたり、その目印に関する具体的な情報の付与が可能。このため、Java 5以降では、特別な振る舞いを付与するためにアノテーションを推奨することが多くなった。なので全てアノテーションを利用すればよいというわけでもなく、マーカーインタフェースには「型」として扱えるという明確な利点がある。型として扱えると、コンパイル時にinstanceof演算子やジェネリクスなどで型チェックを行うことができ、より厳密な型安全性を確保できる場合があるため、どちらを使うかは、要件に応じて適切に判断することが重要。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?