本稿ではOracle試験のJava Gold(Java SE 11 Programmer II, 1Z0-816-JPN)で必要とされる知識のうち見直すべきポイントをまとめます。
クラスとインタフェース
ネストクラスを構造化せよ
クラスの種類 | 説明 | ||
---|---|---|---|
ネストクラス | staticネストクラス | クラスに定義されたstaticなクラス | |
非staticネストクラス (インナークラス) | メンバークラス | クラスに定義されたstaticでないクラス | |
ローカルクラス | クラスのメソッド内に定義したクラス | ||
匿名クラス | 定義とインスタンス化を同時記述したローカルクラス |
ローカルクラスの「ローカル」はローカル変数のローカルと同義と捉えると分かりやすいです。なお,本稿ではネストしたクラスは略して「ネストクラス」と書きます。
インナークラスとは何か
非staticなネストクラス
インナークラスの中にstaticなフィールドは定義できないため注意してください。
ネストクラスとアクセス修飾子の関係をまとめよ
ローカルクラスや匿名クラスにはアクセス修飾子は付けない。これは通常のクラスにおけるデフォルトのアクセス修飾子とは意味合いが異なる。ローカルクラスは定義されたメソッド内でのみ使われるため。
public | protected | 指定なし | private | |
---|---|---|---|---|
staticネストクラス | $\cm$ | $\cm$ | $\cm$ | $\cm$ |
メンバークラス | $\cm$ | $\cm$ | $\cm$ | $\cm$ |
ローカルクラス | - | - | - | - |
匿名クラス | - | - | - | - |
ネストクラスとabstract/static/finalの関係をまとめよ
static | abstract | final | |
---|---|---|---|
staticネストクラス | $\cm$ | $\cm$ | $\cm$ |
メンバークラス | $\cm$ | $\cm$ | $\cm$ |
ローカルクラス | - | $\cm$ | $\cm$ |
匿名クラス | - | - | - |
ローカルクラスにstaticが付けられないのは,そのような定義であると言えばそれまでなのですが,定義されているメソッドのスコープに依存するからです。ローカルクラスはメソッド内でのみ有効であり,static修飾子を付ける意味はありません。
staticネストクラスの定義と呼び出し方をまとめよ
// 定義
class Outer {
static class Inner {}
}
// 呼び出し
Outer.Inner inner = new Outer().Inner();
// staticなのでOuterは省略可能
Inner inner = new Inner();
ローカルクラスの定義と呼び出し方をまとめよ
// 定義
class Outer {
void method() {
class Inner {}
}
}
// 呼び出し
Outer.Inner inner = new Outer().new Inner();
// 型宣言のOuterは省略可能
Inner inner = new Outer().new Inner();
匿名クラスの定義と呼び出し方をまとめよ
// 定義
class Outer {
void method() {
new Inner() {}
}
}
// 呼び出し
Outer.Inner inner = new Outer().new Inner();
// 型宣言のOuterは省略可能
Inner inner = new Outer().new Inner();
staticでないネストクラスにstaticメンバを定義すると何が起きるか
コンパイルエラー
ローカルクラスから参照するローカル変数について注意点を述べよ
ローカル変数は実質的にfinalでないといけない点。
ローカルクラスとローカル変数はメソッド実行時にインスタンス化されてメモリ上に載せられるが,ローカル変数はメソッド終了時にメモリから削除されてしまう。この状態でメモリ上に生き続けているローカルクラスからローカル変数を参照するとエラーが起きてしまうため,メソッド実行時にローカルインスタンスにローカル変数のコピーを持たせる方法を採用している。ローカル変数が実質的にfinalでないとローカルインスタンス上に持たせたコピーとローカル変数の実態が乖離してしまうため,ローカル変数は実質的にfinalでないといけないという制約が生じている。
匿名クラスで新しいメソッドをオーバーロードする場合の注意点を述べよ
型宣言でvarを利用する点。
public class ObjectSample {
public static void main(String[] args) {
// Object型にはtestメソッドは存在しないため型推論に失敗
Object obj = new Object() {
public void test() {}
}
// 代わりにvarを使って型推論する
var obj = new Object() {
public void test() {}
}
}
}
匿名クラスでコンストラクタを定義する際の注意点を述べよ
匿名クラスの用途に立ち返れば,匿名クラスではそもそもコンストラクタは不要といえる点。無理やり匿名クラスでコンストラクタを定義したい場合は,匿名クラスは名前を持たないため初期化子{}
を用いて初期化する。
public class ListSample {
public static void main(String[] args) {
List<String> = new ArrayList<String>() {
{
super.add("initial")
}
};
}
}
interfaceのstaticメソッドについて注意点を述べよ
staticメソッドが定義されたインタフェースからしか呼び出せない点。通常の抽象メソッドであればインタフェースを継承したクラスでも呼び出すことができるが,staticメソッドの場合は定義されたインタフェース経由で呼び出さなくてはいけない。
この挙動はstaticの役割とも矛盾しないため覚えやすいでしょう。
staticメンバの上書きについて整理せよ
staticメンバは上書きされない。
- staticなクラス変数:各クラスに紐づく形で定義される。上書きされない。
- staticなメソッド:各クラスに紐づく形で定義される。オーバーライドされない。
public abstract class Sample {
static int x = 0;
static void test() {
System.out.println("A");
}
}
public class Main {
public class SampleImpl extends Sample {
// SampleImplに紐づくstaticなフィールド
static int x = 10;
// SampleImplに紐づくstaticなメソッド
static void test() {
System.out.println("B");
}
// staticなメソッドはオーバーライドできないためコンパイルエラー
@Override
static void test() {
System.out.println("C");
}
}
public static void main(String[] args){
System.out.println(Sample.x); // 0
System.out.println(SampleImpl.x); // 10
Sample.test(); // A
SampleImpl.test(); // B
}
}
インタフェースや抽象クラスに限らず通常のクラスでも同様の挙動となります。
interfaceの実装クラスからデフォルトメソッドを呼び出す際の注意点を述べよ
実装元interfaceのA,Bに定義されたデフォルトメソッドtest()を呼び出す場合は下記を用いる。itnerfaceは多重実装が許容されているため,実装元が1つの場合でもsuperの前の指定が必要であるため注意する。
interface A {
default void test() {
System.out.println("A")
}
}
interface B {
default void test() {
System.out.println("B")
}
}
public class Impl implements A, B {
@Override
public void test() {
A.super.test(); // A
B.super.test(); // B
}
}
A.superなので「Aの親クラス」と捉えてしまいがちだが,あくまでもsuperの識別を行っているだけと理解する。
同一のシグネチャをもつ抽象クラスとインタフェースを同時に継承・実装すると何が起きるか
コンパイルエラー。
ENUMをswitchする記述方法を述べよ
public enum Colors {
RED, BLUE, GREEN;
}
public class Sample {
public static void main(String[] args) {
Colors color = Colors.GREEN;
switch(color) {
case RED:
System.out.println("RED");
break;
case BLUE:
System.out.println("BLUE");
break;
case GREEN:
System.out.println("GREEN");
break;
}
}
}
enumの列挙子は""
で囲わずに変数チックに書きます。これはenumの列挙子はクラスとして扱われるからです。
Enum列挙子とインスタンスの関係について説明せよ
Enum列挙子はJavaではクラスとして扱われ,Enumが使われる際にJVMが1回だけインスタンス化する。これは内部的にstaticなメンバとして扱われるからである。
Enumのコンストラクタの挙動について説明せよ
Enumではコンストラクタを定義できるが,列挙子の数だけ実行される。
public enum Colors {
RED("red"), BLUE("blue"), GREEN("green");
private Colors(String lower) {
System.out.print(lower + " ");
}
}
public class Sample {
public static void main(String[] args) {
System.out.print(Colors.GREEN); // red blue green GREEN
}
}
Colors型のRED,BLUE,GREENというインスタンスが生成されるということです。
Enumのコンストラクタのアクセス修飾子について説明せよ
Enumの初期化はJVMのみに行わせるためprivateでなくてはならない。
staticネストクラスにおけるprivateメンバの扱いについて述べよ
staticネストクラスもOuterに属するメンバと捉えられるため,OuterからInnerのprivateメンバにアクセスできる。
public class Outer {
public static void main(String[] args) {
Inner inner = new Inner();
inner.data = 100; // OuterからInnerのprivateにアクセス可能
}
private static class Inner {
private int data;
}
}
ローカルクラスが参照する変数がクラス定義よりも後に宣言されていた場合はどうなるか
コンパイルエラー
ローカルクラス内のローカル変数は実質的にfinalであるが,メソッドの引数についてはどうか
メソッドの引数も実質的にfinal
public class Sample {
private Test test(String value) {
class A implements Test {
// 外側のvalueを参照するためvalueは実質的にfinalでなければならない
System.out.println(value);
}
// valueは実質的にfinalでなければならないためコンパイルエラー
value = "sample";
}
}
staticで修飾したメソッドを呼び出す際の注意点を述べよ
staticなメソッドが属しているインタフェースやクラス経由でしか呼び出せないこと
public interface A {
static void test() {}
}
public interface B extends A {}
public interface C implements A {}
public class D implements A {
public static void main(String[] args) {
// OK
A.test();
// NG
B.test();
C.test();
test();
}
}
staticなメソッドをOverrideすると何が起きるか
コンパイルエラー。インタフェースでもクラスでも同様。デフォルトメソッドはOverrideできるため注意する。こちらはA.super.test()のように継承元を指定するやつ。
インタフェースでprivate defaultなメソッドは不自然か
不自然。defaultは継承先に対するデフォルトメソッドを提供するのにprivateにしているという矛盾。
関数型インタフェースとラムダ式
Supplierの戻り値・メソッド名・引数を述べよ
表記 | 戻り値 | メソッド名・引数 |
---|---|---|
Supplier<T> | T | get() |
「何も受け取らずにただ提供(Supplier)するだけ」と覚えます。利用者はTをgetします。
Consumerの戻り値・メソッド名・引数を述べよ
表記 | 戻り値 | メソッド名・引数 |
---|---|---|
Consumer<T> | void | accept(T) |
「引数を受け取って消費するだけ」と覚えます。利用者はTの消費を受け入れます。
BiConsumerの戻り値・メソッド名・引数を述べよ
表記 | 戻り値 | メソッド名・引数 |
---|---|---|
BiConsumer<T, U> | void | accept(T, U) |
「Consumerの引数が2つ(Bi)になった」と覚えます。利用者はT,Uの消費を受け入れます。
Predicateの戻り値・メソッド名・引数を述べよ
表記 | 戻り値 | メソッド名・引数 |
---|---|---|
Predicate<T> | boolean | test(T) |
「述語(predicate)としてtrue/false出力する」と覚えます。利用者はtestします。
BiPredicateの戻り値・メソッド名・引数を述べよ
表記 | 戻り値 | メソッド名・引数 |
---|---|---|
BiPredicate<T, U> | boolean | test(T, U) |
「Predicateの引数が2つ(Bi)になった」と覚えます。利用者はtestします。
Functionの戻り値・メソッド名・引数を述べよ
表記 | 戻り値 | メソッド名・引数 |
---|---|---|
Function<T, R> | R | apply(T) |
「Tを引数,Rを戻り値とする関数(Function)」と覚えます。利用者はTをapplyしてRを得ます。
BiFunctionの戻り値・メソッド名・引数を述べよ
表記 | 戻り値 | メソッド名・引数 |
---|---|---|
BiFunction<T, U, R> | R | apply(T, U) |
「Functionの引数が2つ(Bi)になった」と覚えます。利用者はT,UをapplyしてRを得ます。
UnaryOperatorの戻り値・メソッド名・引数を述べよ
表記 | 戻り値 | メソッド名・引数 |
---|---|---|
UnaryOperator<T> | T | apply(T) |
「Functionの引数と戻り値が同じ(Unary)」と覚えます。利用者はTをapplyしてTを得ます。
BiaryOperatorの戻り値・メソッド名・引数を述べよ
表記 | 戻り値 | メソッド名・引数 |
---|---|---|
BinaryOperator<T> | T | apply(T, T) |
「Unaryの引数が2つ(Bi)になった」と覚えます。利用者はT,TをapplyしてTを得ます。
Runnableの戻り値・メソッド名・引数を述べよ
表記 | 戻り値 | メソッド名・引数 |
---|---|---|
Runnable | void | run() |
「引数も戻り値もなくただ走るだけ」と覚えます。利用者はrunするだけです。
Callableの戻り値・メソッド名・引数を述べよ
表記 | 戻り値 | メソッド名・引数 |
---|---|---|
Callable<V> | V | call() |
「callして受け取るだけ」と覚えます。利用者はcallするだけです。
Aのインスタンスを生成するメソッド参照(コンストラクタ参照)の記述方法を述べよ
A::new
メソッド参照で引数を与えたい場合どうすればよいか
メソッド参照では与えたシグネチャと一致するメソッドを自動で判別してくれるため,引数を与える必要はない。ただし,例えばendsWith("a")など特定の引数を与えるメソッドを参照する場合は引数を省略することはできない。
Supplierを使ってインスタンスを生成するコードを書け
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
public class Main {
public static void main(String[] args) {
// ラムダ式
Supplier<List<String>> supplier1 = () -> new ArrayList();
List<String> list1 = supplier1.get();
// コンストラクタ参照
Supplier<List<String>> supplier2 = ArrayList::new;
List<String> list2 = supplier2.get();
}
}
Consumerを使って与えた文字列を表示するコードを書け
import java.util.function.Consumer;
public class Main {
public static void main(String[] args) {
// ラムダ式
Consumer<String> consumer1 = (str) -> System.out.println(str);
consumer1.accept("Hello, consumer.");
// メソッド参照
Consumer<String> consumer2 = System.out::println;
consumer2.accept("Hello, consumer.");
}
}
BiConsumerを使ってmapにkeyとvalueを設定するコードを書け
import java.util.function.BiConsumer;
public class Main {
public static void main(String[] args) {
var map = new HashMap<String Integer>();
// ラムダ式
BiConsumer<String, Integer> biConsumer1 = (k, v) -> map.put(k, v);
biConsumer1.accept("apple", 2);
biConsumer1.accept("banana", 3);
// メソッド参照
BiConsumer<String, Integer> biConsumer2 = map::put;
biConsumer2.accept("apple", 2);
biConsumer2.accept("banana", 3);
}
}
Predicateを使って与えた文字列がaで始まりbで終わるかどうかを判定せよ
import java.util.function.Predicate;
public class Main {
public static void main(String[] args) {
Predicate<String> predicatePrefix = (str) -> str.startsWith("a");
Predicate<String> predicateSuffix = (str) -> str.endsWith("e");
Predicate<String> predicateAnd = predicatePrefix.and(predicateSuffix);
Predicate<String> predicateOr = predicatePrefix.or(predicateSuffix);
System.out.println(predicateAnd.test("apple")); // true
System.out.println(predicateAnd.test("orange")); // false
System.out.println(predicateOr.test("apple")); // true
System.out.println(predicateOr.test("orange")); // true
}
}
Predicateを使って与えた文字列が特定の文字で終わるかどうかを判定せよ
import java.util.function.BiPredicate;
public class Main {
public static void main(String[] args) {
BiPredicate<String, String> biPredicate = (str, suffix) -> str.endsWith(suffix);
System.out.println(biPredicate.test("apple", "a")); // false
System.out.println(biPredicate.test("apple", "e")); // true
}
}
Functionを使って$2$の加算と$2$の乗算を連続して行いなさい
import java.util.function.Function;
public class Main {
public static void main(String[] args) {
Function<Integer, Integer> plus = (x) -> x + 2;
Function<Integer, Integer> times = (x) -> x * 2;
System.out.println(plus.andThen(times).apply(5)); // (5 + 2) * 2 = 14
System.out.println(plus.compose(times).apply(5)); // (5 * 2) + 2 = 12
}
}
BiFunctionを使ってある値の加算を行いなさい
import java.util.function.BiFunction;
public class Main {
public static void main(String[] args) {
BiFunction<Integer, Integer, Integer> plus = (x, y) -> x + y;
System.out.println(plus.apply(5, 2)); // 5 + 2 = 7
}
}
BiFunctionにはandThenやcomposeというメソッドは存在しません。
UnaryOperatorを利用する例を挙げよ
import java.util.ArrayList;
import java.util.List;
import java.util.function.UnaryOperator;
public class Main {
public static void main(String[] args) {
List<String> list = new ArrayList<>(List.of("a", "b", "c"));
list.replaceAll(x -> x.toUpperCase());
for (String str : list) {
System.out.println(str); // A, B, C
}
}
}
BinaryOperatorを利用する例を挙げよ
import java.util.function.BinaryOperator;
public class Main {
public static void main(String[] args) {
BinaryOperator<String> binaryOperator = (str, suffix) -> str.concat(suffix);
System.out.println(binaryOperator.apply("test", ".txt")); // test.txt
}
}
関数型インタフェースの実装で注意するべきポイントを述べよ
唯一の抽象メソッド名で呼び出すこと。インタフェース名で呼び出した気にならないように注意。
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
public class Main {
public static void main(String[] args) {
Predicate<String> predicate = (str) -> str.startsWith("a");
// 唯一の抽象メソッドを使っていないためコンパイルエラー
// predicate.test("apple")とするべき
System.out.println(predicate("apple"));
}
}
並列処理
並行処理と並列処理の違いを説明せよ
- 並行処理:シングルコアで処理対象を切り替える
- 並列処理:マルチコアで同時に処理
並行処理を実現する方法を2つ説明せよ
- runメソッドをOverrideしたThreadクラスのサブクラスを定義
- ThreadクラスのコンストラクタにRunnableインタフェースを実装したクラスを与える
public class SubThread extends Thread {
@Override
public void run() {
System.out.println("subThread");
}
}
public class Main {
public static void main(String[] args) {
// 方法1
Thread t1 = new SubThread();
t1.start();
// 方法2 (匿名クラス)
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("subThread");
}
});
t2.start();
// 方法2 (ラムダ式)
Thread t3 = new Thread(() -> System.out.println("subThread"));
t3.start();
}
}
"sub"を出力するThreadをstartした後に元のThreadで"main"を出力すると何が起きるか
mainが出力されてからsubが出力される。これはメモリ確保に時間を要するため。
public class Main {
public static void main(String[] args) {
Thread t = new Thread(() -> System.out.println("sub"));
t.start();
// main -> subの順番
System.out.println("main");
}
}
Threadでstartせずにrunすると何が起きるか
同一Thread上でrunが実行される
ThreadのstartをOverrideしてstartすると何が起きるか
同一Thread上でrunが実行される
スレッドプールを実現するためのスーパーインタフェースは何か
java.util.concurrent.Executor
スレッドプールを実現するための代表的なサブインタフェースは何か
- java.util.concurrent.ExecutorService:通常実行
- java.util.concurrent.ScheduledExecutorService:定期実行
スレッドプールを実現するための代表的なサブインタフェースの実装を取得するクラスは何か
java.util.concurrent.Executors
Executorsクラスの代表的なメソッドを挙げよ
- newSingleThreadExecutor:スレッドを1つ作成してプールする
- newFixedThreadPool:スレッドを指定された個数作成してプールする
- newCachedThreadPool:60秒で破棄されるスレッドを動的に作成してプールする
Scheduledを付けるとScheduledExecutorServiceの実装が得られる。
- newSingleThreadScheduledExecutor:定期実行するスレッドを1つ作成してプールする
- newScheduledThreadPool:定期実行するスレッドを指定された個数作成してプールする
Thread内の処理を記述するExecutorServiceのメソッドは何か
submit({実行したいラムダ式})
Thread内の処理を記述するScheduledExecutorServiceのメソッドは何か
メソッド名 | 引数 | 説明 |
---|---|---|
schedule | {Runnable型の処理}, {初期遅延}, {インターバル}, {単位} | 1回だけ遅延実行 |
scheduleAtFixedRate | {Runnable型の処理}, {初期遅延}, {インターバル}, {単位} | 複数回遅延実行 |
scheduleWithFixedDelay | {Runnable型の処理}, {初期遅延}, {インターバル}, {単位} | 複数回遅延実行 |
{単位}
にはjava.util.concurrent.TimeUnitという列挙子が入ります。
Threadを3つ作成してそれぞれのIDを出力するコードを書け
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) {
ExecutorService exec = Executors.newFixedThreadPool(3);
// 12, 13, 14
for (int i = 0; i < 3; i++) {
exec.submit(() -> System.out.println(Thread.concurrentThread().getId()));
}
}
}
scheduleAtFixedRateとscheduleWithFixedDelayの違いを説明せよ
メソッド名 | 説明 |
---|---|
scheduleAtFixedRate | 処理開始と同時にインターバルの計測が開始。 インターバルが終了&処理が完了したら次の処理を実行し,間隔は一定ではない。 |
scheduleWithFixedDelay | 処理完了と同時にインターバルの計測が開始。 インターバルが終了したら次の処理を実行し,間隔は一定となる。 |
scheduleAtFixedRateを停止させる方法を挙げよ
- shutdown()を実行
- scheduleAtFixedRateの戻り値であるScheduledFutureのcancel()を実行
- 例外がThrowされる
java.util.concurrent.Futureインタフェースの役割は何か
生成先のスレッドから生成元のスレッドに情報提供すること
java.util.concurrent.Futureインタフェースの実装はどのように得られるか
submitやscheduleを実行したときの戻り値として得られる。
ExecutorService exec = Executors.newSingleThreadExecutor();
Future future = exec.submit(...);
futureの結果はどのように受け取るか
String result = future.get()
デフォルトではfuture.get()はnullを返します。
Runnableインタフェースのrun()は何も戻さないのに何故futureでgetできるのか
そもそもrun()だけだとnullが返ってくる。結果を受け取る場合は下記のいずれを行う必要がある。
- submit()の第二引数に戻り値を定義する
- Runnableインタフェースの代わりにCollableインタフェースを実装する
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Main {
public static void main(String[] args) throws Exception {
ExecutorService exec = Executors.newSingleThreadExecutor();
Callable<Boolean> task = new Callable<Boolean>() {
@Override
public Boolean call() throws Exception {
return new Random().nextInt() % 2 == 0;
}
};
List<Future<Boolean>> futures1 = new ArrayList<>();
List<Future<Boolean>> futures2 = new ArrayList<>();
for (int i = 0; i < 10; i++) {
// 方法1: submitの第二引数を指定
futures1.add(exec.submit(() -> {}, true));
// 方法2: callableインタフェースを実装したtaskを渡す
futures2.add(exec.submit(task));
}
// 全てtrue
for (Future<Boolean> future : futures1) {
System.out.println(future.get());
}
System.out.println("-----");
// ランダム出力
for (Future<Boolean> future : futures2) {
System.out.println(future.get());
}
}
}
ドキュメント上はschedule()の第二引数には戻り値を定義できない模様です。Collableインタフェースを実装する方法は例外のスローも行うことができるため便利です。
Callableの実装をラムダ式で与える場合の注意点を述べよ
Callableを与えているのかRunnableを与えているのか分かりにくい点。戻り値があればCallableと理解する。
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Main {
public static void main(String[] args) throws Exception {
ExecutorService exec = Executors.newSingleThreadExecutor();
List<Future<Boolean>> futures1 = new ArrayList<>();
List<Future<Boolean>> futures2 = new ArrayList<>();
for (int i = 0; i < 10; i++) {
// 方法1: 戻り値がないためRunnable。第二引数で戻り値を指定するしかない。
futures1.add(exec.submit(() -> {}, false));
// 方法2: 戻り値があるためCallable。
futures2.add(exec.submit(() -> true));
}
// 全てfalse
for (Future<Boolean> future : futures1) {
System.out.println(future.get());
}
System.out.println("-----");
// 全てtrue
for (Future<Boolean> future : futures2) {
System.out.println(future.get());
}
}
}
futureのget()を該当スレッド処理中に実行すると何が起きるか
該当スレッドの処理が完了するまでget()の実行を待機する
futureのget()が投げる例外のクラスは何か
java.util.concurrent.ExecutionException
作成先で発生した例外を処理する例を書け
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class Main {
public static void main(String[] args) throws Exception {
ExecutorService exec = Executors.newSingleThreadExecutor();
List<Future<Boolean>> futures = new ArrayList<>();
for (int i = 0; i < 10; i++) {
futures.add(exec.submit(() -> {
if (new Random().nextInt() % 2 != 0) throw new Exception("odd number");
return true;
}));
}
for (Future<Boolean> future : futures) {
try {
System.out.println(future.get());
} catch (ExecutionException e) {
System.out.println(e.getMessage());
}
}
}
}
Exception("odd number")はExecutionExceptionのcauseとして格納されます。
スレッドを同期化する方法を説明せよ
Runnableの実装クラスにCyclicBarrierのインスタンスを渡し,Runnableの実装クラス(各スレッド)側で渡されたCyclicBarrierのインスタンスのawait()を呼び出す。CyclicBarrierのインスタンスには同期を取るスレッドの数とバリアアクションを設定している。CyclicBarrierの仕組みにより,await()されているスレッドの個数が指定されたCyclicBarrierのインスタンスで指定された個数に達した時点で,バリアアクションを行う。
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class Task implements Runnable {
private CyclicBarrier barrier;
public Task(CyclicBarrier barrier) {
super();
this.barrier = barrier;
}
@Override
public void run() {
// CyclicBarrierの仕様からInterruptedExceptionとBrokenBarrierExceptionを処理する必要がある
try {
this.barrier.await();
} catch (InterruptedException e) {
// スレッドが割り込まれた場合の処理
Thread.currentThread().interrupt(); // 割り込みステータスを再設定
System.out.println("Thread was interrupted");
} catch (BrokenBarrierException e) {
// バリアが壊れた場合の処理
System.out.println("Barrier was broken");
}
}
}
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) {
// 3つのCyclicBarrierを作るため最低限3つのスレッドを含むpoolを作成
ExecutorService exec = Executors.newFixedThreadPool(3);
CyclicBarrier barrier = new CyclicBarrier(3, () -> System.out.println("sync done"));
for (int i = 0; i < 3; i++) {
exec.submit(new Task(barrier));
}
}
}
同一インスタンスを複数のスレッドで使う際の排他制御方法を述べよ
synchronizedキーワードを付与すると,同一メソッドまたはその一部の同時実行を禁止することができる
volatile修飾子を説明せよ
各スレッドがインスタンスのフィールド値をキャッシュすることを禁止する。フィールドに付けられる。
private static volatile int count = 0;
synchronizedよりもライトに排他制御を実現する方法を挙げよ
atomicパッケージを利用する。変数単位で原子性(排他制御)を担保できる。
private AtomicInteger num = new AtomicInteger(0);
AtomicIntegerで原始的に値を操作するメソッドはgetAndAddやaddAndGetなどです。前者はi++に相当し,後者は++iに相当します。getは参照なので,そもそもget同士では競合しません。
スレッドセーフでないArrayListクラスの代替を述べよ
CopyOnWriteArrayList。インスタンス化時にdeep copyするため,インスタンス化後にaddした要素は反映されない。
読み出し中に変更を加えても例外が発生しないコレクションは何か
CopyOnWriteArrayList。Vectorはスレッドセーフだが例外が発生する。
各スレッドの実行順序を担保する方法を述べよ
ReentrantLockクラスを利用する。
import java.util.concurrent.locks.ReentrantLock;
public class LockSample {
private final ReentrantLock lock = new ReentrantLock();
public void lock() {
this.lock.lock();
}
public void unlock() {
this.lock.unlock();
}
public void first() {
System.out.println(Thread.currentThread().getId() + ": 1");
}
public void second() {
System.out.println(Thread.currentThread().getId() + ": 2");
}
public void third() {
System.out.println(Thread.currentThread().getId() + ": 3");
}
}
public class Task implements Runnable {
private LockSample lock;
public Task(LockSample lock) {
super();
this.lock = lock;
}
@Override
public void run() {
// try-finallyでlock-unlockを実現する
try {
lock.lock();
lock.first();
lock.second();
lock.third();
} finally {
lock.unlock();
}
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) {
LockSample lock = new LockSample();
ExecutorService exec = Executors.newFixedThreadPool(3);
exec.submit(new Task(lock));
exec.submit(new Task(lock));
exec.submit(new Task(lock));
}
}
12: 1
12: 2
12: 3
13: 1
13: 2
13: 3
14: 1
14: 2
14: 3
Reentrantは再代入可能という意味です。「複数のプログラムルーチンからいつでも呼び出し可能」と理解しておくとよいでしょう。
ストリームAPI
Optionalにおけるnullとemptyの違いを説明せよ
- null:通常のnull
- empty:値が存在しない(空である)ことを明示的に示す型(≠null)
Optionalは「nullかもしれない値」に対してempty(空)という状態を持たせることで煩雑な例外処理を防止するクラスのことを指す。
Optionalのファクトリメソッドをまとめよ
// 空のOptionalを生成
Optional<String> sample = Optional.empty();
// 引数に空を許容せずにOptionalを生成
Optional<String> sample = Optional.of("sample");
// 引数に空を許容してOptionalを生成
Optional<String> sample = Optional.ofNullable(null);
空のOptionalにget()すると何が起きるか
実行時(≠コンパイル時)にNoSuchElementExceptionが起きる
Kotlinではコンパイル時に気付けるようになります。
OptionalのisEmptyとisPresentの違いを説明せよ
- isEmpty:値が存在しない場合にtrue
- isPresent:値が存在する場合にtrue
OptionalのorElseとorElseGetの違いを説明せよ
- orElse:emptyのときの代替値を直接指定
- orElseGet:emptyのときに実行するSupplierを指定
Optional<String> sample = Optional.empty();
System.out.println(sample.orElse("empty")); // empty
System.out.println(sample.orElseGet(() -> "empty")); // empty
OptionalのorElseThrowを説明せよ
orElseGetのthrowバージョン
Optional<String> sample = Optional.empty();
// Exception in thread "main" java.lang.Exception
System.out.println(sample.orElseThrow(() -> new Exception()));
OptionalのifPresentを説明せよ
emptyでない場合に値をラムダ式に渡す
Optional<String> sample1 = Optional.of("present");
Optional<String> sample2 = Optional.empty();
// present
sample1.ifPresent((str) -> System.out.println(str));
// (何もしない)
sample2.ifPresent((str) -> System.out.println(str));
OptionalのifPresentOrElseを説明せよ
ifPresentとorElseを組み合わせたメソッド。値が存在する場合にラムダ式を実行する。
// 値が存在する場合は引数を受け取るConsumer,存在しない場合は引数を受け取らないRunnableを実行する
ifPresentOrElse(Consumer action, Runnable emptyAction)
orElseは返す値を指定しますが,ifPresentOrElseではSupplierを指定します。挙動としてはorElseGetに相当しますが,なぜかifPresentと組み合わせる場合はorElseでSupplierを指定します。この差異に注意です。
Optionalの値を変更する方法を説明せよ
同一インスタンスで値は変更できない。mapを利用して新しいインスタンスを生成して参照を受け取る。
Optional<String> before = Optional.of("present");
Optional<String> after = before.map(str -> str.toUpperCase());
// mapは値が空でも新しい空のOptionalを生成して参照を返す
Optional<String> beforeNull = Optional.ofNullable(null);
Optional<String> afterNull = before.map(str -> str.toUpperCase());
ストリームAPIを簡単に説明せよ
コレクションや配列をストリーム化することで,メソッドとラムダ式を用いて要素に何らかの処理を分かりやすく行う機能のこと。
for文とストリームのforEachの大きな違いを述べよ
for文はローカル変数を変更できるが,forEachのラムダ式ではローカル変数は変更できない点
ストリームの生成方法を述べよ
import java.util.stream.Stream;
List<Integer> list1 = List.of(1, 2, 3, 4, 5);
Integer[] list2 = {1, 2, 3, 4, 5};
int[] list3 = {1, 2, 3, 4, 5};
// Collectionインタフェースにはデフォルトメソッドとしてstream()が定義されている
Stream<Integer> stream1 = list1.stream();
// 配列からストリームを作成するにはArraysクラスのstreamメソッドを利用する
Stream<Integer> stream2 = Arrays.stream(list2);
// プリミティブ型のStreamは通常のStreamとは別のインタフェースで定義されている
IntStream stream3 = Arrays.stream(list3);
// Stream型の変数にArrays.stream(int[])の戻り値を格納するとコンパイルエラーとなる
Stream<Integer> stream3 = Arrays.stream(list3);
IntStreamで<int>のように型指定するとエラーとなることに注意しましょう。IntStream以外にもLongStreamやDoubleStreamがあります。
並列ストリームの生成方法と受け取り方を説明せよ
コレクションのstreamメソッドをparallelStreamメソッドに変更するだけ。戻り値の型は変わらない。配列で並列ストリームを生成するにはparallelメソッドを利用する。getParallelStreamメソッドではないため注意。
import java.util.stream.Stream;
List<Integer> list1 = List.of(1, 2, 3, 4, 5);
Integer[] list2 = {1, 2, 3, 4, 5};
Stream<Integer> stream1 = list1.parallelStream();
Stream<Integer> stream2 = Arrays.stream(list2).parallel();
StreamにはparallelStreamメソッドは存在しないです。
forEachOrderedを説明せよ
ストリームを並列化した場合は処理する順番を制御することはできないが,forEachの場合はforEachOrderedを利用することによりパフォーマンスを犠牲にして並列化前と同じ順番で処理することができる。
findAny()とfindFirst()の違いを説明せよ
findAny()は何が返されるか分からないが,findFirst()は先頭要素を返す。最初に処理した要素ではない。
reduceメソッドの引数が2つの場合を説明せよ
初期値を表す。
List<Integer> list = Arrays.asList(1,2,3,4,5);
Optional<Integer> result1 = list.stream().reduce((a, b) -> a + b);
int result2 = list.stream().reduce(100, (a, b) -> a + b);
System.out.println(result1); // 15
System.out.println(result2); // 115
reduceメソッドの戻り値の型を説明せよ
引数がBinaryOperatorの1つの場合は要素のOptional型,初期値とBinaryOperatorの2つの場合は初期値と同じ型になる。
List<Integer> list = Arrays.asList(1,2,3,4,5);
Optional<Integer> result1 = list.stream().reduce((a, b) -> a + b);
int result2 = list.stream().reduce(100, (a, b) -> a + b);
System.out.println(result1); // Optional<Integer>
System.out.println(result2); // int
collect()について説明せよ
ラムダ式の中で外部の変数を書き換えるのではなく,ラムダ式の結果をCollectionにして返すことで副作用を防ぐメソッド。
List<Integer> numbers = List.of(1, -1, -3, 4, -4, 10, 2, -6, 2, 1, 6);
List<Integer> result = numbers.stream()
.filter(x -> x > 0)
.collect(Collectors.toList());
System.out.println(result); // [1, 4, 10, 2, 2, 1, 6]
toListやtoSetは引数を取らないが,toMapは第一引数にキーを取り出すためのFunction型のラムダ式,第二引数に値を取り出すためのFunction型のラムダ式を指定する。
List<Integer> numbers = List.of(1, -1, -3, 4, -4, 10, 2, -6, 2, 1, 6);
Map<Integer, Integer> resultMap = numbers.stream()
.filter(x -> x > 0)
.distinct()
.collect(Collectors.toMap(
number -> number,
number -> Math.abs(number)));
resultMap.keySet().stream().forEach(System.out::println); // [1, 2, 4, 6, 10]
Collectorインタフェースの抽象メソッドを全て説明せよ
抽象メソッド | 説明 |
---|---|
supplier | 処理途中の値を保持するためのオブジェクトを生成 |
accumulator | 実行したい処理を記述したBiConsumer型のラムダ式 |
combiner | 並列処理をしている場合に最終的に結合する処理 |
finisher | 処理結果を戻すラムダ式 |
characteristics | Collectorの特徴を表すEnum |
characteristicsを具体的に説明せよ
Enum | 説明 |
---|---|
CONCURRENT | 並行処理をする |
IDENTITY_FINISH | finisherメソッドが省略可能 |
UNORDERED | コレクション操作で順序を保持しない |
これらを指定する必要がなければ,EnumSetのnoneOfメソッドにcharacteristicsのクラスリテラルを渡す。
@Override
public Set<Characteristics> characteristics() {
// return Collector.Characteristics.CONCURRENT; とか
return EnumSet.noneOf(Characteristics.class);
}
Collectorインタフェースを独自実装したSampleCollectorの使い方を述べよ
List<String> list = Arrays.asList({"A", "B", "C", "D", "E"});
// 抽象メソッドを独自でカスタマイズしたい場合はcollectの引数にCollectorインタフェースの実装を渡す
String result = list.stream().collect(new SampleCollector());
System.out.println(result);
peek()を説明せよ
Streamをそのまま返す。デバッグ用の値の出力等に利用される。
{"a", "b", "c"}
を大文字→小文字に戻して出力するコードにpeek()を挿入した場合の出力を示せ
要素を1つずつパイプラインストリームに流す。各メソッドを全ての要素に適用してから次のメソッドに進むのではなく,各要素が先頭から全ての要素を駆け抜けるイメージ。
List<String> list = Arrays.asList("a", "b", "c");
// A a aB b bC c c
list.stream()
.map(str -> str.toUpperCase())
.peek(str -> SYstem.out.println(str + " "))
.map(str -> str.toLowerCase())
.peek(str -> SYstem.out.println(str + " "))
.forEach(System.out::println);
forEachを2回連続で呼び出すと何が起きるか
2回目の呼び出しでIllegalStateExceptionがスローされる。Streamの終端操作は1回しか呼び出せない。
.mapでコンパイルエラーを引き起こす引っ掛け例を説明せよ
Optionalの入れ子をOptionalの型で受け取った場合
Optionalでintは扱えるか
ボクシングされてIntegerとして扱われる。ジェネリクスはObject型しか扱えないため。プリミティブ型のintとして扱いたい場合はOptionalIntを利用する。
Collectors.toListは単独で利用するか
collectと併用する。
.stream()
.collect(Collectors.toList());
summingIntメソッドは単独で利用するか
collectおよびCollectors.groupingByと併用する。
.stream()
.collect(
Collectors.groupingBy(
Item::getTitle,
Collectors.summingInt(Item::getPrice)
)
);
Optional.ofにnullを指定すると何が起きるか
実行時にNullPointerExceptionが起きる。コンパイルエラーではないため注意。これは,Optional.ofが参照型の引数を受け取るためであり,nullは参照がないことを示す参照型であるため,コンパイル時の型チェックに通ってしまうからである。
Setの.distinct().count()で引っかかったポイントを説明せよ
独自にequalsを定義して改変されていて常にtrueを返す場合には,sizeが1となる点
List<String>をStreamに変換してmapした返り値の型は何か
Stream<String>
入出力
java.io.FileクラスのlistメソッドとlistFilesメソッドの違いを説明せよ
- listメソッド:ディレクトリ内の一覧をStringの配列で受け取る
- listFilesメソッド:ディレクトリ内の一覧をFileの配列で受け取る
入力文字ストリームを扱うクラスは何か
java.io.Reader
出力文字ストリームを扱うクラスは何か
java.io.Writer
入力バイトストリームを扱うクラスは何か
java.io.InputStream
出力バイトストリームを扱うクラスは何か
java.io.OutputStream
FileReaderとBufferedReaderの違いを二つ挙げよ
- FileReaderは1文字ずつcharを読み取るが,BufferedReaderは1行ずつStringを読み取る
- FileReaderは終端で-1を返すが,BufferedReaderは終端でnullを返す
reader.read()はintを返すため(char)を使って明示的にキャストして処理します。
BufferedReaderとStreamの関係について述べよ
BufferedReaderではlinesメソッドを使うことでストリームAPIを活用できる。
FileReader fr = new FileReader("sample.txt");
BufferedReader reader = new BufferedReader(fr);
try (reader){
reader.lines().forEach(System.out::println);
}
FileWriterで上書きモードと追記モードの指定について説明せよ
// 上書きモード
FileWriter fw = new FileWriter("output.txt");
FileWriter fw = new FileWriter("output.txt", false);
// 追記モード
FileWriter fw = new FileWriter("output.txt", true);
デフォルトが上書きである理由は「追記モードで誤って大量の文字列をwriteした場合にメモリやディスク容量を意図せず圧迫させないため」と理解する。
BufferedWriterでバッファとディスクを同期させるメソッドを述べよ
flush()
BufferedInputStreamで複数バイトをまとめて扱うメソッドを挙げよ
- readNBytes
- readAllBytes
System.inとSystem.consoleの違いを説明せよ
- Stringに変換するまでの過程がconsoleの方が楽
- Consoleクラスではパスワードを表出させないreadPasswordメソッドが利用できる
Console console = System.console();
char[] password = console.readPassword();
readPasswordメソッドの返り値はchar配列型であるため注意です。
scannerの強みは何か
csvファイルをStreamとして扱える点。
FileInputStream fis = new FileInputStream(file);
Scanner scanner = new Scanner(fis);
// |で複数の区切り文字を指定可能
scanner.useDelimiter(",|\n")
// try-with-resourceでcloseを担保
try (scanner) {
while (scanner.hasNext()) {
...
}
}
シリアライズとは何か
Javaのオブジェクトをbyte形式で永続化すること。適切に変換しないと依存先のオブジェクトが存在しなくなってしまう可能性があるため直列化する必要があるため「Serialize(直列化)」と呼ばれている。
java.io.Serializableインタフェースには何が定義されているか
何も定義されていない
Serializableインタフェースを実装していないクラスをシリアライズすると何が起きるか
java.io.NotSerializableExceptionがスローされる
シリアライズの実装例を挙げよ
FileOutputStream fos = new FileOutputStream("sample.ser");
ObjectOutputStream out = new ObjectOutputStream(fos);
try (out) {
Item item = new Item("banana", 100);
out.writerObject(item);
}
デシリアライズの実装例を挙げよ
FileInputStream fis = new FileInputStream("sample.ser");
ObjectInputStream in = new ObjectInputStream(fis);
try (in) {
Object obj = in.readObject();
Item item = (Item) obj;
}
特定のフィールドをシリアライズの対象外にする修飾子を述べよ
- transient
- static
カスタムシリアライズする際にSerializableインタフェースで実装するメソッドを挙げよ
- writeObject(シリアライズ時に利用)
- readObject(デシリアライズ時に利用)
これらはシリアライズやデシリアライズの前に呼ばれます。
java.nio.file.Pathとjava.nio.file.Pathsの違いを説明せよ
PathはインタフェースでPathsはクラス
NIO.2で注意するべきポイントを述べよ
Pathの操作とFileの操作が別になっている点。FilesクラスからPathsクラスを取得できない。
import java.io.File
import java.nio.file.Path;
import java.nio.file.Files;
Path path = Paths.get("dir/sample.txt"); // PathsからPathを抽出。OK。
Path path = Paths.get("dir", "sample.txt"); // PathsからPathを抽出。OK。
Path path = new File("sample.txt").toPath(); // 従来のFileからPathを抽出。OK。
Path path = Files.get("sample.txt"); // nioのFilesからPathを抽出。NG。
「従来のFileクラスとは相互に変換可能だがnioのFilesとPathsは変換不能」とざっくり覚えます。
nioのFilesクラスでcreateFileメソッドに与えるPathにファイルが存在していた場合何が起きるか
FileAlreadyExistsExceptionがスローされる
nioのFilesクラスにおけるcreateDirectoryとcreateDirectoriesの違いを説明せよ
作成するディレクトリの親が存在しない場合 | 同名のディレクトリが存在する場合 | |
---|---|---|
createDirectory | NoSuchFileExceptionをスロー | FileAlreadyExistsExceptionをスロー |
createDirectories | 再帰的に作成 | 何もしない |
nioのPathにおけるresolveを説明せよ
パスを結合する。
Path p1 = Paths.get("dir1/dir2");
Path p2 = Paths.get("dir3/sample.txt");
Path p3 = p1.resolve(p2); // dir1/dir2/dir3/sample.txt
BufferedWriterのオプションを説明せよ
java.nio.file.StandardOpenOptionのEnumで指定する。複数同時に指定できる。
列挙子 | 説明 |
---|---|
APPEND | 追記モード |
CREATE | ファイルが存在しない場合は新規作成 |
CREATE_NEW | ファイルが存在する場合は失敗 |
DELETE_ON_CLOSE | 閉じるときに削除 |
READ | 読み込み専用 |
FileWriterでは第二引数がtrueで追記モード,falseで上書きモードとなりましたが,BufferedWriterではEnumのオプションを与えて制御するという違いがあります。
nioのFilesにおいて再帰的にファイル一覧をStreamで扱うメソッドは何か
walk
Path base = Paths.get(".");
// baseディレクトリ直下のファイル一覧
Files.list(base).forEach(System.out::println);
// srcディレクトリの下にあるファイルを再帰的に一覧化
Files.walk(base.resolve("src")).forEach(System.out::println);
nioのFilesにおけるfindメソッドを説明せよ
import java.nio.file.attribute.BasicFileAttributes
import java.util.function.BiPredicate;
BiPredicate<Path, BasicFileAttributes> bp = (p, attr) -> {
p.toFile().getName().endsWith(".jpg");
}
// baseディレクトリから3階層下までbpでtrueを返すものを一覧化
Files.find(base, 3, bp).forEach(System.out::println);
nioのFilesにおけるlistメソッドとnewDirectoryStreamメソッドの違いを述べよ
- list:返り値の型はStreamなのでmapやfilterなど汎用的に利用したい場合に有用
- newDirectoryStream:Iterableのサブインタフェースであるため拡張for文などの用途に限定
DirectoryStreamはBaseStreamのサブインタフェースではありません。Streamという名前に惑わされないように注意してください。
nioのFilesにおけるwalkFileTreeメソッドで扱う抽象メソッドを説明せよ
深さ優先でファイルを探索してアクションを実行するためのメソッド。
タイミング | 抽象メソッド名 |
---|---|
ディレクトリに入るとき | preVisitDirectory |
ディレクトリから出るとき | postVisitDirectory |
ファイルを発見したとき | visitFile |
ファイルの処理に失敗したとき | visitFileFailed |
walkFileTreeメソッドのvisitFileのタイミングだけでアクションしたい場合でも,独自に実装する場合は全ての抽象メソッドを実装する必要があります。そこで,最低限の実装がなされているSimpleFileVisitorクラスをオーバーライドすると便利です。また,「ファイルの処理が終了したとき」に該当するタイミングと抽象メソッドが規定されていない点にも注意してください。
JDBCによるデータベース連携
DBMSのドライバとは何か
ベンダーが開発するJDBCのインタフェースを実装したクラス
JDBCには何が含まれるか
- インタフェース
- 例外クラス
- 共通クラス
など
「JDBCにはDBMSと連携するための実装クラスが含まれる」は正しいか
正しくない。ドライバのことを指しているが,ドライバはベンダーが開発する。JDBCは共通部分。
DBMSへの接続文字列を述べよ
// jdbc:{DBエンジンの種類}://{サーバのIPアドレス}:{ポート番号}/{データベースの場所}
jdbc:mysql://localhost/test
Connectionオブジェクトの取得方法を述べよ
import java.sql.Connection
import java.sql.DriverManager
Connection con = DriverManager.getConnection("jdbc:mysql://localhost/test");
ConnectionManagerではないため注意する。
PreparedStatementオブジェクトの取得方法を述べよ
ConnectionクラスのprepareStatementメソッドにSQLを渡す。prepareが過去形でない点に注意。
import java.sql.PreparedStatement
PreparedStatement ps = con.prepareStatement("select * from items");
PreparedStatementオブジェクトの取得時にSQLを渡さない場合,何が起きるか
コンパイルエラー。引数なしのコンストラクタは定義されていない。
preparedStatementのsetXXの第一引数を説明せよ
何番目の?なのかを表す数字。1始まりである点に注意。
// 実行時にSQLException
ps.setInt(0, 1);
// 1番目の?に1を代入
ps.setInt(1, 1);
?に値をsetしないでPreparedStatementを実行すると何が起きるか
実行時にSQLExceptionが発生する
PreparedStatementのexecuteUpdateメソッドの返り値は何か
更新件数
PreparedStatementのexecute系のメソッドにSQLを指定すると何が起きるか
実行時にSQLExceptionが発生する。コンパイルエラーにならない理由はPreparedStatementがStatementの引数ありexecuteメソッドを継承しているから。
ResultSetから結果を取り出す方法を述べよ
// rsを受け取るが、next()でカーソルを進めずにrs.getXXするとSQLExceptionが発生するため注意
ResultSet rs = ps.executeQuery();
while(rs.next()) {
// カーソルが当たっている行の1番目のカラムに格納された値をintとして抽出
System.out.print(rs.getInt(1) + ":");
// カーソルが当たっている行のnameカラムに格納された値をStringとして抽出
System.out.println(rs.getString("name"));
}
JDBCまわりのインデックスは1から始まる,とざっくり理解しておきましょう。
PreparedStatementのexecuteメソッドの返り値は何か
実行結果がResultSetであるかどうかのBoolean。executeQuery()に相当するSQLであった場合はtrueが返り,executeUpdate()に相当するSQLであった場合はfalseが返る。
executeBatchメソッドの使い方を説明せよ
String[] names = {"apple", "banana", "cherry"};
var sql = "insert into item values(?,?)";
try (PreparedStatement ps = con.prepareStatement(sql)) {
int i = 10;
for (String name : names) {
ps.setInt(1, i);
ps.setString(2, name);
// batchに登録
ps.addBatch();
i++;
}
// [1, 1, 1]が返る
int[] results = ps.executeBatch();
}
ストアドプロシージャの使い方を説明せよ
- jarファイルを作成
- jarファイルに含まれるメソッドに名前を付けてストアドプロシージャに登録
- "CALL {ストアドプロシージャに登録した名前}"をprepareCallに渡す
// 引数を二つ取るメソッドをUPDARE_ITEMという名前を付けてストアドプロシージャに登録したと仮定
String proc = "CALL UPDARE_ITEM(?, ?)";
try (CallableStatement cs = con.prepareCall(proc)) {
cs.setInt(1, 1);
cs.setString(2, "sample");
cs.execute();
}
汎用とコレクション
char型のラッパークラスは何か
Character
ジェネリクスの型が決まる流れを説明せよ
引数を見るのではなく,ダイアモンド演算子に依存して決まる。
public class Value<T> {
T val;
public Value(T val) {
super();
this.val = val;
}
public T getVal() {
return val;
}
public void setVal(T val) {
this.val = val;
}
}
public class Main {
public static void main(String[] args) {
// ダイアモンド演算子でStringを指定
Value<String> v1 = new Value("sample");
String x1 = v1.getVal();
// ダイアモンド演算子がないためObject型として認識
// 「"sample"をTとみなしてTが返る」と誤解しないように注意
Value v2 = new Value("sample");
Object x2 = v2.getVal();
}
}
クラス宣言時にジェネリクスによる型推論はできるか
できない。
- 変数への代入
- メソッドの戻り値
- メソッドの引数
では型推論できる。
共変/非変/反変を説明せよ
- 共変:サブクラス型の代入を許す(ポリモーフィズム)
- 非変:サブクラス型の代入を許さない
- 反変:スーパークラス型の代入を許す
ジェネリクスは共変/非変/反変のどれに該当するか
非変
// コンパイルエラー
// list.add(100)のようなObject型のサブクラスの代入を許してしまうと不都合であるため
List<Object> list = new ArrayList<String>();
// OK
List<String> list = new ArrayList<String>();
ジェネリクスの制約付き型パラメータを説明せよ
ジェネリクスに指定できる型を制限する方法
// AクラスまたはAのサブクラスに制限
public class Sample<T extends A> {
public void test(T t) {
// AクラスまたはAのサブクラスが持つメソッドを利用可能
// extendsで制約しないとそのメソッドを持つか不明であるためコンパイルエラーとなる
}
}
非境界ワイルドカードを説明せよ
実行時まで型が不明であるジェネリクスの記述方法。ジェネリクスは共変ではなく非変でありObjectを使って汎用化することができないため,ワイルドカード「?」を使って汎用化させるイメージ。
private void printAll(Collection<?> collection) {
collection.stream().forEach(System.out::println)
}
仮に?にObjectを指定するとジェネリクスの非変性よりObjectしか渡せなくなってしまい不便です。
<?>を使った場合の制限を述べよ
- メソッドの戻り値の型はObject型
- メソッドの引数にはnullリテラルしか渡せない
public class Value<T> {
T val;
public Value(T val) {
super();
this.val = val;
}
public T getVal() {
return val;
}
public void setVal(T val) {
this.val = val;
}
}
public class Main {
public static void main(String[] args) {
// ダイアモンド演算子でStringを指定した場合は問題ない
Value<String> v1 = new Value("sample");
String x1 = (String) v1.getVal();
v1.setVal("fixed");
// ダイアモンド演算子で?を指定した場合は要注意
Value<?> v2 = new Value("sample");
// ジェネリクスはObject型になるためStringへのダウンキャストは成功
String x2 = (String) v2.getVal();
// メソッドにはnullリテラルしか渡せないためコンパイルエラー
v2.setVal("fixed");
// これはOK
v2.setVal(null);
}
}
null以外を渡してもコンパイラは「これは正しい型だ」と即決できないためエラーとなります。
<? extends A>を使った場合の制限を述べよ
ジェネリクスにはAまたはAのサブクラスが渡されるため,下記のような性質を有する。
- メソッドの戻り値の型をAにできる
- メソッドの引数にはnullリテラルしか渡せない
依然としてジェネリクスの型が1つに定まらないためメソッドの引数にはnullしか渡せません。なお,この記述方法を上限境界ワイルドカードと呼びます。
<? super C>を使った場合の制限を述べよ
ジェネリクスにはCよりも上位のクラスが渡されるため,下記のような性質を有する。
- メソッドの戻り値の型はObject型
- メソッドの引数をCにできる
メソッドの引数をnull以外で定められるようになった一方で,メソッドの戻り値はObject型に戻ってしまいます。なお,この記述方法を下限境界ワイルドカードと呼びます。
上限境界ワイルドカードと下限境界ワイルドカードの覚え方をまとめよ
まずワイルドカードは戻り値がObjectで引数がnullでないとならないという制約があることを理解する。戻り値に関してはジェネリクスが参照型を扱うためObjectを返しておけば全てのクラスをカバーできるからであり,メソッドの引数に関してはnull以外を渡してもコンパイラは「これは正しい型だ」と即決できないからである。
戻り値の制約を解消するためには「ジェネリクスに渡す型はAまたはAのサブクラスである」のように上限を定める必要がある。これによりジェネリクスを用いた戻り値は最悪でもAとして扱ってもよくなる。
メソッド引数の制約を解消するためには「ジェネリクスに渡す型はCまたはCのスーパークラスである」のように下限を定める必要がある。これによりジェネリクスを用いたメソッドの引数は最悪でもCとして扱ってもよくなる。
PECSを説明せよ
Producer-Extends and Consumer-Superの略称。戻り値を戻すことを目的とするのであれば上限境界ワイルドカードでextendsを使い,引数を受け取って利用することを目的とするのであれば下限境界ワイルドカードでsuperを使うという原則。
ArrayList/LinkedList/Vectorを説明せよ
- ArrayList:読み込みは速いが書き込みは遅い
- LinkedList:書き込みは速いが読み込みは遅い
- Vector:スレッドセーフでマルチスレッドに対応しているがパフォーマンスは劣る
Dequeを説明せよ
double ended queueの略称で,両端から要素を追加可能なキューのことを指す
HashSet/TreeSetを説明せよ
- HashSet:順序は保証しないがパフォーマンスは高い
- TreeSet:自然順序または任意の順序を保証するがパフォーマンスはHashSetよりも劣る
mapのentrySetを説明せよ
mapの(key, value)のペアをEntryと呼び,map.entrySet()でEntryの集合を取り出すことができる。
// 拡張for文で使うパターン
Map.Entry<Integer, String> entry : map.entrySet() {...}
// Streamで使うパターン
map.entrySet().stream().forEach(...)
entrySet()のSetに引きづられてSetを返すと誤解しないようにしてください。
Comparable/Comparatorを説明せよ
引数の個数 | 説明 | |
---|---|---|
Comparable | 1つ | 比較可能であることを示すインタフェース。 自身のクラスと他のクラスを比較する。 |
Comparator | 2つ | 並べ替えの基準だけを定義するインタフェース。 比較可能なクラス自体ではないため2つの引数を取る。 |
// Comparableを実装して比較可能であることを示す
public class Item implements Comparable<Item> {
private String name;
private int price;
public Item(String name, int price) {
super();
this.name = name;
this.price = price;
}
// 自身と他のクラスを比較するため引数は1つ
@Override
public int compareTo(Item other) {
if (this.price < other.price) return -1;
if (this.price > other.price) return 1;
return 0;
}
public String getName() {
return name;
}
public int getPrice() {
return price;
}
@Override
public String toString() {
return String.format("Item [name=%s, price=%s]", this.name, this.price);
}
}
import java.util.Comparator;
public class ItemNameComparator implements Comparator<Item> {
@Override
public int compare(Item a, Item b) {
return a.getName().compareTo(b.getName());
}
}
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class Main {
public static void main(String[] args) {
Item a = new Item("apple", 100);
Item b = new Item("banana", 200);
Item c = new Item("cherry", 50);
List<Item> list = Arrays.asList(a, b, c);
Collections.sort(list);
list.forEach(System.out::println);
System.out.println("---");
list.sort(new ItemNameComparator());
list.forEach(System.out::println);
}
}
Item [name=cherry, price=50]
Item [name=apple, price=100]
Item [name=banana, price=200]
---
Item [name=apple, price=100]
Item [name=banana, price=200]
Item [name=cherry, price=50]
ComparatorでStringのcompareToを利用せずに独自実装する例を挙げよ
list.sort((x, y) -> x.getPrice() - y.getPrice());
list.forEach(System.out::println);
sortに与えるラムダ式はBooleanを返すのではなく-1,0,1を返す点に注意してください。
Collections.sortとlist.sortの違いを説明せよ
- Collections.sort:引数にlistを渡せる
- list.sort:引数が空だとエラーになるためComparatorの実装クラスを渡す
アノテーション
マーカーインタフェースを説明せよ
ラベルを付けるために利用される中身が空のインタフェースのこと
アノテーションの登場背景を説明せよ
マーカーインタフェースを簡単に利用・拡張できるようにするため
マーカーインタフェースとアノテーションの差異は何か
アノテーションではマーカーインタフェースでは実現できない下記を実現可能
- アノテーションの付与先に値(注釈パラメータ)を渡すことができる
- フィールド・コンストラクタ・メソッドなど細かな単位でアノテーションを付与可能
アノテーションの定義方法を述べよ
interface宣言を@interfaceとすればよいだけ
public @interface NotNull {}
@interfaceの本質を説明せよ
java.lang.annotation.Annotationインタフェースを継承したサブインタフェース
// public @interface NotNull {} をコンパイルすると下記となる
public interface NotNull extends java.lang.annotation.Annotation {}
アノテーションが付与されたクラスと@interfaceの関係を述べよ
アノテーションが付与されたクラスでは「アノテーションが付与されている」という情報を保持するだけであり,コンパイル時にjava.lang.annotation.Annotationインタフェースを継承したサブインタフェースを実装するように変換される訳ではない。
注釈付きアノテーションの定義方法を述べよ
抽象メソッドと同じ記法で@interface内部のブロックに宣言する。フィールドを宣言する訳ではない。
public @interface NotNull {
String message();
}
public class Sample {
// 注釈パラメータが1つの場合は @NotNull("name is not allowed null") のように省略可能
@NotNull(message = "name is not allowed null")
private String name;
}
抽象メソッドがパラメータの値を保持するのは少し違和感がありますが,注釈パラメータは内部で抽象メソッドを用いて管理されている仕様なのだと理解しておきましょう。
注釈付きアノテーションのデフォルト値の設定方法を述べよ
defaultを利用する
public @interface NotNull {
String message() default "not allowed null";
}
リフレクションとは何か
クラスの定義情報に基づいてインスタンスを操作する仕組みのこと
リフレクションで用いられるクラスは何か
Classクラス(.class)
メタアノテーションとは何か
アノテーションの定義に付与するアノテーション
@Retentionとは何か
アノテーションの保持期間を表すメタアノテーション。注釈パラメータはRetentionPolicyの列挙子を利用。
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface NotNull {
String message();
}
列挙子 | 意味 |
---|---|
SOURCE | コンパイル時に利用して破棄する |
CLASS | クラスファイルには残すが実行時に破棄する |
RUNTIME | 実行時まで保持する |
@Targetとは何か
アノテーションの付与先を指定するメタアノテーション
import java.lang.annotation.Target;
// フィールドへの付与を指定
@Retention(ElementType.FIELD)
public @interface NotNull {
String message();
}
指定がなければアノテーションが付与できる対象全てに付与できます。
リフレクションと独自定義のアノテーションを利用してフィールドのNotNullを検査せよ
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
// このアノテーションはフィールドにしか付けられない
@Target(ElementType.FIELD)
// 実行時にも有効なアノテーション
@Retention(RetentionPolicy.RUNTIME)
public @interface NotNull {
// 違反したときのメッセージを格納するフィールド
String message();
}
public class Sample {
// TargetにFIELDを指定しているためOK
@NotNull(message = "name is not allowed null.")
private String name;
// コンストラクタ
public Sample(String name) {
this.name = name;
}
// getter
public String getName() {
return this.name;
}
}
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
public class NotNullValidator {
public static List<String> validate(Object target) {
try {
// 違反したフィールドが存在した場合のメッセージを格納する配列
List<String> messages = new ArrayList<String>();
// リフレクションにより得られたClassクラス
Class clazz = target.getClass();
// Classクラスからフィールド一覧を配列として取得
Field[] fields = clazz.getDeclaredFields();
// それぞれのフィールドについて走査
for (Field field : fields) {
// privateの場合は一時的にアクセス権を変更
field.setAccessible(true);
// NotNullアノテーションを取得
// NotNullは同一パッケージのためimport不要
NotNull annotation = field.getAnnotation(NotNull.class);
// NotNullアノテーションが付いていなければスキップ
if (annotation == null) continue;
// NotNullアノテーションは付いているがnullでない場合もスキップ
if (field.get(target) != null) continue;
// NottNullアノテーションが付いていてnullの場合はmessageを格納
messages.add(annotation.message());
}
return messages;
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
import java.util.List;
public class Main {
public static void main(String[] args) {
// NotNullのフィールドにnullを格納
Sample sample = new Sample(null);
List<String> messages = NotNullValidator.validate(sample);
if (messages.isEmpty()) {
System.out.println("this is a valid object.");
} else {
for (String message : messages) {
System.out.println(message);
}
}
}
}
this is a valid object
@Overrideの意義を説明せよ
サブクラス側のメソッドに付けておけば,スーパークラスの同名メソッドが変更された際にコンパイラが検知してくれる点。意図しないオーバーロードを防ぐ役割がある。
@Deprecatedでより強く使用を制限する方法を説明せよ
注釈パラメータのforRemovalをtrueに設定する
@SuppressWarningsは注釈パラメータを指定せずに利用できるか
利用できない。下記のエラーが出る。
annotation @SuppressWarnings is missing a default value for the element 'value'
@SuppressWarningsに設定できる代表的な注釈パラメータは下記。
- unchecked
- deprecation
- removal
uncheckedはコンパイル時の警告を抑止するが,@Deprecationの警告は抑止しない。@Deprecationを抑止したい場合はdeprecationとremovalを使う。
試験対策としては「uncheckedはClassCastExceptionを抑止する。具体的には,ジェネリクスを使わないリストをコンパイルすると出る警告を抑止する。」とおさえておく。
@SuppressWarningsと@Deprecatedの関係を説明せよ
宣言側 | 呼び出し側 | 警告 |
---|---|---|
@Deprecated | - | $\cm$ |
@Deprecated | @SuppressWarnings("deprecation") | - |
@Deprecated(forRemoval = true) | @SuppressWarnings("deprecation") | $\cm$ |
@Deprecated(forRemoval = true) | @SuppressWarnings("removal") | - |
例外とアサーション
例外のマルチキャッチを説明せよ
|を使って複数の例外を1つのcatchブロックで処理すること。
catch (IOException|SQLException e) {
logger.log(e);
throw e;
}
BExceptionがAExceptionを継承している場合,両者をマルチキャッチすると何が起きるか
コンパイルエラーが起きる
The exception BException is already caught by the alternative AException
catchの()内で|によって繋げるとエラーが起きるだけで, 後続でcatchしてもエラーにはなりません。ただし,後続で子をキャッチしても該当Exceptionは前段の親でcatchされているため,後続のcatchには意味がありません。また,共通の親を持つなど,直接の継承関係にないExceptionをマルチキャッチしてもエラーは起きません。
try-with-resourceの()内で扱えるクラスは何か
Closeableインタフェース,またはAutoCloseableインタフェースの実装クラス
CloseableインタフェースとAutoCloseableインタフェースの違いを説明せよ
インタフェース | パッケージ | スローする例外 | 特徴 |
---|---|---|---|
Closeable | java.io | IOException | IOに特化。AutoCloseableのサブインタフェース。 |
AutoCloseable | java.lang | Exception | 汎用的なCloseableインタフェース。基本的にはこちらを利用する。 |
歴史的にはCloseableが最初に登場し,次にAutoCloseableが登場したという順番らしいです。先に登場したインタフェースが後から登場したインタフェースのサブインタフェースとして定義されているのは違和感ですが,try-with-resourceの()内で扱えるようなcloseできるクラスのスーパーインタフェースとして位置付けるために差し込んだものと思われます。
tryで宣言されたリソースが閉じる順番を説明せよ
宣言とは逆の順番で閉じる。A,B,Cの順で宣言した場合はC,B,Aの順番で閉じる。
抑制された例外の例を挙げよ
try-with-resourceのclose処理内で生じた例外
抑制された例外の例の確認方法を述べよ
catchした例外のgetSuppressed()を利用してThrowable型の配列から取り出せばよい。
// SampleResourceのclose処理でExceptionをthrowするように定義
try (SampleResource resource = new SampleResource();) {
throw new Exception();
} catch (Exception e) {
for(Throwable t : e.getSuppressed()) {
// close処理で定義されたExceptionが取り出される
System.out.println(t);
}
}
アサーションとは何か
validationして引っかかった場合はjava.lang.AssertionErrorを投げることができる仕組み。条件式がfalseの場合にAssertionErrorを投げてコロンの右側のメッセージを渡すことができる。
public void setName(String name) {
assert name != null : "name is null";
this.name = name;
}
public void setPrice(int price) {
assert price > 0 : "invalid price"
this.price = price;
}
アサーションを利用するための手続きを説明せよ
javaコマンドの-eaオプションを利用する
-eaはenable assertionsの略称と理解しましょう。
ローカライズ
javaコマンドで大文字のDが付くオプションを説明せよ
JVMに対してシステムプロパティを指定するオプション
LocaleクラスのgetCountry()で取得するシステムプロパティは何か
user.country
LocaleクラスのgetLanguage()で取得するシステムプロパティは何か
user.language
SampleクラスをcountryにUS,languageにenを指定して実行せよ
java -Duser.country=US -Duser.language=en Sample
Localeクラスのコンストラクタの引数が2つの場合,それぞれの意味を説明せよ
// ({language}, {country})
Locale locale = new Locale("ja", "JP");
引数が3つの場合,3つ目はvariantを表します。WIN/MAC/POSIXなどが指定できます。
Localeには引数なしのコンストラクタは定義されているか
定義されていない
Locale定数を説明せよ
lcaleインスタンス生成時に指定できる定数で,代表的なロケールを指定することができる。
// languageやcountryが自動で設定される
Locale locale = Locale.JAPAN
IETF言語タグを抽出する方法を述べよ
// ja-JP-u-ca-japanese-x-lvariant-JP
locale.toLanguageTag();
IETF言語タグでlocaleをインスタンス化する方法を述べよ
Locale locale = Locale.forLanguageTag("ja-JP-u-ca-japanese-x-lvariant-JP");
java.util.PropertiesにおいてgetメソッドとgetPropertyメソッドの違いを説明せよ
- get:戻り値はObject
- getProperty:戻り値はString
import java.util.Properties;
public class PropertiesExample {
public static void main(String[] args) {
Properties properties = new Properties();
properties.setProperty("key1", "value1");
// 戻り値はObject
Object value1 = properties.get("key1"); // value1
Object value2 = properties.get("key2"); // null
// 戻り値はString
String propValue1 = properties.getProperty("key1"); // value1
String propValue2 = properties.getProperty("key2"); // null
String propValue3 = properties.getProperty("key2", "default_value"); // default_value
}
}
java.util.Propertiesにおけるlistメソッドを説明せよ
引数にjava.io.PrintStream型を受け取り,プロパティファイルの中身を列挙するメソッド
prop.list(System.out)
LocaleのBuilderを使う方法を説明せよ
BuilderはLocaleのstaticなネストクラスであることから,下記のように利用できる。
Locale locale = new Locale.Builder().setLanguage("ja").setRegion("JP").build();
Propertiesクラスで日本語は扱えるか
工夫しないと扱えない。日本語を扱うためには,
- native2asciiコマンドを利用して日本語をUnicode表記に変換
- FileReaderのコンストラクタでCharsetクラスを指定
- InputStreamReaderクラスのコンストラクタで文字コードを指定
- ResourceBundleクラスを利用
のいずれかを行う必要がある。
// 方法1
native2ascii from.txt to.properties
// 方法2
prop.load(new FileReader("sample.properties", Charset.forName("UTF-8")));
// 方法3
prop.load(new InputStreamReader(new FileInputStream("sample.properties"), "utf-8"));
// 方法4
ResourceBundle resource = ResourceBundle.getBundle("sample");
// 方法4でtestというpropertyをgetするには
System.out.println(resource.getString("test"));
ResourceBundleは抽象クラスであるため,staticメソッドのgetBundleを使ってインスタンス化しますが,その際に与える引数はプロパティファイルの.propertiesを除いたファイル名を与える点に注意してください。プロパティファイルの.propertiesを除いたファイル名のことを基底名といいます。
ResourceBundleでロケール情報に合わせてプロパティファイルを読み分ける仕組みを説明せよ
sample_ja_JP.propertiesのように規定名にロケールを指定することで読み分けてくれる
ResourceBundleでロケール情報に該当するプロパティファイルが見つからなかった場合は何が起きるか
ロケール情報を基底名に加えていないデフォルトプロパティファイルが存在する場合はデフォルトプロパティファイルを読み込み,存在しない場合はjava.util.MissingResourceExceptionがスローされる。
ResourceBundleのインスタンス化時にロケール情報を指定することはできるか
できる
// 第二引数にロケール定数を指定
ResourceBundle resource = ResourceBundle.getBundle("sample", Locale.JAPAN);
プロパティファイルに利用できる文字コードを二つ答えよ
- ISO-8859-1
- UTF-8
デフォルト含め該当ロケールのプロパティファイルが存在しない場合何が起きるか
MissingResourceExceptionがスローされる
代表的なDateTimeFormatter定数を挙げよ
定数 | 例 |
---|---|
BASIC_ISO_DATE | 20240525 |
ISO_LOCAL_DATE | 2024-05-25 |
ISO_LOCAL_TIME | 17:02:32 |
ISO_ORDINAL_DATE | 2024-146 |
ISO_ORDINAL_DATEでハイフンの後ろは年始から数えた経過日数です。
DateTimeFormatter定数の使い方を説明せよ
DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE;
System.out.println(formatter.format(LocalDate.now()));
DateTimeFormatterをロケールごとに自動で使い分ける方法を説明せよ
ofLocalizedDateメソッドにFormatStyle定数を指定する。
定数 | 説明 | 例 |
---|---|---|
FULL | 最も詳細 | Thursday, April 1, 2021 |
LONG | 詳細 | April 1, 2021 |
MEDIUM | やや詳細 | Apr 1, 2021 |
SHORT | 短い | 4/1/21 |
// ロケール情報に応じて指定した詳細度でformatする
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL);
NumberFormatの使い方について説明せよ
get{種類}Instanceでformatterをインスタンス化してformatメソッドを適用するだけ。
NumberFormat formatter1 = NumberFormat.getInstance(); // 3桁区切り
NumberFormat formatter2 = NumberFormat.getIntegerInstance(); // 3桁区切り&最も近い整数に丸める
NumberFormat formatter3 = NumberFormat.getCurrencyInstance(); // 3桁区切り&通過フォーマット
NumberFormat formatter4 = NumberFormat.getPercentInstance(); // パーセント表示
System.out.println(formatter1.format(1234.5)); // 1,234.5
System.out.println(formatter2.format(1234.5)); // 1,234
System.out.println(formatter3.format(1234.5)); // ¥1,234
System.out.println(formatter4.format(0.1)); // 10%
NumberFormatをロケールごとに自動で使い分ける方法を説明せよ
インスタンス化時にロケールを指定する
NumberFormat formatter1 = NumberFormat.getInstance(Locale.JAPAN);
モジュールシステム
module-info.javaのopensディレクティブを説明せよ
opens A to BでBにAのリフレクションを許可する
// A.coreのリフレクションをBに許可
module com.sample.A {
opens com.sample.A.core to com.sample.B
}
モジュールの種類を挙げてそれぞれを説明せよ
種類 | module-info.java | パス |
---|---|---|
名前付きモジュール | $\cm$ | モジュールパス |
自動モジュール | - | モジュールパス |
無名モジュール | - | クラスパス |
manifest.mfとmodule-info.javaの違いを説明せよ
- manifest.mf:jarファイルのメタデータを定義する
- module-info.java:モジュールを定義する
自動モジュールはmodule-info.javaを持たないがmanifest.mfは持つため,manifest.mfにモジュール名を定義することができる。
Automatic-Module-Name: Lib
自動モジュールでmanifest.mfが存在しない場合,モジュール名はどうなるか
特定の規則に従って自動付与される
「特定の規則」では,拡張子が削除されたり,記号がドットに置き換えられたり,ハイフン以降のバージョニングが削除されたりするが,暗記する必要はない。
名前付きモジュールとそれ以外のモジュールの参照制約を述べよ
- 対自動モジュール:参照可能
- 対無名モジュール:参照不可
名前付きモジュールからの参照制約はmodule-info.javaを思い浮かべて覚えます。自動モジュールはmodule-info.javaでrequiresすることができるため参照可能です。無名モジュールはmodule-info.javaでrequiresすることができないため参照不可です。
自動モジュールとそれ以外のモジュールの参照制約を述べよ
- 対名前付きモジュール:--add-modulesすれば参照可能
- 対無名モジュール:参照可能
他の種類のモジュールから名前付きモジュールへの参照は--add-modulesが必須と覚えます。自動モジュールには「利用しているモジュールは全て自動でrequiresされる」という仕組みがあるため,自動モジュールから無名モジュールへの参照は可能です。
無名モジュールとそれ以外のモジュールの参照制約を述べよ
- 対名前付きモジュール:--add-modulesすれば参照可能
- 対自動モジュール:--add-modulesすれば参照可能
無名モジュールから他の種類のモジュールを参照するためには--add-modulesが必須と覚えます。
ボトムアップ移行を説明せよ
最も依存されている先のモジュールから順番に名前付きモジュールに変換していく方式のこと。安全に移行することができる。
トップダウン移行を説明せよ
依存先のモジュールがメンテナンス中などの理由により名前付きモジュールに変換できない際に,苦肉の策として依存元のモジュールから名前付きモジュールに変換していく方式のこと。依存元から名前付きモジュールに変換すると,モジュール間の参照制約により,依存先が無名モジュールの場合に参照できないという問題が発生する。そのため,依存先の無名モジュールを一旦自動モジュールに変換してから,依存元のモジュールを名前付きモジュールに変換するようにする。その後,依存先が名前付きモジュールに変換できる状況になり次第,名前付きモジュールに変換する。
モジュール化したプログラムの実行方法を説明せよ
// java --module-path {モジュールパス} --module {モジュール名/実行したい完全修飾クラス名}
java --module-path modules --module App/com.sample.Main
ServiceLoaderを一言で説明せよ
指定したインタフェースを実装したクラスを探し,そのインスタンスを集合として返してくれる便利機能
特定のインタフェースを実装したクラスを探すという部分が肝要であり,ここに言及していない「クラスファイルをロードするためのクラス」という説明は誤りです。
SPIを簡単に説明せよ
Service Provider Interfaceの略称で,アプリケーション側が実装して欲しいインタフェースを定め,ライブラリがそのインタフェースの実装クラスを提供する仕組みのこと。JDBCなどが例として挙げられる。
ServiceLoaderはSPIを実現するための仕組みか
合っている
ServiceLoaderに探させるインタフェースを指示する方法を説明せよ
META-INF/services配下にインタフェースの完全修飾クラス名と同じ名前のファイルを作り,その中に実装クラスの完全修飾クラス名を書いておく。このファイルがインタフェースと実装クラス名を紐づける役割をする。
ServiceLoaderで実際に実装クラスを探す方法を説明せよ
// META-INF/services配下のファイルから実装クラスを探したいインタフェースであるSampleを探す
// Sampleというファイルに書かれている完全修飾クラス名の実装クラスのインスタンスを生成する (DI的なやつ)
Iterator<Sample> samples = ServiceLoader.load(Sample.class).iterator();
モジュールシステムにおけるSPIの利用方法を説明せよ
SPI提供側でprovidesとwithによりインタフェースと実装クラスを明示し,SPI利用側でusesを明示する
module app {
exports app;
uses app.Sample; // app.SampleインタフェースをSPIとして利用する
}
module lib {
exports lib;
requires app;
provides app.Sample with lib.SampleImpl; // Sampleインタフェースを実装したSampleImplを提供
}
公開するインタフェースを宣言するディレクティブは何か
uses
「公開する」という文字面からprovidesと誤解しないように注意する。providesはあくまでもインタフェースを実装する側のディレクティブであり,公開する側のディレクティブではない。アプリケーションがインタフェースを公開するというように,アプリケーションとライブラリで依存関係が逆転しているイメージで理解するとよい。
jdepsコマンドを説明せよ
JDKに梱包されるツールで,クラスやモジュールの依存関係を調べることができる。
jdepsコマンドの-jdkinternalsオプションを説明せよ
JDKの非公開API(内部API)でクラスレベルの依存関係を調べることができる
jdepsコマンドの-verbose:classオプションを説明せよ
クラスレベルで依存関係を調べることができる。
jdepsコマンドの-apionlyオプションを説明せよ
publicなクラスのpublicまたはprotectedなメンバの依存関係を調べることができる
jdepsコマンドの-profileオプションを説明せよ
Javaには標準クラスライブラリのサブセット(組み合わせ)であるプロファイルという概念が存在するが,-profileオプションでは依存するモジュールやライブラリがとのようなプロファイルに属するのかを調べることができる。Full JREが最も多くのライブラリを含むプロファイルで,compact3→compact2→compact1の順で含まれるライブラリが少ないプロファイルとなる。
セキュアコーディング
セキュアコーディングプラクティスのうち最も重要と位置付けられているものを答えよ
入力を検証すること
Unicode正規化を行うクラスとメソッドを挙げよ
java.text.Normalizerクラスのnormalizeメソッド
セキュリティマネージャを説明せよ
Javaにおいて標準で利用できる機能で,適切なアクセス権限の監視を行うことができる。Permissionsに適切な権限を付与し,setPolicyした後にsetSecurityManagerすればよい。
// java.security.PolicyをextendsしてSamplePolicyクラスを定義しているとする
Permissions permissions = new Permissions();
permissions.add(new FilePermission("/", "read"));
Policy.setPolicy(new SamplePolicy(permissions));
System.setSecurityManager(new SecurityManager());
もしくは,起動時にオプションで指定することもできる。
// example.policyでpermissionのgrantを設定する
java -Djava.security.manager -Djava.security.policy=example.policy Sample
全ての権限を全てに付与するセキュリティポリシーファイルを書け
grant {
permission java.security.AllPermission;
}
/
へのreadをlib.jarだけに付与するセキュリティポリシーファイルを書け
grant codeBase "file:/path/to/lib.jar" {
permission java.io.FilePermission "/", "read";
}
アクセス先には権限が付与されているが自分に権限がない場合の回避策を述べよ
特権ブロックを利用して,呼び出し先の権限を使ってアクセスすればよい
AccessController.doPrivileged(new PrivilegedAction<Object>(){
@Override
public Object run() {
// ここに特権で実行した処理を書く
}
})
コンストラクタとimmutableの関係を説明せよ
コンストラクタを利用してインスタンスを生成させると,全てのフィールドがnullでないことを保証できないため,コンストラクタではなくビルダーを経由してインスタンスを生成するとimmutableであることが保証しやすい。
「フィールドをprivateで修飾し,アクセサメソッドを提供する」はリスクのある方針か
リスクのある方針である。アクセサメソッドとはsetterのことを指すが,たとえば参照型のフィールドを定義しているときに,そのフィールドに参照を渡してしまうと裏側で参照が繋がってしまい,副作用を誘発するリスクがある。そこで,フィールドには新しい参照を渡すようにコピーコンストラクタを実装するようにすればよい。同様に,getterでもコピーの参照を返すようにすればよい。
「原則的にクラスはprotectedで宣言する」は正しいか
正しくない。修飾子なしのデフォルト修飾子を利用するのがよい。protectedではデフォルト修飾子よりもアクセス権限を緩めているため。protectedという言葉に騙されないように。
機密情報が含まれるフィールドをシリアライズする際に行うべき対策を述べよ
transientで修飾してシリアライズの対象から外す
シリアライズ・プロキシ・パターンを説明せよ
シリアライズしたインスタンスをコンストラクタを経由せずにデシリアライズすると,コンストラクタで行われているバリデーションをすり抜けて不正なフィールドを持つインスタンスが生成されてしまうリスクがある。そこで,
- シリアライズ時に呼ばれるwriteReplaceメソッドで,シリアライズしたインスタンスを,同じフィールドの値を持ったstaticネストクラスとして保持する
- デシリアライズ時に呼ばれるreadResolveメソッドで,staticネストクラスとして保持しているインスタンスと同じフィールドの値を持ったエンクロージングクラスを,コンストラクタを経由したインスタンスとして戻す
ことにより,不正なインスタンスを生成されるリスクを軽減することができる。このデザインパターンをシリアライズ・プロキシ・パターンという。
デシリアライズ時に自動で呼ばれるreadObjectにInvalidObjectExceptionをスローするような実装を入れておけば,なお磐石の対策となります。
総仕上げ問題
Files.linesとFiles.readAllLinesの違いを説明せよ
- Files.lines:Pathsを受け取り,Stream<String>を返す
- Files.readAllLines:Pathsを受け取り,Listを返す
BufferedReaderのreadLineはStringを返すため注意する。
ServiceLoaderを使ってTestインタフェースの実装クラスのexecuteメソッドを呼び出す方法を述べよ
ServiceLoader<Test> loader = ServiceLoader.load(Test.class)
for (Test test : loader) {
test.execute();
}
// loaderにはTestインタフェースの実装クラスが格納されているためNG
loader.execute();
// loader.servicesというメソッドを実行する必要はないためNG
for (Test test : loader.services()) {
test.execute();
}
forEachは要素を追加・削除するものか
違う。終端操作であるため,要素の追加・削除は行わないし,ストリームに要素を戻すこともない。
public class A implements Comparator
で注意するべきポイントを述べよ
Comparatorでジェネリクスを指定していないため,Objectでしか受け取れない点。
public class A implements Comparator {
@Override
public int compare(Object o1, Object o2) {}
}
// ジェネリクスを指定すると下記のように書ける
public class A implements Comparator<String> {
@Override
public int compare(String s1, String s2) {
return s1.compareTo(s2);
}
}
// 匿名クラスを用いてもOK
new Comparator<String> () {
public int compare(String s1, String s2) {
return s1.compareTo(s2);
}
}
ボクシングとアンボクシングの制約を説明せよ
プリミティブ型とラッパークラス型の間でしか変換できない点。ラッパークラス型同士ではボクシングとアンボクシングはできないため注意する。
List<Integer> list = List.of(0, 1, 2, 3, 4);
// IntegerからDoubleというラッパークラス型の変換であるため不適切
Double a = list.get(0);
インタフェースのデフォルトメソッドと同名のメソッドを継承先でprivateに実装すると何が起きるか
実行時にIllegalAccessErrorがスローされる。コンパイルエラーではないため注意する。恐らく,継承先のクラスでprivateで定義すると,コンパイル時はオーバーライドとは見ないという仕組みなのだろう。ゆえに,コンパイルは通るが,実行時に具象側のprivateメソッドを呼び出そうとしてエラーとなる。
public interface Test {
public default void execute(String str) {
System.out.println("A");
}
}
public abstract class AbstractTest {
private void execute(String str) {
System.out.println("B");
}
}
public class Main extends AbstractTest implements Test {
public static void main(String[] args) {
// java.lang.IllegalAccessError
new Sample().execute("hello");
}
}
privateを修飾子なしに変更するとコンパイルエラーを引き起こします。これは,コンパイラがexecuteメソッドをオーバーライドの対象として捉えたからだと考えられます。privateをpublicに変更するとコンパイルも通り,実行エラーも出ずに"B"が出力されます。試験対策としては「継承先のメソッドでprivateが指定されている場合にはコンパイルエラーではなく実行時エラーとなる」と覚えてしまうとよいでしょう。
「機密情報が含まれたインスタンスは極力早くメモリから削除する」は正しいか
正しくない。フィールドはメモリから削除してもよいが,インスタンスはGCに任せる。
メソッドをsynchronizedにするとフィールドもsynchronizedになるか
ならない。フィールドにsynchronizedを付ける必要がある。
Streamで狙われやすい引っ掛けポイントを説明せよ
ジェネリクスを指定せずに宣言してgetPriceのような独自メソッドを実行しようとする点
Stream itemStream = items.stream();
// itemはObject型になるためgetPrice()が呼び出せずコンパイルエラー
itemStream.filter(item -> item.getPrice() > 200);
ラムダ式で -> System.out.println("A");
のように引数を空白にできるか
できない
列挙子のコンストラクタでアクセス修飾子を省略すると何が起きるか
自動でprivateが付与される。publicで宣言するとコンパイルエラーとなる。
インタフェースにはprotectedなメソッドは定義できるか
定義できない。publicまたはprivateなメソッドのみ定義可能。抽象クラスにはprotectedなメソッドを定義可能。
インタフェースはインスタンスフィールドを定義できるか
定義できない。インタフェースそのものはインスタンス化できないため。抽象クラスにはインスタンスフィールドを定義できる。
長さが2のArrayListの[3]に要素を追加しようとすると何が起きるか
IndexOutOfBoundsExceptionが発生する。[2]にnullを追加してくれるわけではない。
List.copyOfの引っ掛けを説明せよ
返り値はArrayListのようにaddできない配列である点。
ReentrantLockの宣言をメソッド内に移すと何が起きるか
ReentrantLockはメソッドをまたいだ非同期処理を実現するためのものであるため,意味がなくなる。
ReentrantLockのロック取得順のデフォルトは何か
順番はランダムで,これを不公平ロックという。逆にロック待ちの順にロックを取得している方式を公平ロックという。これらはReentrantLockのコンストラクタの引数にBooleanを与えることで制御できる。falseを与えれば不公平ロック,trueを与えれば公平ロックとなる。
collectメソッドの引っ掛けを説明せよ
collectメソッドは終端操作であるため,その後にcount()などを呼び出せない点。
// コンパイルエラー
.stream().collect(Collectors.toSet()).count();
Random.nextIntメソッドの引っ掛けを説明せよ
staticメソッドではないためRandom::nextIntでは参照できず,インスタンス化しなければならない点。
// OK
var r = new Random();
IntStream.generate(r::nextInt);
// NG
IntStream.generate(Random.nextInt);
IntStream.generate(Random::nextInt);
Stream型で数値計算する際の注意点を述べよ
Stream型にはaverageメソッドが定義されていない点。IntStreamには定義されている。Stream型の平均を求めるためには,
- collectの中でCollectors.averagingDoubleを利用する
- mapToIntによりIntStreamに変換してaverageを利用する
の方法がある。
mapメソッドにgetXXを渡すと何が起きるか
コンパイルエラー。getXXは引数を受け取らず,Function型ではないため。
Consumer a = (String s) -> System.out.print(s);
は正しいか
正しくない。Consumerにジェネリクスを与えていないため引数はObject型となるが,Stringにダウンキャストしているためコンパイルエラーとなる。
Locale.setDefaultの引数が2つの場合,それぞれについて説明せよ
// ({カテゴリ指定}, {Locale定数})
Locale.setDefault(Locale.Category.FORMAT, Locale.JAPANESE)
// 第一引数がカテゴリではないためコンパイルエラー
Locale.setDefault("en", Locale.JAPANESE)
languageとcountryのような誤解をしないようにしましょう。
moveメソッドの引っ掛けポイントを説明せよ
Paths src = Paths.get("a/sample.txt");
Paths dest = Paths.get("dest");
// a/sample.txtをdestという名前で移動させようとしている
Files.move(src, dest);
destにファイルが存在している場合はFileAlreadyExistsExceptionがスローされます。
宣言時にジェネリクス指定を省略する場合,左辺と右辺のどちらを省略するか
右辺を省略する
// OK
Comparator<Integer> a = new Comparator<> {}
// NG (左辺の時点でObject型と推論するため)
Comparator<> a = new Comparator<Integer> {}
「セキュアプログラミングではコンストラクタをprivateにする」は正しいか
正しくない。ビルダーパターンを利用すればよいが,それ以外の場合はインスタンス化できなくなってしまうため。
IntStreamでreduceは使えるか
使えない。Streamのみ。
Consumer c = var s -> System.out::println;
は正しいか
二点正しくない。
- ラムダ式で型宣言する場合は()をつけなければならない
- メソッド参照はラムダ式の代わりである
Consumer c = (var s) -> System.out.println(s);
Consumer c = System.out::println;
Function<int, int>は定義可能か
定義できない。ジェネリクスには参照型しか渡せない。