4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

OCJPをSE7からSE11にアップグレードしたときの勉強記録

Last updated at Posted at 2022-02-27

この記事について

2016.10に取得した「Oracle Certified Java Programmer, Gold SE 7」をそろそろアップデートしようかなと思い、2022.02に「Upgrade OCJP Java 6, 7 & 8 to Java SE 11 Developer」を受験した時の勉強内容を記録。

この資格について

Oracleの認定資格のうち、Javaに関する資格「Oracle Certified Java Programmer」は

  • Gold(Professional)
  • Silver(Associate)
  • Bronze

の3段階に分かれている。
海外ではBronzeは存在せず「Silver」「Gold」はそれぞれ「Associate」「Professional」と呼ばれている、そのためBronzeは国際資格ではない。
javase11_ja.png

BronzeとSilverは前提条件なし、Goldは2つ前までのバージョンのSilver保持、または3つ前までのバージョンのGoldの保持が前提条件。
cart_path.png
参考:Oracle Java SE 認定資格パス

180分80問で合格ラインはアップグレードの方が少し低く61%、合格率は非公開

スクリーンショット 2022-02-26 21.09.14.png

参考:認定試験一覧 | オラクル認定資格制度 | Oracle University

アップグレードじゃない方の試験(Java SE 11 Programmer II (1Z0-816-JPN))の方は試験範囲等の詳細が公開されている。
https://www.oracle.com/jp/education/certification/1z0-816-jpn-31705-ja.html

が、アップグレードの方はなぜか404…(下記ページの「試験詳細」のリンク先)
おそらくアップグレードじゃない方と範囲は同じ…はず?
https://education.oracle.com/ja/upgrade-ocp-java-6-7-8-to-java-se-11-developer/pexam_1Z0-817

バージョンアップについて

バージョンはSE8までは1ずつ上がっていたが、Java9以降、Java自体のバージョンアップが6ヶ月おきというハイペースになったため、認定資格は8の次が11、その次は17の予定らしい。

↓SE9以降Javaのリリースモデルが変わった。
java_release.gif

現在はGold SE17が「Comming soon」になっている。※2022.02現在
スクリーンショット 2022-02-13 21.13.52.png

SE8 Goldの認定試験がリリースされたのが2015秋頃、SE11は2019夏頃ということを考えるとだいたい3〜4年周期で認定資格も新しいバージョンがリリースされるようだ。
スクリーンショット 2022-02-13 21.18.15.png

認定パスにもあったように3つ前のバージョンからはバージョンアップが可能なため、取得から10年以上寝かせてもバージョンアップは可能だが、能力の証明としては2つ以上前になるとイメージが悪い、、、
もうすぐSE17もリリースされるようだが、対策本の発売はリリースから半年ほどかかる上、リリース直後は合格ラインが少し辛めになっているため、SE17を待たずにSE11へのアップグレードを行うことにした。

勉強開始前の状況

「Oracle Certified Java Programmer, Gold SE 7」取得から5年半程度経っている。
Javaは仕事でたまに読み書きする。ただし、仕事で使っているバージョンはSE8。

勉強で使ったもの

1. 問題集
Javaの黒本と呼ばれるやつ、少し探してみたがアップグレード試験対策の本は無さそうなのでGold試験対策の本をKindleで購入。
問題集と言いつつ、解答解説で試験範囲を丁寧に解説してくれている。
OCJP Gold SE7を取得したときもこのシリーズを使って図解など非常にわかりやすかったので、今回もこれをメインの教材に利用。

徹底攻略Java SE 11 Gold問題集[1Z0-816]対応

2. IntelliJ IDEA CE
すごく便利なIDE(無償)、いつも開発に使ってるやつ。
問題集で出てきたコードとか実際に書いて動かしてみるのに利用。

ダウンロード IntelliJ IDEA

勉強時間

約55時間
「徹底攻略Java SE 11 Gold問題集」の解答/解説を読みながら、気になったところは公式のリファレンスも読みながら実際にコードを書いて動かしてみて、自分なりにノートとしてまとめるのを繰り返した。
最後の総仕上げ問題まで終わった時点で大体50時間ぐらい、あとはノートを見ながら理解の怪しいところをメインに復習して終わり。

結果

合格ライン 61% に対し正解率 69%
80問 出題なので 55問 正解?思ったよりわからない問題も多かった。

2022-02-27 22.52.22.png

今回の反省点としては、主に下記の3点が大きかった。
クラス、インタフェースの制約についての理解が浅かった。
型推論(var)についてほぼ勉強していなかった。※これに関しては問題集でもほぼ触れていなかった、公式の出題範囲にも書かれていないのが不思議
モジュールシステム(特にコマンド)についての理解が浅かった。

今回の出題割合だが、多かった順に

  1. ストリームAPIと関数型インタフェース:やはりSE8以降の目玉アップデートなので一番出題が多かった。
  2. モジュールシステム:SE9で追加された機能の目玉?これもそれなりに多かった。
  3. NIO2:これはSE7の追加機能だが、ファイルIOはよく使うからかそこそこ出た。
  4. 型推論(var):これもSE10で追加されたからかそこそこ出た。

あとは試験範囲に書かれている内容が全体的に少しずつ。

アップグレード試験は初めて受験したので、本当にアップグレードじゃない方(1Z0-816-JPN)と同じ内容が出るのか少し不安だったが、おそらく同じ内容で少し合格ラインがゆるいだけ?
また2、3個先のバージョン(SEのバージョンとしては12~18個先?)がでたらアップグレードしようかなと思います。

勉強ノート

クラスとインタフェース

クラスのネスト

概要 特徴
インナークラス クラスの中に定義したクラス エンクロージングクラスとインナークラス2つのインスタンス化が必要
staticインナークラス クラスの中に定義したstaticクラス インスタンス化は不要、非staticなエンクロージングクラスの中にある場合、エンクロージングクラスのメンバを参照できない
ローカルクラス メソッドの中に定義したクラス そのクラスを定義したメソッドのローカル変数を扱う場合、先に定義する必要がある。このローカル変数は実質的finalになる。
匿名クラス メソッドの中に定義した名前のないクラス Interfaceの実装やクラスの継承を名前のないクラスとして宣言する。他のメソッドやクラスと共有しない一時的な実装を行いたい場合などに利用する。オーバーライドや独自メソッドの追加ができる。コンストラクタは定義できない。
エンクロージングクラス ネストしたクラスを囲むクラス
  • インナークラスは関連するクラスをまとめたり、カプセル化し、クラス外部から扱えなくするなどの目的で利用する。
  • staticインナークラスから非staticなエンクロージングクラスのメンバにはアクセスできない
  • 非staticなインナークラスにstaticメンバは定義できない
匿名クラスの初期化子

匿名クラスにコンストラクタは存在しない、代わりに「初期化子」を利用する。
{}で処理を囲うとインスタンス生成時に実行される。

Main.java
package com.example;
import java.util.ArrayList;
import java.util.List;

public class Main {

    public static void main(String[] args) {
        List<String> list = new ArrayList<>() {
            {
                super.add("A");
                super.add("B");
            }
        };
    }
}

インタフェースの制限

インタフェースは通常のクラスと違い、定義できるものが限られる
インタフェースで定義できるもの

  • 抽象メソッド
  • 定数
  • デフォルトメソッド(Java8から):インタフェースに具体的な処理を持たせる具象メソッド、public前提
  • staticメソッド(Java8から)
  • privateメソッド(Java9から):デフォルトメソッドから呼び出すためだけの処理を記述する具象メソッド

インタフェースのstaticメソッドはそれを継承したインタフェースや具象クラスには引き継がれない。

その他

  • staticメソッドはオーバーライドできない、オーバーライド=インスタンスの動作であり、staticはインスタンスのメソッドではないから。
  • 親クラスと実装元インタフェースに同名のメソッドがあった場合、親クラスが優先。
  • 「リスコフの置換原則」:SがTの派生型であれば、プログラム内でT型のオブジェクトが使われている箇所は全てS型のオブジェクトで置換可能でなければならない。
  • enumのコンストラクタはprivateでなければならない。※省略すると暗黙的にprivateになる。

関数型インタフェース

パッケージ インタフェース 戻り値 メソッド 概要
java.util.function Supplier<T> T get() supplier=供給者、値を取得する
java.util.function Consumer<T> void accept(T) consumer=消費社、引数で受け取った値から副作用を起こす
java.util.function BiConsumer<T, U> void accept(T, U) 引数が2つ
java.util.function Predicate<T> boolean test(T)
and(Predicate<T>)
or(Predicate<T>)
predicate=断定、判定結果を返す
java.util.function BiPredicate<T, U> boolean test(T, U)
and(Predicate<T, U>)
or(Predicate<T, U>)
引数が2つ
java.util.function Function<T, R> R apply(T)
andThen(Function<T, R>)
compose(Function<T, R>)
値を変換する、特定の値を受け取り、異なる型に変換して返す。compose()は逆順
java.util.function BiFunction<T, U, R> R apply(T, R)
andThen(Function<T, U, R>)
compose(Function<T, U, R>)
引数が2つ
java.util.function UnaryOperator<T> T apply(T) operator=演算子、受け取った値から演算した結果を返す
java.util.function BinaryOperator<T> T apply(T, T) 引数が2つ
java.lang Runnable void run() 戻り値を返さない処理
java.util.concurrent Callable<V> V call() 戻り値を返す処理
java.util Comparator<T> int compare(T, T) 順序付けのため比較、第1引数のほうが小さい場合負数、同じ場合0、大きい場合正数を返す

並列処理

並行処理と並列処理

  • 並行処理(concurrent):処理を切り替えて同時に動いているように見せる。
  • 並列処理(parallel):複数のコアで同時に処理を行う。

マルチスレッド

  • スタック領域:Javaのメソッドの呼び出し順制御に使われる。
  • スレッド:メソッドがスタックに入ったり出たりするのを繰り返す一連の流れ。
  • マルチスレッド:スタック領域が同時に複数用意され、スレッドを同時に複数発生させる。

スレッドの作成

マルチスレッドに関わるクラスとインタフェース

パッケージ クラスorインタフェース メソッド 概要
java.lang Thread(クラス) run()
start()
マルチスレッドのためのスレッド定義、run()メソッドに処理を記述し、start()メソッドの呼び出しで実行
java.lang Runnable(インタフェース) run() Threadクラスのコンストラクタが受け取る関数型インタフェース

サブスレッドを定義し、

SubThread.java
public class SubThread extends Thread {
    @Override
    public void run() {
        System.out.println("sub");
    }
}

メインスレッドの中でサブスレッドを作成

Main.java
public class Main {
    public static void main(String[] args) {
        Thread t = new SubThread();
        t.start();
        System.out.println("main");
    }
}

↓匿名クラスとラムダ式に置き換えることもできる。

Main.java
public class Sample {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            System.out.println("sub");
        });
        t.start();
        System.out.println("main");
    }
}

スレッドプール

  • スレッドプール:複数個の空のスレッドを最初から作っておき、タスクが与えられたときに取り出す仕組み。スレッドを使い回すことでスレッドの無駄遣いをなくす。

Executorフレームワーク

java.util.concurrent.Executor

パッケージ インタフェース メソッド 概要
java.util.concurrent Executor execute() ExecutorServiceScheduledExecutorServiceの親インタフェース
java.util.concurrent ExecutorService submit() submit()で渡された処理をスレッドプールで実行する。
java.util.concurrent ScheduledExecutorService schedule()
scheduleAtFixesRate()
scheduleWithFixedDelay()
一定時間待機したり、一定時間の周期で渡された処理をスレッドプールで実行する。
java.util.concurrent Executors newSingleThreadExecutor()
newFixedThreadPool()
ExecutorServiceScheduledExecutorServiceの実装を返すファクトリメソッドを持つ

Executorsの持つファクトリメソッド

メソッド 引数 概要
newSingleThreadExecutor() なし スレッドを1つプールするExecutorServiceを作る
newFixedThreadPool() int 引数で指定された数のスレッドをプールするExecutorServiceを作る
newCachedThreadPool() なし 必要に応じてスレッドを増減させるExecutorServiceを作る。一度増えたスレッドは60秒間使用されなければ破棄、それ以内であれば再利用される
newSingleThreadScheduledExecutor() なし スレッドを1つプールするScheduledExecutorServiceを作る
newScheduledThreadPool() int 引数で指定された数のスレッドをプールするScheduledExecutorServiceを作る

ScheduledExecutorServiceのメソッド

メソッド 引数 概要
schedule() Runnable, Long, TimeUnit 処理を一度だけ遅延実行する。TimeUnit単位でLong時間待機後、Runnableを実行
scheduleAtFixesRate() Runnable, Long, Long, TimeUnit 一定のインターバルで繰り返し実行する。TimeUnit単位でLong時間待機後、Long時間周期でRunnableを実行
scheduleWithFixedDelay() Runnable, Long, Long, TimeUnit 一定のインターバルで繰り返し実行する、各処理の終了後からインターバルが開始されるので処理時間に関係なく一定のインターバルにできる。TimeUnit単位でLong時間待機後、Runnableを実行

Future

ExecutorServiceScheduledExecutorServiceにタスクを登録するメソッドはFutureを返す。
並列処理の各結果を受け取りたい場合、FutureListなどで受け取る。
その場合、Runnableだと戻り値を返せないのでCallableExecutorServicesubmit()などに渡して登録する。

Main.java
public class Sample {
    public static void main(String[] args) {
        ExecutorService exec = Executors.newSingleThreadExecutor();
        // Callableから受け取りたい戻り値の方をFutureのジェネリクスに指定する
        List<Future<Boolean>> futures = new ArrayList<>();

        for (int i = 0; i < 10; i++){
            futures.add(exec.submit(() -> {
                // 戻り値を返しているのでCallable
                return i % 2 == 0;
            }));
        }

        for (Future<Boolean> future : futures) {
            System.out.println(future.get());
        }
    }
}

サブスレッドで例外が発生した場合はFuture.get()メソッドの実行時に検査例外であるjava.util.concurrent.ExecutionExceptionがスローされる。実際に発生した例外を取り出したいときは、ExecutionExceptiongetCause()メソッドを利用する。

同期化処理

java.util.concurrent.CyclicBarrierクラスを利用すると複数のスレッドが特定のポイントまで到達するのを待機させることができる。

  • バリアー:複数スレッドの待ち合わせポイント
  • バリアーアクション:待ち合わせ後に実行される処理

バリアーの利用例

Main.java
package com.example;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {
    public static void main(String[] args) {
        ExecutorService exec = Executors.newCachedThreadPool();
        CyclicBarrier barrier = new CyclicBarrier(5, () -> {
            System.out.println("end");
        });

        for (int i = 0; i < 5; i++) {
            exec.submit(() -> {
                Long id = Thread.currentThread().getId();
                System.out.println(id);
                try {
                    barrier.await();
                } catch (InterruptedException | BrokenBarrierException e) {
                    throw new RuntimeException(e);
                }
            });
        }
    }
}

排他制御

synchronizedキーワードを利用することであるスレッドが処理している間、他のスレッドで同じ処理をさせない「排他制御」ができる。

  • synchronizedメソッド:メソッドの修飾子にsynchronizedを指定
  • synchronizedブロック:メソッドの中でインスタンスを指定
void someMethod () {
    synchronized (this) {
       doSomething();
    }
}

java.util.concurrent.atomicパッケージのクラスでも、排他制御ができる。addAndGet()getAndSet()などのメソッドを持つ。

クラス 概要
AtomicBoolean アトミック変数としてboolean型を扱うためのクラス
AtomicInteger アトミック変数としてint型を扱うためのクラス
AtomicLong アトミック変数としてlong型を扱うためのクラス
AtomicReference アトミック変数として参照を扱うためのクラス

java.util.concurrent.CopyOnWriteArrayListクラスはスレッドセーフに処理を行うためのListクラス。
読み出しの際にコピーを作るため、マルチスレッドで競合しない。

java.util.concurrent.lock.ReentrantLockクラスのlock()メソッドはそのメソッドを呼び出したスレッドがunlock()メソッドを呼び出すまで別のスレッドによるlock()メソッドの呼び出しを待機させる。
これにより複数のメソッドにまたがるような処理でも排他制御できる。

キャッシュの抑止

マルチスレッドで扱うクラスのフィールドにvolatile修飾子を付けるとキャッシュされなくなる
これにより並列処理時にどちらかがキャッシュを読み込むことによる不整合をなくせる

Optional

Optionalのファクトリメソッド

メソッド 概要
static <T> Optional<T> empty() 空のOptionalインスタンスを生成
static <T> Optional<T> of(T value) 引数の値からOptionalインスタンスを生成
static <T> Optional<T> ofNullable(T value) 引数の値からOptionalインスタンスを生成、引数の値がnullの場合は空のOptionalインスタンスを生成

Optionalの持つメソッド

メソッド 概要
get() 空でない場合取得、空の場合java.util.NoSuchElementExceptionをスロー
isPresent() 空でない場合trueを返す
isEmpty() 空の場合trueを返す(Java11で追加)
orElse(V) 空でない場合取得、空の場合引数で渡した値を返す
orElseGet(Supplier<T>) 空でない場合値を取得、空の場合引数で渡したSupplier処理の結果を返す
ifPresent(Consumer<T>) 空でない場合、引数で渡したConsumerの処理を実行
ifPresentOrElse(Consumer<T>, Runnable) 空でない場合、引数で渡したConsumerの処理を実行、空の場合Runnableを実行
map(Function<T>) 値にFunctionの処理を実行してOptionalで返す、空の場合新たな空のOptionalインスタンスを生成して返す
flatMap(Function<T>) 値にFunctionの処理を実行して返す(Optionalに入れない)、このFunctionはOptionalを返す必要がある。空の場合新たな空のOptionalインスタンスを生成して返す

ストリームAPI

  • 0個以上の中間操作と1個の終端操作を組み合わせたものが「ストリーム・パイプライン」
    「生成→中間操作...中間操作→終端操作」の流れ
  • 実際に処理が実行されるのは終端操作を実行した時
  • ストリームAPIはfor文などの繰り返し処理の置き換えではない、コレクションの全要素への処理やグルーピング、統計、抽出などを行いやすくするためのもの。

中間操作(抽出、変換、ソート等)

メソッド 概要
distinct 重複を除いたストリームを返す
filter 引数で指定した条件でフィルタリングしたストリームを返す
limit 引数で指定した数に切り詰めたストリームを返す
map 引数で指定した関数を適用した結果のストリームを返す
peek 新しい結果ストリームを生成し、引数で指定した関数を適用して返す
skip 引数で指定した最初のn個を破棄し、残った要素のストリームを返す
sorted 自然順序に従ってソートしたストリームを返す

終端操作(取得、コレクション変換、統計等)

メソッド 概要
allMatch すべて一致すればtrue
anyMatch いずれか一致すればtrue
collect コレクションに変換
count 要素数を返す
findAny 任意の要素を返す
findFirst 先頭の要素を返す
forEach 繰り返し処理を実行
max 最大を返す
min 最小を返す
noneMatch 一致するものがなければtrue
reduce 要素を累積的に結合していくリダクション処理を実行する
toArray 配列に変換
  • コレクションの並列ストリーム取得はlist.parallelStream()戻り値はStream型
  • IntStreamなどプリミティブ型のストリームもある、Streamとはスーパークラスは同じだが互換性はない

リダクション操作

  • ストリーム内のすべての要素を結合的累積関数により1つにまとめ、その結果を返す操作を「リダクション操作」という。
  • ストリームの終端操作reduce()collect()がこれにあたる。
  • リダクション関数が引数に取るのはBinaryOperator<T>で、この時渡す結合的累積関数を「アキュムレータ関数」という。
戻り値 メソッド 引数 概要
Optional<T> reduce BinaryOperator<T> 1つめと2つめの要素にBinaryOperatorの処理を実行して返す、返された結果と次の要素にBinaryOperatorの処理を実行して返す、を要素がなくなるまで繰り返す
T reduce T, BinaryOperator<T> 初期値ありのreduce()、戻り値がOptionalじゃない
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Integer result = list.stream()
        .reduce(0, (a, b) -> a + b);

↑は↓の処理に相当する(実際はストリームなので順次実行ではない)

reduceの例
Integer result = 0;
for (Integer i : nums) {
    result += i;
}
return result;

ストリームとラムダ式の特性

ラムダ式はラムダ式内部から外部のオブジェクトを変更(副作用を与えること)をしてはいけない。
ラムダ式内部から外部の変数を参照すると参照先の変数は実質的にfinalとして扱われる。
このため、変数の参照そのものを変更しようとするとコンパイルエラーになる。
だが、参照先オブジェクトの持つ値の変更の場合、コンパイラにチェックされないためエラーにならず、副作用を与えることができてしまう。

Collectorインタフェース

ストリームの終端操作として.collect(Collectors.toList())などはよく使うが、これはjava.util.stream.Collectorインタフェースの実装の一つで、同様にjava.util.stream.Collectorインタフェースを実装すれば、独自の終端操作もできる。この実装を匿名クラスでなく具象クラスとして定義しておけば使い回すこともできる。

groupingBy

終端操作の.groupingBy()を使うと、グループ化したMapの生成ができる。

例1) 奇数と偶数でグルーピング

List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5);
Map<String, List<Integer>> numsByGroup = nums.stream()
        .collect(Collectors.groupingBy(x -> x % 2 == 0 ? "EVEN" : "ODD"));

例2) グループ每に合計

List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5);
Map<String, Integer> sumByGroup = nums.stream()
        .collect(Collectors.groupingBy(
                x -> x % 2 == 0 ? "EVEN" : "ODD",
                Collectors.summingInt(x -> x)
        ));

例3) グループ毎の要素数をカウント

List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5);
Map<String, Long> countByGroup = nums.stream()
        .collect(Collectors.groupingBy(
                x -> x % 2 == 0 ? "EVEN" : "ODD",
                Collectors.counting()
        ));

キーがBooleanでいい場合.partitioningBy()でもいい

List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5);
Map<String, Integer> countByGroup = nums.stream()
        .collect(Collectors.partitioningBy(x -> x % 2 == 0));

処理の順序

.peek()を使うとストリームの途中で値の状態を確認することができる。

List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5);
nums.stream()
        .peek(x -> System.out.println("1つめのpeek: " + x))
        .map(x -> x * 2)
        .peek(x -> System.out.println("2つめのpeek: " + x))
        .forEach(System.out::println);
実行結果
1つめのpeek: 1
2つめのpeek: 2
2
1つめのpeek: 2
2つめのpeek: 4
4
1つめのpeek: 3
2つめのpeek: 6
6
1つめのpeek: 4
2つめのpeek: 8
8
1つめのpeek: 5
2つめのpeek: 10
10

要素が1つずつ順にストリーム・パイプラインを通っていることがわかる。
ただし.sorted()などの要素全体に関わる中間処理が入る場合、その前後で区切られる。

List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5);
nums.stream()
        .peek(x -> System.out.println("1つめのpeek: " + x))
        .map(x -> x * 2)
        .peek(x -> System.out.println("2つめのpeek: " + x))
        .sorted(Integer::compareTo)
        .peek(x -> System.out.println("3つめのpeek: " + x))
        .forEach(System.out::println);
実行結果
1つめのpeek: 1
2つめのpeek: 2
1つめのpeek: 2
2つめのpeek: 4
1つめのpeek: 3
2つめのpeek: 6
1つめのpeek: 4
2つめのpeek: 8
1つめのpeek: 5
2つめのpeek: 10
3つめのpeek: 2
2
3つめのpeek: 4
4
3つめのpeek: 6
6
3つめのpeek: 8
8
3つめのpeek: 10
10

入出力

File

  • java.io.Fileクラスはディレクトリやファイルへのパスを表す
  • FileFilterインタフェースはlistFiles()などの絞り込みに利用する。Fileを受け取りbooleanを返す
メソッド 戻り値 引数 概要
exists() boolean なし 存在すればtrue
mkdirs() boolean なし ディレクトリを作成、親ディレクトリもなければ作成
createNewFile() boolean なし ファイルを作成
getAbsolutePath() String なし 絶対パスを取得
list() Strinng[] なし ファイル・ディレクトリの一覧をStringで取得
listFiles() File[] なし ファイル・ディレクトリの一覧を取得
listFiles() File[] FileFilter ファイル・ディレクトリの一覧を取得、引数渡したラムダの条件でフィルタリング
isDirectory() boolean なし ディレクトリならtrue

FileFileFilterを使ったファイル一覧の出力

Main.java
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        File dir = new File(".");
        // ディレクトリまたは拡張子「.java」
        FileFilter filter = (path) -> path.isDirectory() || path.getName().endsWith(".java");
        Stream.ofNullable(dir.listFiles(filter))
                .forEach(System.out::println);
    }
}

入出力ストリーム

  • java.ioクラスの持つ「ストリーム」はデータの入出力を扱う(「ストリームAPI」とは関係ない)
  • BufferedInputStreamBufferedReaderは読み込んだデータをバッファに溜め込むことでIOを減らす。それ単体で入力の機能はなく、FileReaderなどと合わせて利用する。
  • FileReaderは1文字ずつ読み込み、終端で-1を返す。BufferedReaderは1行ずつ読み込み、終端でnullを返す。

ストリームの基底クラス

文字ストリーム バイトストリーム
入力 java.io.Reader java.io.InputStream
出力 java.io.Writer java.io.OutputStream

FileReaderBufferedReaderを使ったテキストファイルの読み込み

Main.java
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class Main {
    public static void main(String[] args) throws IOException {
        FileReader fr = new FileReader("sample.txt");
        BufferedReader reader = new BufferedReader(fr);
        try (reader) {
            reader.lines().forEach(System.out::println);
        }
    }
}
  • FileWriterクラスのコンストラクタはFileオブジェクトまたはファイルパスのStringを引数に取る、デフォルトは上書き
  • 上書きでなく追記したい場合、第2引数boolean appendtrueを渡すと追記モード

FileWriterBufferedWriterを利用したテキストファイルの書き込み

Main.java
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;

public class Main {
    public static void main(String[] args) throws IOException {
        FileWriter out = new FileWriter("output.txt", true);
        BufferedWriter writer = new BufferedWriter(out);
        try (writer) {
            writer.newLine();
            writer.write("Hello!");
            writer.flush();
        }
    }
}
  • java.io.Consoleはコンソール入出力用のクラス

java.io.Consoleの入力用メソッド

メソッド 戻り値 引数 概要
readLine() String なし 改行までの入力を受け取る
readPassword() char[] なし 改行までの入力を受け取る、入力の際コンソールには表示されない。受け取った値をprintln()で出力はできる
  • java.util.Scannerクラスは文字入力ストリームを簡単に扱えるようにしたクラス

シリアライズ

  • メモリ上にあるインスタンスをストリームとして出力することを「シリアライズ」、ファイルに書き出された情報を元にインスタンスを作り直すことを「デシリアライズ」という。
  • シリアライズはデシリアライズ(復元)したときに正しく動作できることを保証しなくてはいけない。
  • インスタンスが持っている参照先も含めて複数のインスタンスを1つにまとめるので「シリアライズ(直列化)」
  • java.io.Serializableはマーカーインタフェース
    ※マーカーインタフェース=抽象メソッドを持たず、クラスに何らかの意味を追加するためのインスタンス
  • java.io.ObjectOutputStreamはインスタンスをシリアライズしてファイルに出力するためのクラス
  • transientstaticで就職されたフィールドはシリアライズに含まれない
  • serialPersistentFieldsフィールドでシリアライズ対象のフィールドを指定することもできる

NIO.2

  • java.io.FileクラスはJava1.0からあり、いくつかの課題があった
  • 「NIO.2 (JSR 203:More New I/O APIs for the Java Platform」はその課題を解決すべく新しく作られたファイルとパスを扱うためJava7で追加された標準API
  • パスとクラスの操作をjava.nio.file.Pathjava.nio.file.Files java.nio.file.Fileクラスに分離した。
// parent/child/file.txt
Path path = Paths.get("parent", "child", "file.txt");
File file = path.toFile();
File file = new File("file.txt");
Path path = file.toPath();
  • java.nio.file.Filesはファイルの読み書きに便利はstaticメソッドを複数もっている。
  • Files.newBufferedReader()Files.newBufferedWriterPathを引数に渡すことでBufferedReaderBufferedWriterの生成を簡潔に書ける。
Main.java
import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;

public class Main {
    public static void main(String[] args) throws IOException {
        Path path = Paths.get("parent", "child", "file.txt");
        BufferedWriter bw = Files.newBufferedWriter(path, StandardOpenOption.APPEND);
        try (bw) {
            bw.write("Hello!");
            bw.newLine();
        }
    }
}

Files.newBufferedWriterはオプションとして第2引数に列挙型java.nio.file.StandardOpenOptionを渡すことができる

定数 概要
APPEND 追記モード
CREATE ファイルが存在しない場合新しいファイルを作成
CREATE_NEW 新しいファイルを作成、ファイルが既に存在している場合失敗
DELETE_ON_CLOSE 閉じる時に削除
READ 読み込みアクセス用に開く

ディレクトリ操作用のストリームAPIjava.nio.file.DirectoryStreamもある

Main.java
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

public class Main {
    public static void main(String[] args) throws IOException {
        Path path = Paths.get("parent", "child", "file.txt");
        Files.newDirectoryStream(path)
                .forEach(p -> System.out.println(p.getFileName()));
    }
}

Path.normalize

Pathクラスのnonrmalize()メソッドは../などで表現される冗長なパスを正規化してくれる

a/b/c/../../c

a/c

walkFileTree

  • Files.walkFileTreeはディレクトリを捜査して、詳細な処理をするためのメソッド
  • 第2引数にjava.nio.file.FileVisitorインタフェースの実装クラスを受け取る
  • この実装クラスでは4つの抽象メソッドの戻り値として、どのタイミングでどんな振る舞いをさせるかを定義する

FileVisitorの抽象メソッド

抽象メソッド タイミング
preVisitDirectory ディレクトリに入る時
postVisitDirectory ディレクトリから出る時
visitFile ファイルに遭遇した時
visitFileFailed ファイルの処理に失敗した時

抽象メソッドの戻り値に指定する定数(java.nio.file.FileVisitResultが持つ)

定数 振る舞い
CONTINUE 探索を続ける
TERMINATE 探索を終了する

JDBC(Java DataBase Connectivity)

  • JDBCはjava.sqljavax.sqlパッケージで提供される。
  • 各ベンダーのDBMS製品と連携するための共通のクラスなどが含まれる。

接続文字列

jdbc:mysql://localhost:3306/schema;

JDBCの構成要素

要素 概要
ドライバ ベンダが提供する実装クラス、mysql-connectorなど
ドライバマネージャ ドライバを扱うクラスjava.sql.DriverManager
.getConnection()に接続文字列を受け取りコネクションを返す
コネクション DBMSとの接続を管理するインタフェースjava.sql.Connection
  • StatementはSQL文を扱うインタフェース
  • PreparedStatementはパラメータを受け取る、Statementはパラメータを受け取らない
  • PreparedStatementはSQL文を予めコンパイルしておくが、StatementはSQL文を文字列のまま扱う
  • PreparedStatementでステークホルダーを置き換える.setXXX()メソッドの第一引数は1オリジン

PreparedStatementのメソッド

メソッド 概要
execute クエリを実行し、ResultSetが返ってきたかどうかをbooleanで返す。
結果を取得するには続けてgetResultSet()またはgetUpdateCount()メソッドを実行する必要がある。
executeQuery SELECT結果を返す。
executeUpdate INSERT, UPDATE, DELETEを行い、結果行数のintを返す。
executeBatch 複数のSQL文を一度に実行し、更新された数の配列を返す。
  • SELECTの結果ResultSet.next()でカーソルを移動し、.getInt().getString()で値を取得する

  • カーソルの初期状態は1行目の手前なので、.next()の前に.getXXX()するとjava.sql.SQLExceptionが発生する

  • .getXXX()は引数でカラムを指すインデックスかカラム名、インデックスは1オリジン

  • CallableStatementはストアド・プロシージャを呼び出す

  • Connection.prepareCall()で取得する

  • .execute()で実行する

コレクション&ジェネリクス

  • コレクション=集合、ジェネリクス=汎用
  • ジェネリクスはそのクラスが扱うデータの型を実行時に決めさせることで汎用性をもたせる仕組み
  • ダイヤモンド演算子<>を利用する
  • ジェネリクスを利用したクラスを「総称型」という

フィールドとしてIntegerしか扱えないクラス

Item.java
public class Item {
    private Integer value;
    public Item (Integer value) {
        super();
        this.value = value;
    }
    public Integer getValue() {
        return  value;
    }
}

↓ジェネリクスを利用することで汎用的な型を扱えるようにできる

Item.java
public class Item<T> {
    private T value;
    public Item (T value) {
        super();
        this.value = value;
    }
    public T getValue() {
        return  value;
    }
}
  • 総称型は型パラメータを渡さない事もできる、その場合Object型の型パラメータを渡した場合と同じ
List<Integer> list = new ArrayList<Integer>();

↓型パラメータなしでもObject型のListとして動く

List list = new ArrayList();
  • 型パラメータを指定した場合も内部ではObject型で扱い、ダウンキャストをおこなっている
  • 変数への代入など型が自明になるタイミングでは型を省略する「型推論」が利用できる
    • 変数への代入
    • メソッドの戻り値
    • メソッド呼び出しの引数
package com.example;

import java.util.ArrayList;
import java.util.List;

public class Item {
    public static void main(String[] args) {
        // 変数への代入時は変数宣言に記述しているので自明
        List<String> list = new ArrayList<>();
        // メソッドの引数はメソッドのシグネチャに記述しているので自明
        execute(new ArrayList<>());
    }
    private static List<String> test() {
        // メソッドの戻り値もメソッドのシグネチャに記述しているので自明
        return new ArrayList<>();
    }
    private static void execute(List<String> list) {
        list.forEach(System.out::println);
    }
}

変性

参照型をどの型として扱えるようにするかという性質を「変性」という

  • サブクラス型の代入を許す「共変性(covariance)」
  • サブクラス型の代入を許さない「非変性(invariance)」
  • さらにスーパークラス型の代入を許す「反変性(contravariance)」

通常のオブジェクトは「共変」、ジェネリクスは型安全のため「非変」、下記のような書き方はコンパイルエラー

List<Object> list = new ArrayList<Integer>();

制約付き型パラメータ

型パラメータにextendsを宣言することで、特定のクラスが持っているメンバを扱えるようになる

public class A {
    public void hello() {
        System.out.println("hello");
    }
}
public class Test<T extends A> {
    public void execute(T t) {
        t.hello();
    }
}

↑のコードはextendsをつけない場合Thello()メソッドを持っている保証がないためコンパイルエラー

型パラメータのワイルドカード

  • 型パラメータを不定にしたい場合、ワイルドカード「<?>」を利用することもできる
Main.java
import java.util.Collection;
import java.util.List;

public class Main6 {
    public static void main(String[] args) {
        List<Integer> a = List.of(1, 2, 3, 4, 5);
        List<String> b = List.of("A", "B", "C");
        printAll(a);
        printAll(b);
    }
    private static void printAll(Collection<?> collection) {
        collection.forEach(System.out::println);
    }
}
  • ワイルドカードにも型の制約を付けられる
種類 書き方 特徴
非境界ワイルドカード <?> メソッドの戻り値型はObject型になる
メソッドの引数にはnullリテラルしか渡せない
上限境界ワイルドカード <? extends X> メソッドの引数にはnullリテラルしか渡せない
下限境界ワイルドカード <? super X> メソッドの戻り値型はObject型になる

ワイルドカードの制限を決める際の原則

  • 戻り値を戻すことを目的とした「Producer」の場合:「extends(上限境界)」
  • 引数を受け取って利用することを目的とした「Consumer」の場合」:「super(下限境界)」

これを「PECS(Producer-Extends and Consumer-Super)」という

Listインタフェース

Listは追加した順序を保持する
Listインタフェースの実装クラスとして主に3つ

クラス 特徴
ArrayList ・配列を利用している
・要素数が増えた際はより大きな配列に要素をコピーするので追加が遅い
LinkedList ・要素をもたせたオブジェクトを数珠つなぎ(前後への参照を持つ)にしている
・ArrayListのようにコピーが不要のため要素の追加が速い
・リンクを順にたどるため読み込みのパフォーマンスは低い
Vector ・マルチスレッド時に利用中は他スレッドからのアクセスにロックをかけるスレッドセーフなList
・パフォーマンスは低い

Queue&Deque

キューとデックはjava.util.Queuejava.util.Dequeで実現できる

  • キュー(queue):FIFO
  • デック(deque):両端キュー(double ended queue)

Setインタフェース

Setは重複排除、順序保証なし
Setインタフェースの実装クラスとして主に2つ

クラス 特徴
HashSet 順序を保証しないので速い
TreeSet 自然順序または引数のComparatorでソート

Map.Entry

MapインタフェースはインナーインタフェースEntryを持つ
EntryMapの1要素を扱うのに利用する

Main.java
import java.util.HashMap;
import java.util.Map;

public class Main {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("ONE", 1);
        map.put("TWO", 2);

        map.entrySet().stream().forEach((Map.Entry<String, Integer> entry) -> {
            System.out.println(entry.getKey() + entry.getValue());
        });
    }
}

Java8以降はMapforEachを持つので↑このコードは↓のように置き換えられる
Map.forEach()は内部でEntryを取り出している

Main.java
import java.util.HashMap;
import java.util.Map;

public class Main7 {
    public static void main(String[] args) {
        Map<String, Integer> map = new HashMap<>();
        map.put("ONE", 1);
        map.put("TWO", 2);

        map.forEach((k, v) -> System.out.println(k + v));
    }
}

Comparable&Comparator

  • java.lang.Comparableインタフェース:.compareTo()メソッドを持つ=この実装クラスは比較可能
  • java.util.Comparatorインタフェース:比較処理.compare()メソッドを持つ関数型インタフェース=比較アルゴリズムを引数に取るためのもの

オートボクシング&アンボクシング

Java5から可能になったプリミティブ型とラッパークラスを自動変換する仕組み
もともとプリミティブ型はより大きな型への暗黙の型変換があるので、合わせて覚える

プリミティブ型 ラッパークラス 暗黙の型変換が可能な型 この型で扱われる書き方
boolean Boolean なし true,false
char Character int,long,float,double '1','a'
byte Byte short,int,long,float,double なし
short Short int,long,float,double なし
int Integer long,float,double 1,-1
long Long float,double 1L,-1L
float Float double 1.0F,-1.0F
double Double なし 1.0,-1.0

アンボクシング後の暗黙の型変換は働くが、更にオートボクシングは働かない

Integer
↓アンボクシング
int
↓暗黙の型変換
double
↓オートボクシング

Double

アノテーション

クラスやフィールド、メソッド、コンストラクタなどに意味を追加するマーカーの役割をする
アノテーションを付与されたクラスを利用するクラスに対して値を渡すこともできる

アノテーションの定義

Test.java
public @interface Test {
    String name();
    int price();
}
  • アノテーションで値を扱いたい場合抽象メソッドを定義する、この値を「注釈パラメータ」という
  • 注釈パラメータはアノテーションの付与時に引数として渡す
@Test(name = "cookie", price = 150)
public class Product {
    private final String name;
    private final int price;

    public Product(String name, int price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public int getPrice() {
        return price;
    }
}

値が1つの場合名前を省略できる

Test.java
public @interface Test {
    int price();
}
@Test(150)
public class Product {
    private final String name;
    private final int price;

    public Product(String name, int price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public int getPrice() {
        return price;
    }
}

アノテーション定義でデフォルト値を設定することもできる、デフォルト値なしで引数なしの場合コンパイルエラー

Test.java
public @interface Test {
    String name() default "No name";
    int price() default 100;
}

JavaSEで事前に定義されているアノテーション

@Override
  • @Overrideはスーパークラスのオーバーライド元メソッドの変更を検知し、コンパイルエラーにする
@Deprecated
  • @DeprecatedはJDKやライブラリのバージョンアップによって利用不可にしたい機能の移行期間に「非推奨」であることを示すために利用する
  • このアノテーションがついているクラスやメソッド、フィールドを利用しているとコンパイラが警告を発する
  • 注釈パラメータとして削除予定かどうかを指すboolean型のforRemovalを受け取る、デフォルトはfalse
@SuppressWarnings
  • @SuppressWarningsはコンパイル時の警告を抑制する
  • 注釈パラメータにString"unchecked", "deprecation", "removal"のいずれかを受け取る

メタ・アノテーション

  • インスタンスの元となるクラスの定義情報を取得するための「リフレクション」という機能がある
  • java.lang.reflectパッケージではリフレクションのためのAPIが提供される
  • java.lang.Classクラスは「クラスリテラル」(クラス名.class)でクラスの定義情報を取得できる
  • この機能を使いアノテーションから値を取得し、利用する
  • この時、アノテーションにもアノテーションを付ける必要がある、これを「メタ・アノテーション」という
Main.java(Productをテストするクラス)
public class Main {
    public static void main(String[] args) {
        Class<Product> productClass = Product.class;
        Test test = productClass.getAnnotation(Test.class);

        Product product = new Product(test.name(), test.price());
        System.out.println(product.getName());
        System.out.println(product.getPrice());
    }
}
Product.java(テスト対象クラス)
@Test(name = "cookie", price = 150)
public class Product {
    private final String name;
    private final int price;

    public Product(String name, int price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public int getPrice() {
        return price;
    }
}
Test.java(アノテーション定義)
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

// アノテーション用のメタ・アノテーションRetention
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
    String name() default "example";
    int price() default 100;
}
@Retention
  • Retentionアノテーションは注釈パラメータとして情報の保持期間を指定するjava.lang.annotation.RetentionPolicyを受け取る

列挙型RetentionPolicyの列挙子

列挙子 概要
SOURCE アノテーションに関する情報をコンパイル時に破棄する
CLASS アノテーションに関する情報を実行時に破棄する
RUNTIME アノテーションに関する情報を実行時まで保持する

通常の使い方では実行時に必要なのでRUNTIMEを指定する

@Target
  • @Targetアノテーションを利用するとアノテーションに対し、付与できる対象を指定することができる
  • @Targetアノテーションは付与できるタイミングを指定するjava.lang.annotation.ElementTypeを注釈パラメータとして受け取る
  • @Targetなしの場合すべて対象になる
  • 複数指定したい場合注釈パラメータを配列で渡す

列挙型ElementTypeの列挙子

列挙子 対象
ANNOTATION_TYPE アノテーション
CONSTRUCTOR コンストラクタ
FIELD フィールド
LOCAL_VARIABLE ローカル変数
METHOD メソッド
MODULE モジュール
PACKAGE パッケージ
PARAMETER メソッドの引数
TYPE クラス、インタフェース、Enum
TYPE_PARAMETER 型パラメータ
TYPE_USE 型の仕様

例外とアサーション

  • プログラムが対処できるトラブルは「例外」=Exception、プログラムで対処できないトラブルは「エラー」=Error
  • 独自例外ではExceptionを継承する

try-with-resource

  • AutoCloseableインタフェースまたはサブインタフェースCloseableを実装したクラスはtry-with-resourceで自動的にcloseするリソースとして利用することができる
  • 宣言はブロックの外でもいいが、実質的finalでなければいけない(参照の書き換えをしてはいけない)
  • ;区切りで複数のリソースを指定できる
  • 宣言の逆順で閉じる
  • catch, finallyをつけた場合、closecatchfinallyの順で実行される
BufferedReader br = Files.newBufferedReader(path);
BufferedWriter bw = Files.newBufferedWriter(path, StandardOpenOption.APPEND);
try (br; bw) {
    bw.write(br.read());
    bw.newLine();
}
  • close()メソッド内で発生した例外はcatchブロックでキャッチされる
  • ただし、tryブロック内で例外発生し、close()メソッド内でも例外発生した場合、tryブロック内の例外に隠され抑制される
CloseableImpl(リソースクラス)
public class CloseableImpl implements AutoCloseable {
    @Override
    public void close() throws Exception {
        System.out.println("close");
        throw new Exception("Exception in close");
    }

    public void exec() throws Exception {
        System.out.println("exec");
        throw new Exception("Exception in exec");
    }
}
Main.java(リソースを利用するクラス)
public class Main {
    public static void main(String[] args) {
        CloseableImpl c = new CloseableImpl();
        try (c) {
            c.exec();
        } catch (Exception e) {
            System.out.println("catch");
            System.out.println(e);
        } finally {
            System.out.println("finally");
        }
    }
}

main()の実行結果

コンソール出力
exec
close
catch
java.lang.Exception: Exception in exec
finally

抑制された例外の取得するにはキャッチした例外の.getSuppressed()メソッドを呼ぶ必要がある

Main.java(catchブロックの処理を変更)
public class Main {
    public static void main(String[] args) {
        CloseableImpl c = new CloseableImpl();
        try (c) {
            c.exec();
        } catch (Exception e) {
            System.out.println("catch");
            System.out.println(e);
            // .getSuppressed()はThrowableの配列を返す
            for (Throwable t : e.getSuppressed()) {
                System.out.println(t);
            }
        } finally {
            System.out.println("finally");
        }
    }
}

↓実行結果

コンソール出力
exec
close
catch
java.lang.Exception: Exception in exec
java.lang.Exception: Exception in close
finally

ローカライズ

java.util.Localeは地域や言語などの「ロケール情報」を扱うクラス
「ロケール情報」は「i18n(internationalization)」を実現するためにあり、地理的・政治的・文化的な特定の地域を表す

ロケールの基本的な使い方
https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/Locale.html

デフォルトのロケール取得

Main.java
import java.util.Locale;

public class Main {
    public static void main(String[] args) {
        Locale locale = Locale.getDefault();
        System.out.println(locale.getCountry());
        System.out.println(locale.getLanguage());
    }
}

いくつかのロケール生成方法

// デフォルト
Locale locale = Locale.getDefault();
// 定数
locale = Locale.JAPAN;
// コンストラクタで生成する場合、第1引数が言語、第2引数が国、第3引数が派生情報
locale = new Locale("ja", "JP", "JP");
// ビルダー
locale = new Locale.Builder()
        .setLanguage("jp")
        .setRegion("JP")
        .build();
// FETF言語タグによるファクトリメソッド
locale = Locale.forLanguageTag("ja-JP-u-ca-japanese-x-lvariant-JP");

言語コードの例
https://ja.wikipedia.org/wiki/ISO_639-1%E3%82%B3%E3%83%BC%E3%83%89%E4%B8%80%E8%A6%A7

言語 コード
日本語 ja
英語 en
ドイツ語 de
中国語 zh
フランス語 fr

国コードの例
https://ja.wikipedia.org/wiki/ISO_3166-1

コード
日本 JP
アメリカ US
ドイツ DE
中国 CN
フランス FR

文字列による文字コード、国コードの指定は安全でないので、定数で用意されているものは定数を利用
Locale.JAPANESEは日本語、Locale.JAPANは日本語+日本

2022-02-25 22.06.41.png

プロパティファイル

java.util.Propertiesは実行環境ごとの設定値などを別ファイルとして書き出した際に設定情報を読み出して扱うためのクラス

Main.jp
public class Main {
    public static void main(String[] args) throws IOException {
        // Propertiesのload()メソッドにリーダーを渡す
        Properties prop = new Properties();
        prop.load(new FileReader("default.properties"));

        // PropertiesはforEach()を持っている
        prop.forEach((k, v) -> System.out.println(k + ": " + v));

        // keySet()からはObjectのSetが返される
        Set<Object> keys = prop.keySet();
        for (Object key: keys) {
            // Objectをキーとして取得する際はget()
            System.out.println(key + ": " + prop.get(key));
        }

        // list()はPrintStreamを渡すことで簡単にPropertiesの情報を出力できる
        prop.list(System.out);

        // 文字列から取得する際はgetProperty()
        String logLevel = prop.getProperty("LOG_LEVEL");
        String language = prop.getProperty("LANGUAGE");
    }
}
default.properties
LOG_LEVEL=INFO
LANGUAGE=JP

java.util.ResourceBundleで読み込むこともできる

// ファイル名は基底名(拡張子なし)で指定
ResourceBundle resource = ResourceBundle.getBundle("default");
System.out.println(resource.getString("LOG_LEVEL"));
System.out.println(resource.getString("LANGUAGE"));

ResourceBundleはファイル名に言語コードや国コードを含めるとロケールに合わせたプロパティファイルを自動で選択してくれる

ファイル構成例
.
└── src
    ├── com
    │   └── example
    │       └── Main.java
    ├── default_en_US.properties
    └── default_ja_JP.properties

日付フォーマット

Java8から追加されたDateAPIはjava.time.format.DateTimeFormatterで日付や時刻のフォーマットを指定できる。

Main.java
import java.io.IOException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.Locale;

public class Main {
    public static void main(String[] args) throws IOException {
        Locale locale = Locale.JAPAN;
        LocalDateTime now = LocalDateTime.now();
        LocalDate today = LocalDate.now();
        System.out.println(
                now.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM).withLocale(locale))
        );
        System.out.println(
                now.format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT).withLocale(locale))
        );
        System.out.println(
                today.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL).withLocale(locale))
        );
        System.out.println(
                today.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG).withLocale(locale))
        );
        System.out.println(
                today.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM).withLocale(locale))
        );
        System.out.println(
                today.format(DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT).withLocale(locale))
        );
    }
}
実行結果
2022/02/27 1:20:46
2022/02/27 1:20
2022年2月27日日曜日
2022年2月27日
2022/02/27
2022/02/27

モジュール・システム

Java9からパッケージを分類する仕組みとして「モジュール・システム」が導入された
モジュールではパッケージ単位のアクセス制御ができる

モジュール利用のメリット

  • 公開されたクラスにしかアクセスできないためセキュリティが高まる
  • モジュール間の依存関係を明確化できる
  • プラットフォーム間の整合性が向上する
  • 柔軟な実行環境を実現できる
  • 最適化されたモジュール構成によってパフォーマンスが向上する

モジュールの設定は「module-info.java」に定義する、この設定ファイルによってパッケージ間の依存関係が明確になる

module-info.java
// com.sample.Testモジュールでcom.sample.serviceパッケージのみ公開
module com.sample.Test {
    exports com.sample.service;
}

モジュール定義のブロック内にはディレクティブを記述する

ディレクティブ 概要
exports 公開するパッケージを指定
requires 利用する別のモジュールを指定
opens リフレクションを許可するパッケージを指定
to モジュール, モジュール」を付けることで許可先のモジュールを制限できる
モジュールのシグネチャにも書ける
uses SPIとして利用するインタフェースを指定、SPIの利用側モジュールに記述
provides SPIとなるインタフェースとその実装クラスを指定、SPIの実装側モジュールに記述

モジュールパス

  • モジュール・システムを利用する際の必要なモジュールの配置先、javacコマンドやjavaコマンドのオプションとして--module-pathで指定する
  • コンパイラやJVMは指定された場所を検索しに行く
  • Java8以前のクラスパスにあたる、Java9以降はモジュールパスの利用を推奨

モジュールの種類

ディレクティブ 概要
名前付きモジュール モジュール定義ファイル(module-info.java)でモジュール名が定義されているモジュール
自動モジュール モジュール定義ファイルを持たないjarファイル
無名モジュール クラスパス上に配置されたjarファイル、名前付きモジュールから参照できない

モジュール・システムへの移行

既存のアプリケーションをモジュール・システムへ移行する際は適切に依存関係を解決する必要がある

移行には2つの方式がある

移行方式 概要
ボトムアップ移行 依存されている側から順に名前付きモジュールに変換していく方法
トップダウン移行 依存されている側のモジュールは一旦自動モジュールにし、依存する側から順に名前付きモジュールに変換していく方法

実際の移行は、下記のような流れになる

  1. 対象パッケージ内のどのパッケージが利用されているか調る
  2. 利用されているパッケージのみ公開するようにmodule-info.javaにモジュール宣言を追加する
  3. jarファイルを再作成する

モジュール作成

ファイル構成
.
├── classes
├── modules
└── src
    ├── client
    │   ├── com
    │   │   └── sample
    │   │       └── client
    │   │           └── Main.java
    │   └── module-info.java
    └── lib
        ├── com
        │   └── sample
        │       └── lib
        │           └── Lib.java
        └── module-info.java

↑の構成から実際にモジュールを作成する場合

# 依存先モジュール「lib」をコンパイル
javac -d classes/lib/ src/lib/module-info.java src/lib/com/sample/lib/Lib.java
# jarファイル作成
jar cvf modules/lib.jar -C classes/lib .
# 依存元モジュール「client」をコンパイル
# 依存先モジュールのjarがある場所を--module-pathで指定
javac --module-path modules -d classes/client/ src/client/module-info.java src/client/com/sample/client/Main.java
# jarファイル作成
jar cvf modules/client.jar -C classes/client .

Mainを実行する

// モジュールから実行する際も--module-pathオプションで場所を指定
java --module-path modules --module Client/com.sample.client.Main

依存関係逆転

  • 「依存関係逆元の原則(DIP:Dependency Inversion Principle)」とは柔軟なシステムを実現するための設計原則
  • アプリケーションのライブラリへの依存を解消するために、アプリケーションが利用する機能をインタフェースにまとめ、それに従ったライブラリを作成する
  • このようにインタフェースだけ事前に定義し、それに沿ったライブラリを提供する仕組みを「SPI:Service Provider Interface」という

SPIの利用時のmodule-info.javaは↓のように記述する

module-info.java(SPI利用側)
module app {
    exports app;
    uses app.Hello;  // app.HelloインタフェースをSPIとして利用
}
module-info.java(SPI実装側)
module lib {
    exports lib;
    requires app;
    provides app.Hello with lib.HelloImpl;  // app.HelloインタフェースをSPIとして実装
}

ServiceLoader

java.util.ServiceLoaderクラスはSPIを利用する際にクラスファイルをロードするのに利用する
SPIのインタフェースを指定することでその実装クラスを検索する

Main.java
import java.util.ServiceLoader;

public class Main {
    public static void main(String[] args) {
        // ProductというSPIを利用する場合
        for (Product p : ServiceLoader.load(Product.class)) {
            System.out.println(p.getPrice());
        }
    }
}

jdeps

jdepsコマンドはJDKに含まれており、クラスやモジュールの依存関係を調べるのに利用できる

jdeps jarファイルのパス

jdepsのオプション

$ jdeps -h
使用方法: jdeps <options> <classes...>
<classes>には、.classファイルのパス名、ディレクトリ、JARファイルまたは完全修飾
クラス名を指定できます。使用できるオプションは次のとおりです:
  -dotoutput <dir>                   DOTファイル出力の宛先ディレクトリ
  -s           -summary              依存性のサマリーのみ出力します
  -v           -verbose              クラス・レベルの依存性をすべて出力します
                                     -verbose:class -filter:noneと同等です。
  -verbose:package                   パッケージ・レベルの依存性を出力します
                                     (デフォルトでは、同じパッケージ内の依存性を除く)
  -verbose:class                     クラス・レベルの依存性を出力します
                                     (デフォルトでは、同じパッケージ内の依存性を除く)
  -cp <path>   -classpath <path>     クラス・ファイルを検索する場所を指定します
  -p <pkgname> -package <pkgname>    指定のパッケージ名に一致する依存性を検出します
                                     (複数回指定可能)
  -e <regex>   -regex <regex>        指定のパターンに一致する依存性を検出します
                                     (-pと-eは排他的)
  -f <regex>   -filter <regex>       指定のパターンに一致する依存性をフィルタします
                                     複数回指定された場合、最後のものが使用されます。
  -filter:package                    同じパッケージ内の依存性をフィルタします(デフォルト)
  -filter:archive                    同じアーカイブ内の依存性をフィルタします
  -filter:none                       -filter:packageおよび-filter:archiveのフィルタリングは行われません
                                     -filterオプションで指定したフィルタリングが引き続き適用されます。
  -include <regex>                   パターンに一致するクラスに分析を制限します
                                     このオプションを指定すると、分析対象クラスの
                                     リストがフィルタされます。パターンを依存性に
                                     適用する-pおよび-eと一緒に使用できます
  -P           -profile              プロファイル、またはパッケージを含むファイルを表示します
  -apionly                           分析をAPI、つまり、パブリック・クラスの
                                     パブリック・メンバーおよび保護されたメンバーの
                                     署名における依存性(フィールド・タイプ、メソッド・
                                     パラメータ・タイプ、戻されたタイプ、チェックされた
                                     例外タイプなど)に制限します
  -R           -recursive            すべての依存性を再帰的にトラバースします。
                                     -Rオプションは-filter:noneを意味します。-p、-e、-f
                                     オプションが指定されている場合、一致する依存性のみ
                                     分析されます。
  -jdkinternals                      JDK内部APIのクラスレベルの依存性を検出します。
                                     デフォルトでは、-includeオプションを指定しないと、
                                     -classpathのすべてのクラスと入力ファイルを分析します。
                                     このオプションは-p、-eおよび-sオプションと一緒に使用できません。
                                     警告: JDK内部APIは、次のリリースでアクセスできなくなる可能性が
                                     あります。
  -version                           バージョン情報

セキュアコーディング

セキュアコーディングで重要なトップ10

  1. 入力を検証する
  2. コンパイラからの警告に注意を払う
  3. セキュリティポリシーを実装するよう設計する
  4. シンプルにする
  5. 拒否をデフォルトにする
  6. 最小権限の原則に従う
  7. 他のシステムに送信するデータを無害化する
  8. 多重防御を実践する
  9. 効果的な品質保証テクニックを利用する
  10. セキュアコーディング標準を採用する

文字列の正規化

入力の検証のために受け取った文字列にはまず「Unicodeの正規化(normalization)」を行う
java.text.Normalizerクラスは文字列正規化のためのクラス

String result = Normalizer.normalize(de, Normalizer.Form.NFKC);

整数オーバーフロー対策

数値の演算により、表現可能な範囲を超えることでエラーを発生させる「整数オーバーフロー攻撃」という脅威がある
これにはいくつかの対策方法がある

対策方法 概要
事前条件テスト 入力値が演算可能な範囲かどうかを演算の前にチェックする
アップキャスト より大きな数値を扱える型(short→int→long等)に変換する
BigIntegerクラスを使う BigIntegerは動的にメモリを確保するためオーバーフローしない、ただし通常の整数型よりパフォーマンスは落ちる

システムに関する情報を見せない

機密情報はもちろんだが、そうでない情報でも
下記のような情報はデバッグ時や開発時は必要かもしれないが、ユーザにはできるだけ見えないようにし、速やかに削除させることで、OS・ミドルウェアやファイルシステム・アプリケーションの構成から攻撃の手がかりを見つけられることを避ける

  • Javaのスタックトレース
  • ミドルウェアが出力するOSやミドルウェアのバージョンなどの情報
  • ファイルシステムの構成

Dos攻撃対策

JVMに負荷をかける方法として

  • 大きなファイルやデータを読み込ませる
  • ファイルオープンやDBMSへの接続を増やす

などがある、このため

  • ファイルを開く処理は対象のファイルサイズをチェックする
  • ファイルオープンやDBMSへの接続は例外発生時や一定直でクローズするようにする

などの対策が必要

SecurityManager

Java標準のセキュリティ機構としてjava.lang.SecurityManagerがある
SecurityManagerは下記の1文を追加することでJVMに登録され、有効になる

System.setSecurityManager(new SecurityManager());

↑の代わりにjavaコマンドのオプションでも有効にできる

java -Djava.security.manager Main

セキュリティポリシーを変更するにはjava.security.Policyクラスとjava.security.Permissionsクラスを利用するか、.policyファイルを作成しjavaコマンドのオプションで指定する

readOnly.policy
// lib.jarのみに権限を付与
grant codeBase "file:/Users/xxx/workspace/project/lib.jar" {
    // "/"ディレクトリへの読み込みを許可
    permission java.io.FilePermission "/" "read";
}
java FileRead -cp .:lib.jar -Djava.security.manager -Djava.security.policy=readOnly.policy

ライブラリなどを呼び出す際に呼び出す側の権限に関わらず呼び出された側の権限で実行できるようにする仕組みとして「特権ブロック」がある

java.security.AccessControllerクラスのdoPrivilegedメソッドにjava.security.PrivilegedActionインタフェースの実装クラスを渡す

可変性

  • 可変(Mutable)オブジェクト: フィールドの値が変わる事があるクラス
  • 不変(Immutable)オブジェクト: フィールドの値が変わらないクラス

下記のような実装で不変オブジェクトを利用することでセキュリティ強度が高まる

  • 継承できないようクラスをfinalにする
  • パッケージ外からインスタンス化できないようコンストラクタは修飾子なしかprivateにする
  • コンストラクタで受け取ったオブジェクトをそのまま代入しない
  • ビルダーを用意する
  • setterを提供しない
  • getterはコピーした別の参照を返す

シリアライズ・プロキシ

デシリアライズ時はコンストラクタが利用されず、デシリアライズによる不正なインスタンスの生成が行われる可能性がある
デシリアライズによるバグやセキュリティ上の問題を防ぐための手法として「シリアライズ・プロキシ」パターンがある

  1. シリアライズ対象のクラスにstaticインナークラスを追加する
  2. エンクロージングクラスにwriteReplaceメソッドを追加する
  3. staticインナークラスにreadResolveメソッドを追加する
  4. エンクロージングクラスにreadObjectメソッドを追加する
  • シリアライズ時にwriteReplaceメソッドが呼び出されるので、writeReplaceメソッドにはstaticインナークラスのインスタンスを返すよう書いておく
  • デシリアライズ時にreadResolveメソッドが呼び出されるので、エンクロージングクラスのインスタンスを返すように書いておく
  • エンクロージングクラスを直接デシリアライズしようとするとreadObjectメソッドが呼ばれるので、例外を創出するように書いておく
4
3
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
4
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?