この記事について
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は国際資格ではない。
BronzeとSilverは前提条件なし、Goldは2つ前までのバージョンのSilver保持、または3つ前までのバージョンのGoldの保持が前提条件。
参考:Oracle Java SE 認定資格パス
180分80問で合格ラインはアップグレードの方が少し低く61%、合格率は非公開
参考:認定試験一覧 | オラクル認定資格制度 | 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の予定らしい。
現在はGold SE17が「Comming soon」になっている。※2022.02現在
SE8 Goldの認定試験がリリースされたのが2015秋頃、SE11は2019夏頃ということを考えるとだいたい3〜4年周期で認定資格も新しいバージョンがリリースされるようだ。
認定パスにもあったように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(無償)、いつも開発に使ってるやつ。
問題集で出てきたコードとか実際に書いて動かしてみるのに利用。
勉強時間
約55時間
「徹底攻略Java SE 11 Gold問題集」の解答/解説を読みながら、気になったところは公式のリファレンスも読みながら実際にコードを書いて動かしてみて、自分なりにノートとしてまとめるのを繰り返した。
最後の総仕上げ問題まで終わった時点で大体50時間ぐらい、あとはノートを見ながら理解の怪しいところをメインに復習して終わり。
結果
合格ライン 61% に対し正解率 69%
80問 出題なので 55問 正解?思ったよりわからない問題も多かった。
今回の反省点としては、主に下記の3点が大きかった。
・クラス、インタフェースの制約についての理解が浅かった。
・型推論(var
)についてほぼ勉強していなかった。※これに関しては問題集でもほぼ触れていなかった、公式の出題範囲にも書かれていないのが不思議
・モジュールシステム(特にコマンド)についての理解が浅かった。
今回の出題割合だが、多かった順に
- ストリームAPIと関数型インタフェース:やはりSE8以降の目玉アップデートなので一番出題が多かった。
- モジュールシステム:SE9で追加された機能の目玉?これもそれなりに多かった。
- NIO2:これはSE7の追加機能だが、ファイルIOはよく使うからかそこそこ出た。
-
型推論(
var
):これもSE10で追加されたからかそこそこ出た。
あとは試験範囲に書かれている内容が全体的に少しずつ。
アップグレード試験は初めて受験したので、本当にアップグレードじゃない方(1Z0-816-JPN)と同じ内容が出るのか少し不安だったが、おそらく同じ内容で少し合格ラインがゆるいだけ?
また2、3個先のバージョン(SEのバージョンとしては12~18個先?)がでたらアップグレードしようかなと思います。
勉強ノート
クラスとインタフェース
クラスのネスト
概要 | 特徴 | |
---|---|---|
インナークラス | クラスの中に定義したクラス | エンクロージングクラスとインナークラス2つのインスタンス化が必要 |
staticインナークラス | クラスの中に定義したstaticクラス | インスタンス化は不要、非staticなエンクロージングクラスの中にある場合、エンクロージングクラスのメンバを参照できない |
ローカルクラス | メソッドの中に定義したクラス | そのクラスを定義したメソッドのローカル変数を扱う場合、先に定義する必要がある。このローカル変数は実質的finalになる。 |
匿名クラス | メソッドの中に定義した名前のないクラス | Interfaceの実装やクラスの継承を名前のないクラスとして宣言する。他のメソッドやクラスと共有しない一時的な実装を行いたい場合などに利用する。オーバーライドや独自メソッドの追加ができる。コンストラクタは定義できない。 |
エンクロージングクラス | ネストしたクラスを囲むクラス |
- インナークラスは関連するクラスをまとめたり、カプセル化し、クラス外部から扱えなくするなどの目的で利用する。
- staticインナークラスから非staticなエンクロージングクラスのメンバにはアクセスできない
- 非staticなインナークラスにstaticメンバは定義できない
匿名クラスの初期化子
匿名クラスにコンストラクタは存在しない、代わりに「初期化子」を利用する。
{}
で処理を囲うとインスタンス生成時に実行される。
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クラスのコンストラクタが受け取る関数型インタフェース |
サブスレッドを定義し、
public class SubThread extends Thread {
@Override
public void run() {
System.out.println("sub");
}
}
メインスレッドの中でサブスレッドを作成
public class Main {
public static void main(String[] args) {
Thread t = new SubThread();
t.start();
System.out.println("main");
}
}
↓匿名クラスとラムダ式に置き換えることもできる。
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() |
ExecutorService とScheduledExecutorService の親インタフェース |
java.util.concurrent | ExecutorService |
submit() |
submit() で渡された処理をスレッドプールで実行する。 |
java.util.concurrent | ScheduledExecutorService |
schedule() scheduleAtFixesRate() scheduleWithFixedDelay()
|
一定時間待機したり、一定時間の周期で渡された処理をスレッドプールで実行する。 |
java.util.concurrent | Executors |
newSingleThreadExecutor() newFixedThreadPool()
|
ExecutorService とScheduledExecutorService の実装を返すファクトリメソッドを持つ |
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
ExecutorService
やScheduledExecutorService
にタスクを登録するメソッドはFuture
を返す。
並列処理の各結果を受け取りたい場合、Future
のList
などで受け取る。
その場合、Runnable
だと戻り値を返せないのでCallable
をExecutorService
のsubmit()
などに渡して登録する。
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
がスローされる。実際に発生した例外を取り出したいときは、ExecutionException
のgetCause()
メソッドを利用する。
同期化処理
java.util.concurrent.CyclicBarrier
クラスを利用すると複数のスレッドが特定のポイントまで到達するのを待機させることができる。
- バリアー:複数スレッドの待ち合わせポイント
- バリアーアクション:待ち合わせ後に実行される処理
バリアーの利用例
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);
↑は↓の処理に相当する(実際はストリームなので順次実行ではない)
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 |
File
とFileFilter
を使ったファイル一覧の出力
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」とは関係ない) -
BufferedInputStream
やBufferedReader
は読み込んだデータをバッファに溜め込むことでIOを減らす。それ単体で入力の機能はなく、FileReader
などと合わせて利用する。 -
FileReader
は1文字ずつ読み込み、終端で-1を返す。BufferedReader
は1行ずつ読み込み、終端でnull
を返す。
ストリームの基底クラス
文字ストリーム | バイトストリーム | |
---|---|---|
入力 | java.io.Reader |
java.io.InputStream |
出力 | java.io.Writer |
java.io.OutputStream |
FileReader
とBufferedReader
を使ったテキストファイルの読み込み
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 append
にtrue
を渡すと追記モード
FileWriter
とBufferedWriter
を利用したテキストファイルの書き込み
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
はインスタンスをシリアライズしてファイルに出力するためのクラス -
transient
やstatic
で就職されたフィールドはシリアライズに含まれない -
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.Path
とjava.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.newBufferedWriter
はPath
を引数に渡すことでBufferedReader
、BufferedWriter
の生成を簡潔に書ける。
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
もある
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.sql
やjavax.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
しか扱えないクラス
public class Item {
private Integer value;
public Item (Integer value) {
super();
this.value = value;
}
public Integer getValue() {
return value;
}
}
↓ジェネリクスを利用することで汎用的な型を扱えるようにできる
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
をつけない場合T
がhello()
メソッドを持っている保証がないためコンパイルエラー
型パラメータのワイルドカード
- 型パラメータを不定にしたい場合、ワイルドカード「
<?>
」を利用することもできる
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.Queue
とjava.util.Deque
で実現できる
- キュー(queue):FIFO
- デック(deque):両端キュー(double ended queue)
Setインタフェース
Setは重複排除、順序保証なし
Setインタフェースの実装クラスとして主に2つ
クラス | 特徴 |
---|---|
HashSet |
順序を保証しないので速い |
TreeSet |
自然順序または引数のComparator でソート |
Map.Entry
Map
インタフェースはインナーインタフェースEntry
を持つ
Entry
はMap
の1要素を扱うのに利用する
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以降はMap
がforEach
を持つので↑このコードは↓のように置き換えられる
Map.forEach()
は内部でEntry
を取り出している
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
アノテーション
クラスやフィールド、メソッド、コンストラクタなどに意味を追加するマーカーの役割をする
アノテーションを付与されたクラスを利用するクラスに対して値を渡すこともできる
アノテーションの定義
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つの場合名前を省略できる
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;
}
}
アノテーション定義でデフォルト値を設定することもできる、デフォルト値なしで引数なしの場合コンパイルエラー
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
)でクラスの定義情報を取得できる - この機能を使いアノテーションから値を取得し、利用する
- この時、アノテーションにもアノテーションを付ける必要がある、これを「メタ・アノテーション」という
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());
}
}
@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;
}
}
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
をつけた場合、close
→catch
→finally
の順で実行される
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
ブロック内の例外に隠され抑制される
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");
}
}
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()
メソッドを呼ぶ必要がある
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
デフォルトのロケール取得
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
は日本語+日本
プロパティファイル
java.util.Properties
は実行環境ごとの設定値などを別ファイルとして書き出した際に設定情報を読み出して扱うためのクラス
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");
}
}
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から追加されたDate
APIはjava.time.format.DateTimeFormatter
で日付や時刻のフォーマットを指定できる。
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」に定義する、この設定ファイルによってパッケージ間の依存関係が明確になる
// 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つの方式がある
移行方式 | 概要 |
---|---|
ボトムアップ移行 | 依存されている側から順に名前付きモジュールに変換していく方法 |
トップダウン移行 | 依存されている側のモジュールは一旦自動モジュールにし、依存する側から順に名前付きモジュールに変換していく方法 |
実際の移行は、下記のような流れになる
- 対象パッケージ内のどのパッケージが利用されているか調る
- 利用されているパッケージのみ公開するように
module-info.java
にモジュール宣言を追加する - 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 app {
exports app;
uses app.Hello; // app.HelloインタフェースをSPIとして利用
}
module lib {
exports lib;
requires app;
provides app.Hello with lib.HelloImpl; // app.HelloインタフェースをSPIとして実装
}
ServiceLoader
java.util.ServiceLoader
クラスはSPIを利用する際にクラスファイルをロードするのに利用する
SPIのインタフェースを指定することでその実装クラスを検索する
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
- 入力を検証する
- コンパイラからの警告に注意を払う
- セキュリティポリシーを実装するよう設計する
- シンプルにする
- 拒否をデフォルトにする
- 最小権限の原則に従う
- 他のシステムに送信するデータを無害化する
- 多重防御を実践する
- 効果的な品質保証テクニックを利用する
- セキュアコーディング標準を採用する
文字列の正規化
入力の検証のために受け取った文字列にはまず「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
コマンドのオプションで指定する
// 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
はコピーした別の参照を返す
シリアライズ・プロキシ
デシリアライズ時はコンストラクタが利用されず、デシリアライズによる不正なインスタンスの生成が行われる可能性がある
デシリアライズによるバグやセキュリティ上の問題を防ぐための手法として「シリアライズ・プロキシ」パターンがある
- シリアライズ対象のクラスに
static
インナークラスを追加する - エンクロージングクラスに
writeReplace
メソッドを追加する -
static
インナークラスにreadResolve
メソッドを追加する - エンクロージングクラスに
readObject
メソッドを追加する
- シリアライズ時に
writeReplace
メソッドが呼び出されるので、writeReplace
メソッドにはstatic
インナークラスのインスタンスを返すよう書いておく - デシリアライズ時に
readResolve
メソッドが呼び出されるので、エンクロージングクラスのインスタンスを返すように書いておく - エンクロージングクラスを直接デシリアライズしようとすると
readObject
メソッドが呼ばれるので、例外を創出するように書いておく