対策する資格
Java Silver ・ 近日ORACLE Silver ・ 近日
Java SE Gold1Z0-829

無料サンプル問題

登録なしで解ける良問。「正解・解説を見る」で監修解説とひっかけが開きます。

※ サンプルは実際の例題です。各問の「正解・解説を見る」を開くと、正解・詳細解説・ひっかけが表示されます。 全 20 問のうち 20 問を無料公開しています。
Q1ジェネリクス難易度 標準無料

次のコードのコンパイル・実行結果として正しいものを選べ。

1  import java.util.*;
2  public class Q1 {
3      public static void main(String[] args) {
4          List<? extends Number> nums = new ArrayList<Integer>();
5          nums.add(1);             // 行A
6          Number n = nums.get(0);  // 行B
7          System.out.println(n);
8      }
9  }
  1. A行A でコンパイルエラーになる
  2. B1 が出力される
  3. C行B でコンパイルエラーになる
  4. D実行時に ClassCastException がスローされる
正解・解説・誤答理由・ひっかけを見る▼ open
✓ 正解:AGold監修

解説

List<? extends Number> は「Number の何らかのサブ型のリスト」を表すが、 その具体型はコンパイル時に不明。よって要素を追加しようとすると、引数がどの型に当てはまるか保証できず、 addnull 以外を受け付けない(上限付きワイルドカードは プロデューサ=読み取り専用。PECS の "Producer Extends")。 行A の nums.add(1) は型推論で受理されずコンパイルエラー。行B の get は要素を Number として安全に取り出せるため問題ない。
各誤答が違う理由
  • B行A でコンパイルが通らないため、そもそも実行されず出力に至らない。
  • C行B の get? extends Number から Number として取り出すので完全に合法。エラーにならない。
  • Dコンパイルが通らない以上、実行時例外(ClassCastException)は発生し得ない。
ひっかけ: 「add は List のメソッドだから当然呼べる」と思い込む。? extends は読めるが書けない(null除く)。
実機確認の答え合わせ
javac でコンパイルすると行Aで
「incompatible types: int cannot be converted to CAP#1 / no suitable method found for add(int)」系のエラー。
→ コンパイル不成立(実行に至らない)。
Gold 保有者による書き下ろし解説・実機で検証済
Q2ジェネリクス難易度 高無料

次のコードのコンパイル・実行結果として正しいものを選べ。

1  import java.util.*;
2  public class Q2 {
3      public static void print(List<String>  list) { System.out.println("String"); }
4      public static void print(List<Integer> list) { System.out.println("Integer"); }
5      public static void main(String[] args) {
6          print(new ArrayList<String>());
7          print(new ArrayList<Integer>());
8      }
9  }
  1. AString 改行 Integer の順で出力される
  2. Bコンパイルエラー(2つの print が型消去後に同一シグネチャになる)
  3. C実行時に例外がスローされる
  4. DInteger のみ出力される
正解・解説・誤答理由・ひっかけを見る▼ open
✓ 正解:BGold監修

解説

ジェネリクスは型消去(type erasure)されるため、print(List<String>)print(List<Integer>) は どちらも実行時には print(List) という同一の消去シグネチャになる。 JLS §8.4.2 により、消去後シグネチャが衝突するオーバーロードは宣言できず、コンパイル時に "name clash" エラーになる。 メソッドのオーバーロードはコンパイル時の静的シグネチャで区別されるが、型パラメータは区別材料にならない。
各誤答が違う理由
  • A2つのメソッドが共存できず宣言段階でコンパイル不成立。実行されない。
  • C実行時の問題ではなくコンパイル時エラー。実行に到達しない。
  • Dオーバーロード自体が成立しないため、どちらか一方が選ばれることもない。
ひっかけ: 引数の総称型が違うのでオーバーロードできそうに見える。実体は型消去で同一=衝突。
実機確認の答え合わせ
javac Q2.java で
「name clash: print(List<Integer>) and print(List<String>) have the same erasure」
→ コンパイル不成立。
Gold 保有者による書き下ろし解説・実機で検証済
Q3Stream API難易度 高無料

次のコードの出力として正しいものを選べ。

1  import java.util.stream.*;
2  public class Q3 {
3      public static void main(String[] args) {
4          Stream.of("a", "b", "c", "d")
5                .filter(s -> { System.out.print("f" + s + " "); return true; })
6                .map(s -> { System.out.print("m" + s + " "); return s.toUpperCase(); })
7                .forEach(s -> System.out.print(s + " "));
8      }
9  }
  1. Afa fb fc fd ma mb mc md A B C D
  2. Bfa ma A fb mb B fc mc C fd md D
  3. CA B C D
  4. Dfa ma fb mb fc mc fd md A B C D
正解・解説・誤答理由・ひっかけを見る▼ open
✓ 正解:BGold監修

解説

Stream のパイプラインは「全要素にfilter→次に全要素にmap」という水平処理ではない。 1要素ずつパイプライン全体(filter→map→forEach)を縦に通してから次の要素へ進む(element-at-a-time / 縦方向の処理)。 中間操作は遅延評価され、終端操作 forEach が要素を1つ引くたびに上流が1要素分だけ評価される。 よって "a" が filter→map→forEach を通り(fa ma A)、続いて "b"(fb mb B)… となる。
各誤答が違う理由
  • A全filterを先に走らせる「水平処理」を仮定した誤り。実際は縦方向。
  • Cfilter/map 内の print も実行されるので、f*m* が出力に現れる。
  • Dfilter→map をまとめてから forEach をまとめて、という誤り。forEach も1要素ごとに即実行される。
ひっかけ: 中間操作を「配列のmap/filterのように一括」と捉える。Streamは終端の引きに応じた縦パイプ。
実機確認の答え合わせ
出力(末尾スペースあり):
fa ma A fb mb B fc mc C fd md D 
Gold 保有者による書き下ろし解説・実機で検証済
Q4Stream API / Optional難易度 高無料

次のコードの出力として正しいものを選べ。

1  import java.util.*;
2  import java.util.stream.*;
3  public class Q4 {
4      public static void main(String[] args) {
5          Optional<Integer> r1 = Stream.<Integer>of().reduce((a, b) -> a + b);
6          System.out.println(r1.isPresent());
7
8          int r2 = Stream.<Integer>of().reduce(0, (a, b) -> a + b);
9          System.out.println(r2);
10     }
11 }
  1. Atrue 改行 0
  2. Bfalse 改行 0
  3. Cfalse の後に例外がスローされる
  4. DNoSuchElementException がスローされる
正解・解説・誤答理由・ひっかけを見る▼ open
✓ 正解:BGold監修

解説

reduce には複数のオーバーロードがある。 1引数版 reduce(BinaryOperator) は初期値を持たないため、空ストリームでは「結果なし」を表す 空の Optional を返す(isPresent()false)。 一方 2引数版 reduce(identity, accumulator)identity(初期値)を持つため、 空ストリームではそのidentityをそのまま返す。よって r20。 identity を持つか否かで、空入力時の戻り(Optional か 値そのもの)が分かれるのが要点。
各誤答が違う理由
  • A1引数版は空ストリームで空Optionalを返すので isPresent()true でなく false
  • Cどちらの行も例外を投げない。1引数版は空Optionalを返すだけ、2引数版はidentityを返すだけ。
  • Dreduce は空でも例外を投げない(NoSuchElementExceptionOptional.get() を空に対して呼んだ場合などの話)。
ひっかけ: 「reduceは合計だから空なら0」と一括りにする。identityの有無で戻り型と空入力時挙動が変わる。
実機確認の答え合わせ
出力:
false
0
Gold 保有者による書き下ろし解説・実機で検証済
Q5並行処理(Atomic)難易度 標準無料

次のコードの出力として正しいものを選べ。

1  import java.util.concurrent.atomic.AtomicInteger;
2  public class Q5 {
3      public static void main(String[] args) {
4          AtomicInteger a = new AtomicInteger(0);
5          System.out.println(a.getAndIncrement());  // (1)
6          System.out.println(a.incrementAndGet());   // (2)
7          System.out.println(a.getAndAdd(5));         // (3)
8          System.out.println(a.get());                // (4)
9      }
10 }
  1. A0 / 2 / 2 / 7(上から順に)
  2. B1 / 2 / 7 / 7
  3. C0 / 1 / 2 / 7
  4. D1 / 1 / 7 / 7
正解・解説・誤答理由・ひっかけを見る▼ open
✓ 正解:AGold監修

解説

AtomicIntegergetAndXxx 系は更新前の値を返し、xxxAndGet 系は更新後の値を返す。

  • (1) getAndIncrement():現在0を返し → 値は1。出力 0
  • (2) incrementAndGet():値を2にして → 2を返す。出力 2
  • (3) getAndAdd(5):現在2を返し → 値は7。出力 2
  • (4) get():現在値7。出力 7
各誤答が違う理由
  • B(1)を「更新後の1」と取り違えた誤り。getAndIncrement は更新前の0を返す。
  • C(2)を「更新前の1」と取り違えた誤り。incrementAndGet は更新後の2を返す。
  • Dget系/AndGet系の返す値(前/後)を全体的に取り違えた誤り。
ひっかけ: メソッド名の語順(get-And-Increment と increment-And-Get)で「返すのが前か後か」が決まる。
実機確認の答え合わせ
出力(単一スレッドなので決定的):
0
2
2
7
Gold 保有者による書き下ろし解説・実機で検証済
Q6並行処理(CompletableFuture)難易度 高無料

次のコードの出力として正しいものを選べ。

1  import java.util.concurrent.*;
2  public class Q6 {
3      public static void main(String[] args) throws Exception {
4          CompletableFuture<Integer> cf = CompletableFuture
5              .<Integer>supplyAsync(() -> { throw new RuntimeException("boom"); })
6              .handle((res, ex) -> ex != null ? -1 : res)
7              .thenApply(n -> n + 100);
8          System.out.println(cf.get());
9      }
10 }
  1. Acf.get()ExecutionException をスローする
  2. B99
  3. C-1
  4. D101
正解・解説・誤答理由・ひっかけを見る▼ open
✓ 正解:BGold監修

解説

supplyAsync 内の Supplier が例外を投げると、そのステージは例外完了する。 handle((res, ex) -> ...)正常・例外のどちらの場合も必ず呼ばれるのが特徴で、 例外時は res=null, ex=例外 が渡る。本コードは ex != null なので -1 を返し、 例外を回復して正常完了の値 -1 に置き換える。続く thenApply-1 + 100 = 99 を生成。 cf.get() は正常完了なので例外を投げず 99 を返す。
各誤答が違う理由
  • Ahandle が例外を吸収して値に変換するため、下流に例外は伝播せず get() は例外を投げない。(仮に thenApply だけなら例外が伝播し A になる ― そこが対比のポイント)
  • Chandle で -1 に回復した後、thenApply(+100) が必ず適用されるため最終値は -1 ではない。
  • D1 + 100 としてしまう誤り。回復値は -1 なので -1 + 100 = 99
ひっかけ: handleexceptionally/whenComplete の違い。handle は両ケースで呼ばれ<戻り値で次ステージの値を差し替える>。whenComplete は副作用のみで値を変えない。
実機確認の答え合わせ
出力:99
(注:handleに渡るのは ex != null の真偽だけが効くため、例外がCompletionExceptionで包まれるか否かに関わらず結果は不変)
Gold 保有者による書き下ろし解説・実機で検証済
Q7sealed / record / instanceofパターン難易度 標準無料

次のコード(Java 17)の出力として正しいものを選べ。

 1  public class Q7 {
 2      sealed interface Shape permits Circle, Square {}
 3      record Circle(double radius) implements Shape {}
 4      record Square(double side) implements Shape {}
 5
 6      static String describe(Shape s) {
 7          if (s instanceof Circle c && c.radius() > 10) return "big circle";
 8          else if (s instanceof Circle c)  return "small circle";
 9          else if (s instanceof Square sq) return "square " + sq.side();
10          return "unknown";
11      }
12      public static void main(String[] args) {
13          System.out.println(describe(new Circle(5)));
14          System.out.println(describe(new Square(3)));
15      }
16  }
  1. Asmall circle 改行 square 3
  2. Bsmall circle 改行 square 3.0
  3. Cbig circle 改行 square 3.0
  4. Dコンパイルエラー
正解・解説・誤答理由・ひっかけを見る▼ open
✓ 正解:BGold監修

解説

sealed interfacepermits で許可された型だけが実装でき、record は暗黙に final なので sealed の許可対象として適格(コンパイルは通る)。 instanceof Circle cパターンマッチング for instanceof(SE16以降で標準)で、束縛変数 c がそのブロック内で使える。 Circle(5) は radius=5 で > 10 でないため「small circle」。 Square(3)record のアクセサ side()double を返すため 3.0 となり、 文字列連結で "square " + 3.0 = "square 3.0"
各誤答が違う理由
  • Asidedouble 型。3 ではなく 3.0 が連結される。
  • Cradius=5 は > 10 を満たさないので「big circle」にならず「small circle」。
  • Dsealed+record+instanceofパターンはいずれもJava 17で標準。正しく書かれており、コンパイルは通る。
ひっかけ: double リテラルは整数値でも 3.0 と表示される(プリミティブ型の連結)。recordアクセサは field() 形式(field ではない)。
実機確認の答え合わせ
出力:
small circle
square 3.0
Gold 保有者による書き下ろし解説・実機で検証済
Q8record難易度 標準無料

次のコードの出力として正しいものを選べ。

 1  public class Q8 {
 2      record Point(int x, int y) {
 3          Point {                          // コンパクトコンストラクタ
 4              if (x < 0 || y < 0) throw new IllegalArgumentException();
 5          }
 6      }
 7      public static void main(String[] args) {
 8          Point p1 = new Point(1, 2);
 9          Point p2 = new Point(1, 2);
10          System.out.println(p1.equals(p2));
11          System.out.println(p1 == p2);
12          System.out.println(p1);
13      }
14  }
  1. Atrue / false / Point[x=1, y=2]
  2. Btrue / true / Point[x=1, y=2]
  3. Cfalse / false / Point@1b6d3586(ハッシュ表記)
  4. Dtrue / false / Point(x=1, y=2)
正解・解説・誤答理由・ひっかけを見る▼ open
✓ 正解:AGold監修

解説

recordequals/hashCode/toString全コンポーネントに基づき自動生成する。 p1.equals(p2) は x・y が等しいので true(値ベースの等価)。 p1 == p2 は別インスタンスへの参照比較なので false。 自動生成 toString の形式は RecordName[component1=値, component2=値](角括弧・=・カンマ区切り)で、ここでは Point[x=1, y=2]
各誤答が違う理由
  • B== は参照比較。new で別々に生成しているので同一参照ではなく false
  • Crecord は toString を自動生成するので Object 既定のハッシュ表記にはならない。equals も自動生成で値比較=falseでない。
  • Drecord の toString は丸括弧 (...) ではなく角括弧 [...] を使う。
ひっかけ: 自動生成 toString の正確な書式(角括弧 Point[x=1, y=2])。equals=値、===参照。
実機確認の答え合わせ
出力:
true
false
Point[x=1, y=2]
Gold 保有者による書き下ろし解説・実機で検証済
Q9try-with-resources難易度 標準無料

次のコードの出力として正しいものを選べ。

 1  public class Q9 {
 2      static class Res implements AutoCloseable {
 3          String name;
 4          Res(String name) { this.name = name; System.out.println("open " + name); }
 5          public void close() { System.out.println("close " + name); }
 6      }
 7      public static void main(String[] args) {
 8          try (Res a = new Res("A"); Res b = new Res("B")) {
 9              System.out.println("body");
10          }
11      }
12  }
  1. Aopen A / open B / body / close A / close B
  2. Bopen A / open B / body / close B / close A
  3. Copen B / open A / body / close A / close B
  4. Dopen A / open B / close B / close A / body
正解・解説・誤答理由・ひっかけを見る▼ open
✓ 正解:BGold監修

解説

try-with-resources のリソースは宣言順に初期化され(open A → open B)、 try ブロック本体が実行された後(body)、宣言と逆順に close() される(close B → close A)。 JLS §14.20.3 で「リソースは宣言と逆順にクローズされる」と規定されている(後で開いたものを先に閉じる=スタック的解放)。
各誤答が違う理由
  • Aclose が宣言順(A→B)になっている誤り。実際は逆順(B→A)。
  • C初期化(open)も逆順だとした誤り。openは宣言順(A→B)。
  • Dbody より先に close するとした誤り。close は try 本体の完了後。
ひっかけ: open は宣言順・close は逆順。「初期化と解放で順序が逆になる」点。
実機確認の答え合わせ
出力:
open A
open B
body
close B
close A
Gold 保有者による書き下ろし解説・実機で検証済
Q10try-with-resources / 抑制例外難易度 高無料

次のコードの出力として正しいものを選べ。

 1  public class Q10 {
 2      static class Res implements AutoCloseable {
 3          public void close() { throw new RuntimeException("close-fail"); }
 4      }
 5      public static void main(String[] args) {
 6          try (Res r = new Res()) {
 7              throw new RuntimeException("body-fail");
 8          } catch (Exception e) {
 9              System.out.println(e.getMessage());
10              for (Throwable t : e.getSuppressed())
11                  System.out.println("suppressed: " + t.getMessage());
12          }
13      }
14  }
  1. Aclose-fail 改行 suppressed: body-fail
  2. Bbody-fail 改行 suppressed: close-fail
  3. Cbody-fail のみ
  4. Dclose-fail のみ
正解・解説・誤答理由・ひっかけを見る▼ open
✓ 正解:BGold監修

解説

try-with-resources で本体(try ブロック)の例外と close() の例外が両方発生した場合、 本体の例外が主(プライマリ)として伝播し、close() 側の例外は抑制例外(suppressed)として 主例外に addSuppressed で付加される(JLS §14.20.3)。 よって catch が捕捉する e"body-fail"getMessage())、 e.getSuppressed() には "close-fail" が入る。
各誤答が違う理由
  • A主と抑制が逆。本体の例外が主、close の例外が抑制される(close が主ではない)。
  • Cclose の例外は握り潰されず getSuppressed() で取得できる。出力に suppressed: close-fail が現れる。
  • Dclose の例外が主として捕捉されるとした誤り。捕捉されるのは本体の body-fail
ひっかけ: 「最後に投げられた close の例外が勝つ」と思いがち。実際は本体例外が主、close例外が抑制
実機確認の答え合わせ
出力:
body-fail
suppressed: close-fail
Gold 保有者による書き下ろし解説・実機で検証済
Q11モジュール(requires transitive)難易度 標準無料

3つのモジュールが次の module-info.java を持つ。

1   // ===== モジュール com.foo.util =====
2   module com.foo.util {
3       exports com.foo.util;            // public class Token を含む
4   }
5
6   // ===== モジュール com.foo.service =====
7   module com.foo.service {
8       requires transitive com.foo.util;
9       exports com.foo.service;         // Svc.make() は com.foo.util.Token を返す
10  }
11
12  // ===== モジュール com.foo.app =====
13  module com.foo.app {
14      requires com.foo.service;        // ← com.foo.util は requires していない
15  }

com.foo.app 内のコードが、次のように書かれている。正しい記述を選べ。

16  import com.foo.service.Svc;
17  import com.foo.util.Token;           // util を requires していないが…
18  Token t = Svc.make();
19  System.out.println(t.id());
  1. Acom.foo.appcom.foo.util.Token を参照できない。module-inforequires com.foo.util; を足さないとコンパイルエラー。
  2. Bcom.foo.servicerequires transitive com.foo.util によって暗黙的可読性(implied readability)が com.foo.app へ伝播するため、追加の requires なしで Token を参照でき、コンパイル・実行とも成功する。
  3. Ccom.foo.utilexports だけでなく com.foo.app 向けに opens しないと Token を参照できない。
  4. Drequires transitive は不正な構文であり com.foo.service のコンパイル時点でエラーになる。
正解・解説・誤答理由・ひっかけを見る▼ open
✓ 正解:BGold監修

解説

モジュールの requires は「読む(read)」関係を1段だけ作る。 通常 com.foo.appcom.foo.util の型を使うには、app 自身が requires com.foo.util する必要がある。

ところが requires transitive を付けると「自分を読むモジュールにも、その依存先を読ませる」という暗黙的可読性(implied readability)が成立する。

ここでは com.foo.servicerequires transitive com.foo.util しているので、 service を読む者(=app)は自動的に util も読める。よって app は requires com.foo.util を書かずとも Token を参照でき、コンパイル・実行とも成功する。

transitive は「公開APIの戻り値・引数に他モジュールの型が露出する」ときに使うのが定石(ここでは Svc.make()Token を返す)。これを付けないと、service を使う全モジュールが util を個別に requires する羽目になる。

各誤答が違う理由
  • Aこれは transitive が無い(=普通の requires com.foo.util)場合の挙動。transitive 付きでは追加の requires は不要。
  • Copens は実行時リフレクション用。コンパイル時の通常の型参照には無関係で、exports で足りている。
  • Drequires transitive は正規の構文(JLS / JPMS)。コンパイルは通る。
ひっかけ: 「使う型のモジュールは必ず自分で requires する」と機械的に覚えていると A を選ぶ。transitive は依存を“横流し”できる例外。逆に service が transitive を外すと、app は即コンパイルエラー(下記・実機確認の負側)。
実機確認の答え合わせ
javac --module-source-path ... --module com.foo.app → コンパイル成功
java --module com.foo.app/...Main → 出力:TKN

【負側の確認】service を「requires com.foo.util(transitive なし)」に変えて再ビルドすると app がコンパイル失敗:
Main.java:17: error: package com.foo.util does not exist
import com.foo.util.Token;
                   ^
Gold 保有者による書き下ろし解説・実機で検証済
Q12モジュール(exports vs opens)難易度 高無料

モジュール com.lib が次のように宣言されている(exports は無く opens のみ)。

1   module com.lib {
2       opens com.lib.model;             // public class Entity を含む
3   }

別モジュール com.clientrequires com.lib;)が、ソースコードで次のように com.lib.model.Entity を直接 import して使おうとする。結果は?

4   package com.client;
5   import com.lib.model.Entity;
6   public class Main {
7       public static void main(String[] a) {
8           System.out.println(new Entity().name());
9       }
10  }
  1. Aコンパイル成功。opens により com.clientcom.lib.model の public 型をコンパイル時に参照できる。
  2. Bコンパイルエラー。opens実行時のリフレクティブアクセスのみを許可し、コンパイル時/通常コードの型参照は exports でなければ許可されない。
  3. Cコンパイルは成功するが、実行時に IllegalAccessException がスローされる。
  4. Dopensexports は同義であり、どちらを書いても結果は同じ。
正解・解説・誤答理由・ひっかけを見る▼ open
✓ 正解:BGold監修

解説

exportsopens は「公開」の意味がまったく違う。1段ずつ整理する。

exports com.x = 他モジュールが com.xpublic 型の public メンバを、コンパイル時も実行時も通常コードから使える。
opens com.x = 他モジュールが com.x実行時の深いリフレクションsetAccessible(true) を含む。private にも届く)でアクセスできる。ただし通常のコンパイル時の型参照は許可しない

opens はフレームワーク(Jackson の JSON マッピング、JPA、DI 等)が privateフィールドにリフレクションで触るために使うもの。 本問の com.clientimport して new Entity() という通常のコンパイル時参照をしているので、opens しかない com.lib.model は「コンパイルからは見えない」。よってコンパイルエラー

各誤答が違う理由
  • Aopens はコンパイル時可視性を与えない。通常の import/new は通らない。
  • Cそもそもコンパイルが通らないので、実行時例外に至らない。
  • D同義ではない。exports=通常アクセス(public のみ)、opens=実行時リフレクション(private 含む)。役割が直交する。
ひっかけ:opens の方が private まで開くんだから、当然 public も使えるはず(exports の上位互換)」という直感。実際は軸が別で、コンパイル時参照は exports 専用。両方欲しければ両方書く(exportsopens)。
実機確認の答え合わせ
opens のみの com.lib.model を client が import → javac がコンパイルエラー:
Main.java:5: error: package com.lib.model does not exist
import com.lib.model.Entity;
                    ^
Main.java:8: error: cannot find symbol  (class Entity)
Gold 保有者による書き下ろし解説・実機で検証済
Q13モジュール(サービス provides / uses)難易度 高無料

サービスを3モジュールで構成する。

1   // ===== API モジュール =====
2   module com.pay.api {
3       exports com.pay.api;             // interface PaymentService
4   }
5   // ===== プロバイダ モジュール =====
6   module com.pay.stripe {
7       requires com.pay.api;
8       provides com.pay.api.PaymentService
9           with com.pay.stripe.StripePayment;   // 実装クラス
10  }
11  // ===== コンシューマ モジュール =====
12  module com.shop {
13      requires com.pay.api;
14      uses com.pay.api.PaymentService;
15  }

com.shopServiceLoader.load(PaymentService.class) でプロバイダを取得する。StripePaymentServiceLoader で正しく発見・生成されるための要件として正しいものを選べ。

  1. Acom.pay.stripe は実装クラス StripePaymentexports しなければならない(さもないと ServiceLoader が見つけられない)。
  2. BStripePayment が public で、public な引数なしコンストラクタ(または public static な provider() メソッド)を持てばよい。実装クラスを exports する必要はない。
  3. Ccom.shopmodule-inforequires com.pay.stripe; も追加しないとプロバイダを発見できない。
  4. Duses 宣言は不要。ServiceLoader.loaduses なしでも全モジュールを走査して自動発見する。
正解・解説・誤答理由・ひっかけを見る▼ open
✓ 正解:BGold監修

解説

JPMS のサービス機構は「実装を隠したまま、インタフェースだけで疎結合に繋ぐ」のが目的。配線は module-info の宣言で行う。

プロバイダ側の provides … with … が「このインタフェースの実装はこれ」とモジュールシステムに登録する。 だから実装クラスを exports する必要はない(むしろ exports すると実装が漏れて疎結合が崩れる)。モジュールシステムが実装をリフレクティブに生成するため、実装は public な no-arg コンストラクタpublic static provider() を持てばよい。

コンシューマ側は uses で「このサービスを使う」と宣言する。これが ServiceLoader.load(...) の前提になる。 そしてコンシューマはプロバイダ・モジュールを requires しない――それこそが「実装を知らずに差し替え可能」という疎結合の核心。

各誤答が違う理由
  • A実装クラスの exports は不要。provides … with … が登録を担い、生成はモジュールシステムが行う。exports は逆に実装を露出させる悪手。
  • Cコンシューマがプロバイダを requires したら、実装に直接依存してしまい疎結合の意味が消える。requires は不要(実際に無しで動く)。
  • Duses は必須。これが無いと ServiceLoader.load はサービスを発見できない(モジュールグラフ上の宣言が前提)。
ひっかけ: 「使うんだから実装モジュールを requires/実装を exports するのが当然」という非モジュール時代の感覚。サービスはrequires も exports も無しで、provides/withuses だけで繋がる。これが SPI(Service Provider Interface)の旨味。
実機確認の答え合わせ
StripePayment を exports せず/com.shop は com.pay.stripe を requires せず、
provides/with+uses だけで構成 → java --module com.shop/...Main の出力:

stripe-paid

(=ServiceLoader がプロバイダを発見・生成できた。実装クラスは公開していない)
※ もし StripePayment に public 引数なしコンストラクタが無いと、生成時に ServiceConfigurationError になる。
Gold 保有者による書き下ろし解説・実機で検証済
Q14NIO.2(Path 操作)難易度 高無料

次のコードの出力として正しいものを選べ(UNIX系ファイルシステム=区切り文字は /)。

1   import java.nio.file.*;
2   public class Q4 {
3       public static void main(String[] a) {
4           Path p = Paths.get("/home/user/../data/./file.txt");
5           System.out.println(p.normalize());
6           Path x = Paths.get("/a/b");
7           Path y = Paths.get("/a/c/d");
8           System.out.println(x.relativize(y));
9           System.out.println(x.resolve("x/y"));
10      }
11  }
  1. A/home/data/file.txt../c/d/a/b/x/y
  2. B/home/user/data/file.txt../c/d/a/b/x/y
  3. C/home/data/file.txt../../c/d/a/b/x/y
  4. D/home/data/file.txt../c/dx/y
正解・解説・誤答理由・ひっかけを見る▼ open
✓ 正解:AGold監修

解説

Path の3メソッドはどれも純粋に文字列(パス要素)レベルの計算で、実ファイルの存在は問わない。

normalize():冗長な要素を畳む。.(カレント)は除去、..(親)は直前の要素を1つ打ち消す。
/home/user/../data/./file.txtuser/.. が相殺、. が消えて → /home/data/file.txt

x.relativize(y):「x から見た y への相対パス」を作る。
x=/a/by=/a/c/d。共通の先頭は /a。x はそこから b が1つ余る → 1つ上がる(..。y はそこから c/d → 下りる。結果 ../c/d

x.resolve("x/y"):x に相対パスを連結する(引数が絶対パスならそれをそのまま返す)。
/a/bx/y/a/b/x/y

各誤答が違う理由
  • Bnormalize().. を畳まない前提。実際は user が打ち消され /home/data/...
  • Crelativize の「上がる回数」を誤算。x で余るのは b の1つだけなので .. は1個(../c/d)。../../ は2つ余る場合。
  • Dresolve("x/y") は引数が相対なので連結される。引数をそのまま返すのは絶対パスを渡したとき
ひっかけ:relativize の方向(「x→y」であって「y→x」ではない)。② resolve は引数が絶対パスのときだけ引数を返し、相対なら連結。③ これらは I/O せず純粋に要素計算(実体が無くても動く)。
実機確認の答え合わせ
出力:
/home/data/file.txt
../c/d
/a/b/x/y
(注:toString の区切り文字はプラットフォーム依存。Windows では \ になる)
Gold 保有者による書き下ろし解説・実機で検証済
Q15NIO.2(Files.walk)難易度 標準無料

空のディレクトリを新規作成し、その中に下図の構造を作ってから Files.walk で要素数を数える。出力は?

 1  import java.nio.file.*;
 2  import java.io.IOException;
 3  public class Q5 {
 4      public static void main(String[] a) throws IOException {
 5          Path dir = Files.createTempDirectory("q5");
 6          Files.createFile(dir.resolve("a.txt"));
 7          Files.createDirectory(dir.resolve("sub"));
 8          Files.createFile(dir.resolve("sub/b.txt"));
 9          long count = Files.walk(dir).count();
10          System.out.println(count);
11      }
12  }

  作られる構造:
    <dir>/
    ├── a.txt
    └── sub/
        └── b.txt
  1. A4
  2. B3
  3. C2
  4. Dコンパイルエラー(Files.walkIOException を処理していない)
正解・解説・誤答理由・ひっかけを見る▼ open
✓ 正解:AGold監修

解説

Files.walk(start)start を起点に深さ優先でファイルツリーを再帰的に走査する Stream<Path> を返す。重要なのは起点ディレクトリ自身が最初の要素として含まれる点と、深さ制限を付けなければ全階層まで降りる点。

列挙される要素は次の4つ:

  • <dir>(起点ディレクトリ自身)
  • <dir>/a.txt
  • <dir>/sub(サブディレクトリ自身)
  • <dir>/sub/b.txt

よって count()4。(列挙順は保証されないが、件数は決定的。)

mainthrows IOException を宣言しているのでコンパイルは通る(Files.walkcreateFile 等は検査例外 IOException を投げる)。

各誤答が違う理由
  • B起点ディレクトリ自身を数え忘れる典型ミス。walk は起点を含む。
  • Csub 配下に降りない(浅い列挙)と誤解。walk は既定で全階層を再帰する(深さ制限は Files.walk(dir, maxDepth) で指定)。
  • Dmainthrows IOException 済みなので検査例外は処理されており、コンパイルエラーにならない。
ひっかけ: ①「中身の数」と思って起点ディレクトリを数え落とす(実際は起点も1件)。②サブディレクトリ自体も1件として数える。③ Files.walk が返す Streamclose すべきリソース(本問では件数には影響しないが、実務では try-with-resources 推奨)。
実機確認の答え合わせ
出力:
4
公式ドキュメント・関連Files.walk(Path, FileVisitOption...)
Gold 保有者による書き下ろし解説・実機で検証済
Q16java.time(Period / ChronoUnit)難易度 高無料

次のコードの出力として正しいものを選べ。

1   import java.time.*;
2   import java.time.temporal.ChronoUnit;
3   public class Q6 {
4       public static void main(String[] a) {
5           LocalDate d1 = LocalDate.of(2026, 1, 31);
6           LocalDate d2 = LocalDate.of(2026, 3, 1);
7           Period p = Period.between(d1, d2);
8           System.out.println(p.getMonths() + " " + p.getDays());
9           System.out.println(ChronoUnit.DAYS.between(d1, d2));
10      }
11  }

(2026年は閏年ではなく、2月は28日まで)

  1. A1 1 改行 29
  2. B1 1 改行 28
  3. C0 29 改行 29
  4. D1 0 改行 29
正解・解説・誤答理由・ひっかけを見る▼ open
✓ 正解:AGold監修

解説

PeriodChronoUnit.DAYS は「期間」の数え方が根本的に違う。

Period.between(d1, d2)カレンダー的な年・月・日に分解する。 2026-01-31 → 2026-03-01 を計算する内部手順は:

  • まず月差を取る:1月→3月で「2か月」、ただし日が 1 - 31 = マイナス なので月を1つ繰り下げて「1か月」。
  • 繰り下げた基準 d1 + 1か月 = 2026-02-31? → 2月末に丸めて 2026-02-28
  • そこから 2026-03-01 までの残り日数 = 1日

よって Period は「1か月1日」。getMonths()=1getDays()=1 → 出力 1 1

ChronoUnit.DAYS.between(d1, d2)実日数(通算日)を返す。 1月31日 → 2月28日が28日、さらに3月1日まで1日で 29日

各誤答が違う理由
  • B通算日を28と誤算。2月28日からさらに3月1日まで1日あるので29。
  • CPeriod を「0か月29日」と捉える誤り。Period は年月日に正規化するため、29日ではなく「1か月1日」になる。
  • D残り日数を0と誤算。月末丸め(2/28)後、3/1まで1日残るので getDays()=1
ひっかけ: 最大の罠は 「1月31日 + 1か月 = 2月28日」という月末丸めPeriod は実日数ではなくカレンダー単位なので、月の長さの違いを吸収して「1か月1日」になる。一方 ChronoUnit.DAYS は純粋な日数(29)。同じ2日付でも「期間の表し方」で答えが変わるのが核心。
実機確認の答え合わせ
出力:
1 1
29
公式ドキュメント・関連Period.between()ChronoUnit.between()
Gold 保有者による書き下ろし解説・実機で検証済
Q17java.time(ZonedDateTime / DST)難易度 高無料

米国 America/New_York2026年3月8日 午前2:00 に夏時間(DST)へ切替(時計が2:00→3:00へ1時間進む)。次のコードの出力は?

1   import java.time.*;
2   public class Q7 {
3       public static void main(String[] a) {
4           ZoneId ny = ZoneId.of("America/New_York");
5           ZonedDateTime z =
6               ZonedDateTime.of(2026, 3, 7, 12, 0, 0, 0, ny);  // 3/7 12:00(切替前日の正午)
7           ZonedDateTime d = z.plusDays(1);    // 「1日」足す
8           ZonedDateTime h = z.plusHours(24);  // 「24時間」足す
9           System.out.println(d.getHour());
10          System.out.println(h.getHour());
11      }
12  }
  1. A12 改行 12
  2. B12 改行 13
  3. C13 改行 13
  4. D11 改行 12
正解・解説・誤答理由・ひっかけを見る▼ open
✓ 正解:BGold監修

解説

ZonedDateTime の加算メソッドは、操作する「時間軸」が2系統に分かれる。これが DST をまたぐと結果を分ける。

日・週・月・年系(plusDays など)=ローカル時間軸(local time-line)で計算する。「壁の時計を1日進める」イメージ。
3/7 12:00 +1日 → 3/8 12:00(DST 切替後でも、壁時計の“12:00”はそのまま)。getHour()=12

時・分・秒系(plusHours など)=インスタント時間軸(instant time-line)で計算する。「実時間でちょうど24時間後」。
3/8 の 2:00→3:00 で1時間が“消える”ため、実時間24時間後の壁時計は25時間進んだように見え → 3/8 13:00getHour()=13

つまり 「1日」≠「24時間」。DST 切替日だけは plusDays(1)plusHours(24) が壁時計で1時間ずれる。

各誤答が違う理由
  • AplusHours(24) もローカル時間軸だと誤解。時・分・秒系はインスタント時間軸なので、DST で1時間ずれて13になる。
  • CplusDays(1) までインスタント計算だと誤解。日・月・年系はローカル時間軸なので壁時計12:00を維持する。
  • D進む向き(春の繰り上げ=時計が進む)を逆(繰り下げ)に取った誤り。春のDSTは1時間“失う”ので壁時計はむしろ先へ。
ひっかけ: 「24時間 = 1日」という常識。ZonedDateTime では plusDays=ローカル軸(カレンダー)/plusHours=インスタント軸(実時間) で、DST 境界をまたぐと両者が分岐する(公式 Javadoc も「adding one day is not the same as adding 24 hours」と明記)。
実機確認の答え合わせ
出力:
12
13
(参考・完全な値)
plusDays(1)  → 2026-03-08T12:00-04:00[America/New_York]
plusHours(24)→ 2026-03-08T13:00-04:00[America/New_York]
※ どちらもオフセットは -04:00(EDT=夏時間)になっている点に注目。
Gold 保有者による書き下ろし解説・実機で検証済
Q18関数型(Function 合成)難易度 標準無料

次のコードの出力として正しいものを選べ。

1   import java.util.function.*;
2   public class Q8 {
3       public static void main(String[] a) {
4           Function<Integer,Integer> f = x -> x + 1;
5           Function<Integer,Integer> g = x -> x * 2;
6           System.out.println(f.andThen(g).apply(3));
7           System.out.println(f.compose(g).apply(3));
8       }
9   }
  1. A8 改行 7
  2. B7 改行 8
  3. C8 改行 8
  4. D7 改行 7
正解・解説・誤答理由・ひっかけを見る▼ open
✓ 正解:AGold監修

解説

Function の合成は2方向あり、「どちらを先に適用するか」が逆になる。

f.andThen(g) = 「f を先、その後 g」(and-then=そのあと)。
apply(3) → f(3)=4 → g(4)=8

f.compose(g) = 「g を先、その後 f」(compose=g∘f の数学的合成順で、引数に近い g が先)。
apply(3) → g(3)=6 → f(6)=7

覚え方:a.andThen(b) は「a→b(左から右)」、a.compose(b) は「b→a(右から左)」。

各誤答が違う理由
  • BandThen と compose の結果を取り違え。andThen は8、compose は7。
  • Ccompose も「f→g」と誤解。compose は引数側(g)が先で f(6)=7。
  • DandThen も「g→f」と誤解。andThen は f が先で g(4)=8。
ひっかけ: andThencompose の適用順は真逆f.andThen(g)=f→g、f.compose(g)=g→f。名前の語感(compose=合成)で順序を取り違えやすい。
実機確認の答え合わせ
出力:
8
7
公式ドキュメント・関連Function.andThen()Function.compose()
Gold 保有者による書き下ろし解説・実機で検証済
Q19関数型(プリミティブ特化)難易度 高無料

次のコードの出力として正しいものを選べ。

1   import java.util.function.*;
2   public class Q9 {
3       public static void main(String[] a) {
4           IntFunction<String> f = i -> "n" + i;
5           ToIntFunction<String> g = String::length;
6           Supplier<Integer> s = () -> 42;
7           System.out.println(f.apply(7));
8           System.out.println(g.applyAsInt("hello"));
9           System.out.println(s.get() + 1);
10      }
11  }
  1. An7 改行 5 改行 43
  2. Bn7 改行 5 改行 42
  3. Cコンパイルエラー:IntFunctionapply は無く applyAsInt が正しい。
  4. Dコンパイルエラー:ToIntFunctionapplyAsInt は無く apply が正しい。
正解・解説・誤答理由・ひっかけを見る▼ open
✓ 正解:AGold監修

解説

プリミティブ特化の関数型インタフェースは、「どこがプリミティブか(入力か/出力か)」で抽象メソッド名が決まる。これを正確に区別できるかがこの問題の核心。

  • IntFunction<R>入力が int、出力が参照型 R。抽象メソッドは R apply(int)
    f.apply(7)"n" + 7n7
  • ToIntFunction<T>入力が参照型 T、出力が int。抽象メソッドは int applyAsInt(T)
    g.applyAsInt("hello")"hello".length()5
  • Supplier<Integer>:抽象メソッドは Integer get()
    s.get()Integer の 42。+ 1 で自動アンボクシングされ 43

原則:「To〜」が付く型は出力がプリミティブ=applyAsXxx/「〜Function」で入力だけプリミティブなら apply(戻りは参照型 R)

各誤答が違う理由
  • Bs.get()Integer 42。+ 1 でアンボクシングされ 43 になる(42 のままにはならない)。
  • CIntFunction の抽象メソッドは apply(int)正しいapplyAsInt を持つのは「出力が int」の ToIntFunction 等。よってコンパイルは通る。
  • DToIntFunction の抽象メソッドは applyAsInt(T)正しい。出力が int だから applyAsInt。コンパイルは通る。
ひっかけ: 「Int が付くから全部 applyAsInt」と短絡する。プリミティブが“出力側”のときだけ applyAsInt。入力だけ int の IntFunction は戻りが参照型なので普通の apply。さらに Supplier<Integer>get()getAsInt()IntSupplier の方)。
実機確認の答え合わせ
出力:
n7
5
43
Gold 保有者による書き下ろし解説・実機で検証済
Q20ローカライゼーション(NumberFormat 通貨)難易度 標準無料

次のコードの出力として正しいものを選べ。

1   import java.text.NumberFormat;
2   import java.util.Locale;
3   public class Q10 {
4       public static void main(String[] a) {
5           double v = 1234.56;
6           NumberFormat jp = NumberFormat.getCurrencyInstance(Locale.JAPAN);
7           NumberFormat us = NumberFormat.getCurrencyInstance(Locale.US);
8           System.out.println(jp.format(v));
9           System.out.println(us.format(v));
10      }
11  }
  1. A¥1,235 改行 $1,234.56
  2. B¥1,234.56 改行 $1,234.56
  3. CJPY1,235 改行 USD1,234.56
  4. D¥1,234 改行 $1,234.50
正解・解説・誤答理由・ひっかけを見る▼ open
✓ 正解:AGold監修

解説

NumberFormat.getCurrencyInstance(locale)ロケールごとの通貨書式を返す。鍵は通貨ごとの「小数桁数(default fraction digits)」が違うこと。

日本円(JPY)の標準小数桁数は 0。よって 1234.56 は小数を四捨五入して整数になり、3桁区切り・円記号を付けて ¥1,235(.56 は切り上げ方向で 1235)。

米ドル(USD)の標準小数桁数は 2。よって 1234.56 はそのまま2桁で $1,234.56

同じ 1234.56 でも、通貨の小数桁数の違いだけで桁が変わるのがこの問題のポイント。

各誤答が違う理由
  • B円も小数2桁で出るとした誤り。JPY は小数桁0なので 1234.561,235
  • C通貨「記号」ではなく ISO コード(JPY/USD)が出るとした誤り。getCurrencyInstance は記号(¥/$)を使う。
  • D円を 1,234(切り捨て)かつドルを .50 とした誤り。1234.56 の四捨五入で円は 1,235、ドルは 1,234.56
ひっかけ: ①通貨で小数桁数が違う(JPY=0/USD=2)。②記号は ISO コードでなくロケール記号( は全角の円記号 U+FFE5、$)。
なお、円記号が全角 か半角 ¥ のどちらで表示されるかは JDK の CLDR ロケールデータに依存するため、実行環境(JDK のバージョン)によって表示グリフが異なる場合があります(例:OpenJDK 21 系では全角 )。
実機確認の答え合わせ
出力:
¥1,235
$1,234.56
Gold 保有者による書き下ろし解説・実機で検証済

20 問・本番形式の模試・弱点優先の出題は今後のアップデートで開放予定です。

料金プランを見る