本稿ではOracle試験のJava Silver(Java SE 11 Programmer I, 1Z0-815-JPN)で必要とされる知識のうち見直すべきポイントをまとめます。
簡単なJavaプログラムの作成
パッケージの目的にアクセス制御は含まれるか
含まれる。アクセス修飾子と併せてアクセス制御機能が実現されると理解する。
アクセス修飾子をまとめよ
同パッケージ | 他パッケージ | ||||
自クラス | サブクラス | 自クラス | サブクラス | 他クラス | |
public | $\cm$ | $\cm$ | $\cm$ | $\cm$ | $\cm$ |
protected | $\cm$ | $\cm$ | $\cm$ | $\cm$ | - |
指定なし | $\cm$ | $\cm$ | $\cm$ | - | - |
private | $\cm$ | - | - | - | - |
指定しないと同パッケージのみ。protectedは他パッケージのサブクラスも含める。
クラスのアクセス修飾子は「public」と「指定なし」のみ利用可能で,フィールドやメソッドは4種類とも利用可能。
packageを明記しないクラスの扱いを説明せよ
無名パッケージと呼ばれるパッケージに属する。他のパッケージに属するクラスからは呼び出すことはできない。無名パッケージは複数箇所で定義できるためスコープ管理が複雑になりやすく,本番のワークロードでは利用しないことが推奨される。無名パッケージの存在により,パッケージに属さないクラスは存在しなくなる。
java.util.*
でjava.util.regex
パッケージをimportできるか
できない。utilパッケージに含まれるArrayListなどのクラスがimportされるだけで,完全修飾クラス名がutilの配下にあるパッケージがimportされる訳ではない。例えばPatternクラスとMatcherクラスを利用したい場合は下記のように記述すればよい。
import java.util.*;
import java.util.regex.*;
public class Main {
public static void main(String[] args) {
String str = "abc";
Pattern p = Pattern.compile("[a-z]");
Matcher m = p.matcher(str);
System.out.println(m.find()); //true
}
}
パッケージに属するクラスから無名パッケージに属するクラスを呼び出すと何が起こるか
コンパイルエラーが起きる。例外がスローされる訳ではない。
エントリーポイントの条件を述べよ
- public static voidであること
- メソッド名はmainであること
- 引数はString配列型であること
ただし,String... args
のようなString可変長型はコンパイル時にString配列型に変換されるため,エントリーポイントの引数として扱うことができる。
public static void main(String... args) {
// エントリーポイントとなる
}
Java SE 11で追加されたソースファイルモードを説明せよ
javacコマンドによるコンパイルをせずにJavaのプログラムを実行できるようにする機能。javaコマンドに与える引数の拡張子が.javaであれば自動的にソースファイルモードで実行され,.javaではないファイルをソースファイルモードで実行する場合はsourceオプションでソースファイルに使われているJavaのバージョンを指定する必要がある。
// Java SE 11以前
javac Sample.java
java Sample a b c
// Java SE 11以降
Java Sample.java a b c
// .javaの拡張子でない「Example」というJavaプログラムの場合
java --source 11 Example a b c
ソースファイルモードは実質的にjavac
コマンドのd
オプションでコンパイルの結果をメモリ空間に展開して実行しているものと理解するとよい。
// 上記のソースファイルモードは本質的に以下の操作を行なっている
javac -d {メモリ空間} Sample.java
java Sample a b c
javacコマンドとjavaコマンドのソースファイルモードで異なる制約を説明せよ
- javacコマンド:publicなクラス名とファイル名は一致させる
- javaコマンドのソースファイルモード:publicなクラス名とファイル名は一致させなくてもよい
Javaでは1つのファイルに1つのpublicなクラスを記述するというルールがあり,そのpublicなクラス名とファイル名は一致させなくてはならなかった。
エントリーポイントのmainはメソッドに関する話であるため,mainを記述するファイル名をmainにする必要はない。クラスとメソッドを混同しないように注意する。
javaコマンドの引数にa¥"b"c d"e
を入力すると何個の引数として解釈されるか
1つ。
"
を文字列として解釈させるためには¥
を付けてエスケープする必要がある- 引数の区切れ目は半角スペースである
の二つのルールを理解すれば十分。引数としてはa"bc de
という1つの文字列が引数として渡される。
基本データ型と文字列操作
Javaのプリミティブ型を全て列挙せよ
データ型 | 説明 |
---|---|
boolean | true/false |
char | 16ビットUnicode文字 |
byte | 8ビット整数 |
short | 16ビット整数 |
int | 32ビット整数 |
long | 64ビット整数 |
float | 32ビット単精度浮動小数点数 |
double | 64ビット倍精度浮動小数点数 |
bool flg = true;
を実行すると何が起こるか
コンパイルエラー。boolではなくbooleanである。
整数リテラルの記述方法を説明せよ
データ型 | 記述方法 | 説明 |
---|---|---|
byte | byte x = (byte) 127; | (byte) でキャストする |
short | short x = (short) 127; | (short) でキャストする |
int | int x = 127; | 整数のデフォルト |
long | long x = 12.7L; or long x = 12.7l; | L かl の接尾辞 |
float | float x = 12.7F; or float x = 12.7f; | F かf の接尾辞 |
double | double x = 12.7; | 浮動小数点のデフォルト |
2進数 | 0b011001 | 0b の接頭辞 |
8進数 | 076 | 0 の接頭辞 |
16進数 | 0x3F | 0x の接頭辞 |
数値リテラルにおける自動型変換の互換性をまとめよ
()
を用いてキャストしない限り,数値リテラルの型同士には下表の互換性がある。
代入される側 | |||||||
byte | short | int | long | float | double | ||
代入する側 | byte | $\cm$ | $\cm$ | $\cm$ | $\cm$ | $\cm$ | $\cm$ |
short | $\cm$ | $\cm$ | $\cm$ | $\cm$ | $\cm$ | ||
int | $\cm$※ | $\cm$※ | $\cm$ | $\cm$ | $\cm$ | $\cm$ | |
long | $\cm$ | $\cm$ | $\cm$ | ||||
float | $\cm$ | $\cm$ | |||||
double | $\cm$ |
※ int型リテラルは例外的に「相対的に小さい」型のbyteとshortに型変換できます
byte型の取りうる値の範囲を述べよ
-128〜127
数値リテラルでアンダースコアを利用するためのルールを述べよ
- リテラルの先頭と末尾には記述できない
- 記号の前後には記述できない(ただし8進数の接頭辞である
0
の直後は例外的にOK)
プリミティブ型・参照型それぞれにnullを代入すると何が起きるか
- プリミティブ型:コンパイルエラーが起きる
- 参照型:何も起こらないが,参照先にアクセスする際にNullPointerExceptionが起きる
char型に代入可能な値を列挙せよ
''
で括った文字''
で括ったUnicode番号('¥u30A2'
など)0〜65535
の数値
数値は$16$進数$4$桁($0$〜$16^{4}$)のUnicode番号を表している。
変数名に利用できる代表的な記号を説明せよ
アンダースコア(_
)とドル記号($
)
変数名で注意するべき数字に関するルールを一つ挙げよ
数字は変数名の先頭に使えない
varを型推論でコンパイルエラーとなる例を挙げよ
import java.util.ArrayList;
public class Test {
// フィールドでvarを使うとコンパイルエラー
var sample5 = new ArrayList<Object>();
// メソッドの引数でvarを使うとコンパイルエラー
public void test(var value) {
}
public void main(String[] args) {
// 初期化しないとコンパイルエラー
var sample1;
// nullで初期化するとコンパイルエラー
var sample2 = null;
// ラムダ式で初期化するとコンパイルエラー(右辺でキャストすればエラーにはならない)
var sample3 = () -> {};
// 配列の初期化式を使うとコンパイルエラー
var sample4 = {1, 2, 3};
}
}
型推論はどのタイミングで行われるか
コンパイル時。型推論された行以降にその変数がどのように使われようと,型推論が成功すればコンパイルエラーとはならない。型推論された行以降で異なる型を代入しようとするとコンパイルエラーとなる。
JavaにおけるString型の特徴を説明せよ
- ヒープ領域に値の実体,スタック領域にヒープ領域のアドレスをJVMで格納する
- 定数や.classファイル向けに用意されたヒープ領域をコンスタントプールと呼ぶ
- 同一の値がコンスタントプールに存在する場合は再利用する
- immutableのため書き換えようとすると別のヒープ領域とスタック領域をJVMで確保する
- s.replaceAllのようなメソッドはsを破壊的に変更せず変更後のヒープ領域の参照を返す
String s = "hello, world";
s.replaceAll("hello", "hoge");
System.out.println(s); // hello, world
s = s.replaceAll("hello", "hoge");
System.out.println(s); // hoge, world
String型のオブジェクトを生成する方法を三つ説明せよ
// Heap領域のコンスタントプールを利用
String a = "sample";
// コンスタントプールに同一の値が格納されていても新しいHeap領域を確保
String b = new String("sample");
String c = String.valueOf("sample");
StringとStringBuilderで連結を行うためのメソッドを説明せよ
- String:
concat
- StringBuilder:
append
String str = "hello, ";
StringBuilder strBuilder = new StringBuilder("hello, ");
System.out.println(str.concat("world")); // hello, world
System.out.println(strBuilder.append("world")); // hello, world
nullが代入されているStringに文字列を結合すると何が起こるか
- +で結合した場合:null{結合前の文字列}となる
- concatで結合した場合:NullPointerException
String str = null;
System.out.println(str + ", world"); // null, world
System.out.println(str.concat("world")); // NullPointerException
NullPointerExceptionはコンパイル時と実行時のどちらで検知されるか
実行時。java.lang.RuntimeExceptionのサブクラス。
StringBuilderのcapacityを説明せよ
デフォルトで$16$文字分のバッファを確保し,$n$文字のStringで初期化された場合は$16{+}n$文字分のバッファを確保する。
演算子と判定構造
num=10
のときに-num
という記法で$-10$を表せるか
表せる
インクリメントとデクリメントの前置と後置の違いを説明せよ
int a = 10;
int b = ++a + a; // int b = (10 + 1) + (10 + 1)
int c = 10;
int d = c++ + c; // int d = 10 + (10 + 1)
// a: 11, b: 22, c: 11, d: 21
System.out.println(String.format("a: %s, b: %s, c: %s, d: %s", a, b, c, d));
- ++a:インクリメントされたaは「++a」自体を含めた右側で利用される
- a++:インクリメントされたaは「a++」より右側で利用される
これはデクリメントでも同様。
++が左であれば先,右であれば後と位置と順番の対応で覚える。
数値以外に対して不等号を含む演算子を適用すると何が起きるか
コンパイルエラー
ショートサーキット演算子を説明せよ
&&や||など論理演算子を二つ連続で利用する記法のことを指す。&や|は左右の評価式を必ず実行するが,&&や||は左の評価式がfalseである場合は以降の評価式を実行しないという特徴があり,無駄な演算を最小化する効果がある。
試験問題としてはショートサーキット演算子とインクリメントが組み合わせて出題されます。
同一性と同値性の違いを説明せよ
- 同一性:保持している参照が同じであること
- 同値性:保持している参照先の値が同じであること
equalsメソッドはオーバーライド前提であるがデフォルトではどのような定義か
同一性を確認する。
public boolean equals(Object obj) {
return (this == obj); // 同一性の確認
}
equalsという同名のメソッドを定義したとしても引数がObjectの1つでない場合は,オーバーライドではなく新規メソッドの定義となるため注意が必要です。
「プリミティブ型・文字列・null」とnullを==で比較すると何が起きるか
// プリミティブ型と参照型のコンパイルエラー
System.out.println(10 == null);
// false
System.out.println("10" == null);
// true
System.out.println(null == null);
nullは参照型で「参照がないこと」を表すことに注意しましょう。
Javaには===
という比較演算子は存在するか
存在しない
x
にequals
が適切に定義されている場合x.equals(null)
は何が起きるか
xがnullでない場合はfalseを返す。nullは参照型。
Stringでコンスタントプールに値が格納される条件を述べよ
""
を使ってインスタンスを生成した場合。newを利用してインスタンス化した場合はコンスタントプール外のヒープ領域に値が格納される。
Stringのinternメソッドの挙動を説明せよ
コンスタントプール内に同じ値が見つかった場合はその参照を返し,見つからなかった場合はコンスタントプール内に新しい領域を確保してそこへの参照を返す。Javaの「インターン化」とは,コンスタントプールの値を使い回す仕組みのことを指す。
internメソッドは適用した文字列の参照を破壊的に変更するか
変更しない。参照を変更するには変数を上書きする必要がある。
// いずれもコンスタントプール外のHeap領域を利用
String str1 = new String("test");
String str2 = new String("test");
System.out.println(str1 == str2); // false
// internは元の文字列を破壊的に変更しない
// もしかしたらこのタイミングでコンスタントプールに値が格納されているかもしれない
str1.intern();
str2.intern();
System.out.println(str1 == str2); // false
// コンスタントプールへの参照を返す (↑でコンスタントプールに値が格納されていようが格納されていまいが関係ない)
str1 = str1.intern();
// ↑で作成されたコンスタントプールへの参照を返す
str2 = str2.intern();
System.out.println(str1 == str2); // true
else ifで改行すると何が起こるか
else句の中にif句が記述されていると解釈される。
// 本来こう書きたいが
if (条件A) {
} else if (条件B) {
} else if (条件C) {
} else {
}
// 誤ってこう書いてしまうと
if (条件A) {
} else
if (条件B) {
} else
if (条件C) {
} else {
}
// こう解釈されてしまう
if (条件A) {
} else {
if (条件B) {
} else {
if (条件C) {
} else {
}
}
}
}
改行したとしてもロジックが変更される訳ではないと理解している。else ifを利用した場合でもどこかのブロックを一つだけ通ることになるが,改行されたとてどこかのブロックを一つだけ通ることには変わりないはずだ。たとえば,条件Aかつ条件Bを満たす場合は最初の条件Aのブロックを通った後は他にどのブロックも通らない。これは改行前後で変わらない挙動である。
switch ()
の引数で使えない型を挙げよ
long・float・double・boolean
case
で指定できないものを挙げよ
switch ()
の引数と異なる値・変数
public class Main {
public static void main(String[] args) {
final int NUM = 0;
int num = 10;
switch (num) {
// numと異なる型であるためコンパイルエラー
case "10": break;
// caseに変数は利用できないためコンパイルエラー
case num: break;
// NUMはfinalな定数であるため正常
case NUM: break;
}
}
}
finalで定義された変数は定数とみなされるため指定することができます。
breakがなくcaseとdefaultはあるswitch文では何が起きるか
全てのcaseとdefaultが実行される
制御構造
while文で{}
を省略すると何が起きるか
1文(;
で区切られる1つの命令)だけが実行される
1行で2つの命令を書いても1つ目の命令しか実行されません。
do-while文で{}
を省略すると何が起きるか
- do:1文しか記述できず,その文が実行される
- while:1文だけが実行される
doで{}
を省略して2文記述した場合にはコンパイルエラーとなります。
for文の()
内で利用する変数で異なる型を利用すると何が起きるか
コンパイルエラー
// intとlongが混在しているためコンパイルエラー
for (int i = 0, long j = 0; i < 6; i++)
for文の()
内で,
を使って複数記述できるものを述べよ
- 初期化文
- 更新文
// for (初期化文; 条件文; 更新文;)
for (int i = 0, j = 1; i < 1 && j < 2; i++, j++)
for文の()
内で条件文を省略すると何が起きるか
{}
内でbreakを使わない限り無限ループに陥る
for文では更新式も省略できます。
for文の{}
内で利用する変数の注意点を述べよ
スコープはfor文の{}
内だけである点
Javaで配列の初期化を行う方法を述べよ
{}
を使う。[]
は右辺ではなく左辺の型名で利用するため注意。
String[][] array = {{"A", "B", "C"}};
拡張for文の構文を述べよ
for (型 変数名: 集合) {
// 繰り返し処理
}
while (true); System.out.println("hello");
を実行すると何が起きるか
helloが一回だけ出力される。while文で{}
が省略されて中身は;
で完了しているため空となる点に注意。
Javaでラベルの活用例を述べよ
ネストされたfor文で特定のfor文までbreakしたい場合
label:
for () {
for () {
// 外側のfor文まで併せて抜ける
break label;
}
}
labelはfor文以外にもif文/switch文/式/代入/return文/try分/catch文に付けられますが,可読性低下のためラベルの濫用は推奨されていません。
配列の操作
配列をprintlnすると何が起きるか
Objectクラスから継承しているtoString()メソッドを実行し,クラス名 @ ハッシュコード
を出力する。例えば二次元配列であれば[[I@2626b418
のような文字列が出力される。
おそらく配列クラスのgetNameが「次元数の[
+配列に格納する要素の型」と定義されているのでしょう。
配列が参照の集合を扱い,配列そのものも参照である理由を説明せよ
多次元配列を扱う際にも整合性を保つため。配列自体が参照であれば配列の配列も自然に扱うことができる。
二次元配列の宣言方法を列挙せよ
int[][] a;
int[] b[];
int c[][];
配列型変数と配列インスタンスの違いを説明せよ
// 配列型変数 = 配列インスタンス
int[] array = new int[3];
- 配列型変数:配列インスタンスへの参照を格納
- 配列インスタンス:参照の集合
これより,配列型変数では要素数を指定することはできない。
// コンパイルエラー
int[3] array = new int[3];
配列型変数と配列インスタンスで次元数が異なる場合は何が起きるか
コンパイルエラー
// コンパイルエラー
int[] a = new int[2][3];
多次元配列の内部で保持する配列の次元数が異なる場合は何が起きるか
何も起きない。配列は参照を扱っているため。
int[][] a = new int[3][];
a[0] = new int[1];
a[1] = new int[2];
a[2] = new int[3];
一次元の配列インスタンス生成時に要素数を指定しなかった場合何が起きるか
コンパイルエラー
// コンパイルエラー
int[] array = new int[];
二次元の配列インスタンス生成時に要素数を指定しなかった場合何が起きるか
一次元目に要素数を指定しなかった場合はコンパイルエラーとなり,指定した場合は二次元目の要素数はしていしなくてもコンパイルエラーとはならない。
// コンパイルエラー
int[][] a = new int[][];
int[][] a = new int[][3];
// OK
int[][] a = new int[2][];
int[][] a = new int[2][3];
配列の初期値をまとめよ
データ型 | デフォルト値 |
---|---|
整数 | 0 |
浮動小数点 | 0.0 |
真偽 | false |
Char | ¥u0000 |
オブジェクト(String含む参照型) | null |
Stringの初期値は空文字ではなくnullであることに注意しましょう。
nullをprintすると何が起きるか
nullという文字列が表示される
要素がnullのインデックスでlengthを呼び出すと何が起きるか
NullPointerExceptionが発生する
配列をnewで初期化する際に要素数をしていしないと何が起きるか
要素数を指定しない方が正常。逆に要素数を指定するとコンパイルエラーが起きる。
// OK
int[] a = new int[]{1,2};
// コンパイルエラー
int[] a = new int[2]{1,2};
一次元配列を初期化子{}
で初期化すると何が起きるか
何も起きない。正常。
多次元配列を初期化子{}
で初期化すると何が起きるか
何も起きない。正常。
左辺は多次元なのに右辺は一次元で違和感があるかもしれないが,初期化子は自動で左辺の次元を判定して要素を初期化していると理解しましょう。だからこそ,newで初期化する際に右辺で要素数を指定するとコンパイルエラーとなります。
newで配列を作成してから初期化子{}
で初期化すると何が起きるか
コンパイルエラー。上でも述べている通り,初期化では左辺の配列から次元数を判定して初期化しているため,変数宣言と同時にしか使えないという制限がある。
int[] a;
// コンパイルエラー
a = {2,3};
Javaのアップキャストとダウンキャストを説明せよ
- アップキャスト:サブクラスをスーパークラスにキャスト。型安全。
- ダウンキャスト:スーパークラスをサブクラスにキャスト。コンパイルエラーが起きる。
サブクラスではスーパークラスのもつ関数やフィールド以外のものが定義されている可能性があるためエラーになると理解しましょう。逆に,スーパークラスで定義されている関数やフィールドは必ずサブクラスでも定義されていますので,サブクラスからスーパークラスへのアップキャストは可能です。
ダウンキャスト時にコンパイルエラーを起こさせない方法を説明せよ
明示的なキャストを行う。アップキャストはextendsやimplementsで明示的な親子関係が明示されているために,コンパイラは自動的に型安全と判断することができる。一方,ダウンキャストは型安全だと判断できないためコンパイルエラーとなる。そこで,ダウンキャスト時は()
を用いて明示的なキャストを行うことでコンパイルエラーを回避することができる。ダウンキャストは危険な操作であるため,instanceof
で安全にキャスト可能かチェックしてからキャストするとよい。
ダウンキャスト時に起きるエラーについてまとめよ
明示的なキャストを行えばコンパイルエラーは回避できるが,実行時にエラーとなる可能性がある。
class A {
void hello() {
System.out.println("A")
}
}
class B extends A {
void hello() {
System.out.println("B")
}
}
public class Main {
public static void main(String[] args) {
// aの型はA
A a = new A();
// 明示的にキャストしているためコンパイルエラーは回避できる
B b = (B) a;
// aの型はBであるがaにはBの差分が含まれていない
// Bのhelloメソッドを呼び出そうとしてもaにはBのhelloメソッドが含まれていない
a.hello(); // コンパイルエラー
}
}
a.hello()でAのメソッドを呼び出していると誤解しないようにしましょう。Bで型宣言しているのだから,呼び出しているのはあくまでもBのメソッドです。AとBの差分がインスタンス化されていないため,ダウンキャスト後の型でオーバーライドしているメソッドを呼び出そうとすると実行時にエラーが起きます。
差分を含むBをインスタンス化してAにアップキャストすれば,オーバーライドしたメソッドはそのまま生き続ける。
public class Main {
public static void main(String[] args) {
// bの型はB
B b = new B();
// 型安全のため型変換できる
A a = b;
// aにはBの差分が含まれている
// Aのhelloメソッドを呼び出すとオーバーライドされたBの差分が呼び出される
a.hello(); // "B"
}
}
Objectのcloneメソッドの挙動についてまとめよ
cloneは新しい配列を作り,その要素にclone元の要素への参照を格納するメソッドである。clone元の要素がプリミティブ型の場合は参照ではなく値そのものが格納され,参照型の場合は参照が格納される。まとめると下記の挙動となる。
- 配列の要素がプリミティブ型:Deepコピー
- 配列の要素がObject型:Shallowコピー(ただしString型は挙動に注意)
// プリミティブ型の配列には値そのものが格納されているためDeepコピーとなる
int[] a = { 10, 20, 30 };
int[] b = a.clone();
b[0] = 100;
// a: [10, 20, 30], b: [100, 20, 30]
System.out.println(String.format("a: %s, b: %s", Arrays.toString(a), Arrays.toString(b)));
// String型はObject型であるためShallowコピーとなる
// String型はimmutableであるため書き換えようとすると新しい文字列への参照を作成して格納してしまう
// 結果としてDeepコピーのような振る舞いをする
String[] c = { "10", "20", "30" };
String[] d = c.clone();
d[0] = "100";
// c: [10, 20, 30], d: [100, 20, 30]
System.out.println(String.format("c: %s, d: %s", Arrays.toString(c), Arrays.toString(d)));
// 配列はObject型であるためShallowコピーとなる
// String型とは異なりmutableであるため参照先の値を書き換える
Object[][] e = { {"10", "20", "30"}, {"40", "50", "60"} };
Object[][] f = e.clone();
f[0][0] = "100";
// e: [[100, 20, 30], [40, 50, 60]], f: [[100, 20, 30], [40, 50, 60]]
System.out.println(String.format("e: %s, f: %s", Arrays.deepToString(e), Arrays.deepToString(f)));
多次元配列だけArrays.deepToString
を使っているのは,Arrays.toString
を使ってしまうとArrayのtoString
が呼び出されてクラス名 @ ハッシュ値
が出力されてしまうためです。
インスタンスとメソッド
JavaではNULLはnullとみなされるか
みなされない。nullはnullという書き方でしか表現できない。
JavaのGCはどのタイミングで起きるか
参照が外れたタイミング
staticなメンバについて説明せよ
クラスファイル読み込み時に,メモリ上のstatic領域に読み込まれるメンバのことをstaticなメンバと呼ぶ。クラスをインスタンス化しなくてもimportするだけで参照することができる。
staticなメンバとstaticなメソッドの順序制約について説明せよ
staticでないメンバはインスタンス化しないと参照できないため,下記のような順序制約が発生する。
呼び出し元 | 呼び出し先 | 順序制約 |
---|---|---|
static | static | OK |
static | staticでない | NG |
staticでない | static | OK |
staticでない | staticでない | OK |
可変長引数の記法と注意点を述べよ
// 可変長引数は最後に配置する
// 「...」はデータ型の直後に書く
void method(String name, int... a)
唯一のreturn直後にコードを記述すると何が起きるか
到達不能としてコンパイルエラーが起きる
シグネチャとは何か
メソッド名と引数の数・型・順番のセットのこと。シグネチャが異なればメソッドをオーバーライドできる。
オーバーライドの条件を述べよ
下記を全て満たす必要がある。
- シグネチャが同じであること
- 返り値は同じ型か,そのサブクラスであること
- アクセス修飾子は同じか,より緩いものであること
もしアクセス修飾子をより厳しいものにしてしまうと,親クラスで呼び出せていたメソッドが子クラスでは呼びさせなくなってしまい,「抽象化された親を見るだけで扱い方が分かる」というオブジェクト指向におけるポリモーフィズムの旨みを生かすことができなくなってしまいます。
返り値の型が異なるだけでメソッドはオーバーライドできるか
下記の理由によりできない。
- 返り値はシグネチャに含まれない
- そもそも返り値は同じ型か,そのサブクラスである必要がある
アクセス修飾子が異なるだけでメソッドはオーバーライドできるか
下記の理由によりできない。
- アクセス修飾子はシグネチャに含まれない
- アクセス修飾子は同じか,より緩いものである必要がある
(int, int)
の呼び出しで(int, double)
は実行できるか
実行できる。intは暗黙的にdoubleにキャストできるため。
(int, int)
の呼び出しでは(int, double)
と(double, int)
のどちらを実行するか
曖昧な呼び出しとしてコンパイルエラーが発生する
定義したメソッドがコンストラクタとみなされるための条件を述べよ
- メソッド名がクラス名と同じ
- 返り値の型が定義されていない(voidと書くのもダメ)
全てのコンストラクタで共通する処理の記述方法を述べよ
下記のように初期化子を利用する。
public class Sample {
{
// ここにコンストラクタ共通の処理を記述
}
// コンストラクタ1
Sample() {
}
// コンストラクタ2
Sample(int a) {
}
}
初期化子はコンストラクタよりも前に実行されます。
staticなメンバは初期化できるか
下記の二つの方法により可能。
- 宣言時に代入(単純な初期値の場合こちら)
- static初期化子を利用(ブロック内で処理を書きたい場合はこちら)
public class Sample {
static int num1 = 10;
static int num2;
static {
num2 = 20;
}
}
通常の初期化子でstaticメンバを初期化すると,インスタンス生成時に上書きされる。
public class Sample {
static int num;
{
num = 10;
}
}
public class Main() {
public static void main(String[] args) {
// 初期化子はインスタンス化しないと実行されないためnumはintのデフォルト値である0のまま
System.out.println(Sample.num);
Sample s = new Sample();
System.out.println(Sample.num);
}
}
初期化 | タイミング |
---|---|
static初期化子 | クラスロード時 |
インスタンス初期化子 | インスタンス生成前 |
コンストラクタ | インスタンス生成後 |
「staticは上書きされない」とfinalと混同しないように注意です。
独自のコンストラクタを定義している場合,デフォルトコンストラクタは生成されるか
生成されない。引数なしでインスタンス化しようとするとコンパイルエラーが起きる。
Javaにおけるthisの使い方をまとめよ
public class Sample {
public Sample() {
// コンストラクタ内から別のコンストラクタを呼び出す場合
this("hello");
}
public Sample(String name) {
// 同一クラスのメソッドを呼び出す場合
this.Sample(name);
}
public void Sample(String name) {
System.out.println(name);
}
}
コンストラクタから別のコンストラクタを呼び出す場合の注意点を述べよ
thisを使って別のコンストラクタを呼び出す前に別の処理は記述できない点。
public class Sample {
public Sample() {
// コンパイルエラー
System.out.println("sample")
this("hello");
}
public Sample(String name) {
System.out.println(name);
}
}
アクセス修飾子の問題で継承に関して注意するべき引っかけポイントを説明せよ
サブクラスをインスタンス化してもMainクラスの中ではprotectedにはアクセスできないという点。
package other;
public class Book {
protected void sample() {
System.out.println("sample")
}
}
package sample;
public class StoryBook extends Book {
// この中ではsample()にアクセスできる
}
public class Main {
public static void main(String[] args) {
StoryBook story = new StoryBook();
// MainはStoryBookを継承している訳ではないためコンパイルエラー
story.sample()
}
}
クラス同士のアクセス制御という観点が大切です。
継承・インタフェース・抽象クラス
継承で引き継がないものを全て挙げよ
- コンストラクタ
- privateメンバ
継承では親クラスとその差分をそれぞれ別のクラスファイルで保持するため,コンストラクタを引き継ぐと意味が分からなくなってしまう。親クラスは親クラスのコンストラクタ,子クラス(差分)は子クラスのコンストラクタを定義する。
インタフェースの意義とアクセス修飾子の関係を説明せよ
インタフェースはメソッドに関する扱い方を規定するツールである。ゆえにインタフェースの実装ではインタフェースで定義されているメソッドを全て定義しなければならず,裏を返せばインタフェースの抽象メソッドはpublic以外では定義されない。
Java Goldではインタフェースのprivateメソッドが問われます。
アクセス修飾子のdefaultとインタフェースのdefaultの違いを説明せよ
通常のクラスではアクセス修飾子としてdefault(指定なし)が用意されているが,インタフェースでは複数の実装で共通したメソッドを記述するdefault修飾子が用意されている。インタフェースではアクセス修飾子のデフォルトはpublicとなっており,public以外のアクセス修飾子は用いられない。
public interface A {
default void common() {
System.out.println("common")
}
}
インタフェース内のメソッド定義で注意するべきポイントを述べよ
default修飾子でない限り,メソッドの中身を書いてはならないという点。
public interface A {
// OK
void hello()
// OK
default void hello() {}
// コンパイルエラー (何もしないという処理を記述しているため)
void hello() {}
}
多重継承と多重実装を説明せよ
Javaでは多重継承は禁止されており,多重実装は禁止されていない。
// コンパイルエラー
public class AbstractClass extends abstractA, abstractB {}
// OK
public class ConcreteClass implements interfaceA, interfaceB {}
インタフェースにはフィールドを定義できないか
finalで定数化し,staticでインスタンス化せずに使えるフィールドであれば定義できる
インタフェースは継承できるか
継承できる
public interface extends interfaceA {}
「抽象クラスはインタフェースに定義されているメソッドを実装しなくてもよい」は正しいか
正しい。抽象クラスの具象クラスが実装すればよい。
Overrideアノテーションを付けなければOverrideできないか
Overrideアノテーションを付けなくてもOverrideできる。コンパイラに意図しないOverrideを検知させるために付けておく。
java.lang.ObjectクラスのメソッドはOverrideできるか
できない。コンパイルエラーになる。
interface Aを実装したclass BからAのデフォルトメソッドを呼び出す方法を述べよ
// sampleというデフォルトメソッド
A.super.sample();
interface Aを継承したinterface Bを実装したclass CからAのデフォルトメソッドを呼び出す方法を述べよ
super
は1階層だけ戻る参照を意味しているため,2階層離れているデフォルトメソッドは呼び出せない。
同じデフォルトメソッドを持つinterfaceを多重実装した場合には何が起きるか
コンパイルエラー。実装側でオーバーライドした上でいずれかのデフォルトメソッドを呼び出せばよい。
public class Main implements A, B {
// インタフェースA,Bに共通したデフォルトメソッドtestをOverrideしてA側を採用
@Override
public void test() {
A.super.test();
}
}
この問題を菱形継承問題といいます。
抽象クラスを説明せよ
インタフェースとクラスの両方の性質を有するクラス。インタフェースと同様に抽象メソッドをもつためインスタンス化できない。
public abstract class AbstractClass {
// インタフェースの性質
public abstract void methodA() {}
// クラスの性質
public void methodB() {}
}
インスタンス化はできませんが,変数の型としては利用可能です。
「抽象メソッドは全てのサブクラスが実装しなければならない」は正しいか
正しくない。インタフェースや抽象クラスを継承したインタフェースや抽象クラスでは抽象メソッドを実装しなくてもよい。あくまでも最後の具象クラスで実装すればよい。
「抽象クラスでは定数フィールドしか定義できない」は正しいか
正しくない。インタフェースには定数フィールドしか定義できないが,抽象クラスでは動的に値が変更できるフィールドを定義できる。
オーバーライド前後で返り値の型は同じでなければならないか
同じ型かそのサブクラスであればよい。これを共変戻り値と呼ぶ。
オーバーロードとは何か
シグネチャが異なる新しいメソッドを定義すること
継承クラスにおけるフィールドとメソッドの注意点を述べよ
- フィールド:型指定に依存
- メソッド:オーバーライド側を採用
class A {
String val = "A";
void print() {
System.out.println(val);
}
}
class B extends A {
String val = "B";
void print() {
System.out.println(val);
}
}
public class Main {
public static void main(String[] args) {
// いずれも継承前のAを指定
A a = new A();
A b = new B();
// いずれも型はAが指定されているので"A"が出力される
System.out.println(a.val);
System.out.println(b.val);
// Aのフィールドである"A"が出力される
a.print();
// Bでprintをオーバーライドしているため"B"が出力される
b.print();
}
}
親クラスAに存在しないメソッドを定義した子クラスBをAでインスタンス化する際の注意点を述べよ
子クラスBで新しく定義したメソッドが使えない点。アップキャストでは共通部分のみ抽出して他は消えるイメージ。
public class A {
}
public class B extends A {
public void sample() {
}
}
public class Main {
public static void main(String[] args) {
// アップキャスト
// Bで差分として定義されたメソッドは消えるイメージ
A a = new B();
// コンパイルエラー
a.sample();
}
}
フィールドと同名のローカル変数は定義できるか
定義できるがローカル変数が優先される。フィールドを扱いたい場合はthis
を用いる。
何も継承していないクラスでsuperを利用すると何が起きるか
java.lang.Object
クラスの参照を指す
継承元のコンストラクタと継承先のコンストラクタの実行順序を説明せよ
共通部分である継承元のコンストラクタから実行され,次に継承先のコンストラクタが実行される。この順序を保証するため,コンパイラは継承先のコンストラクタに自動で継承元のコンストラクタを挿入する。
public class A {
public A() {
}
}
public class B extends A {
public B(){
// コンパイラが自動で挿入する
super();
}
}
継承を禁止する方法を述べよ
finalを付ける
// Stringを継承することはできない
public final class String extends Object {
}
関数型インタフェース・ラムダ式
関数型インタフェースを説明せよ
抽象メソッドを1つだけ持つインタフェースのこと
ラムダ式の省略記法を説明せよ
- 引数が1つの場合は
()
を省略できる {}
を付ける場合は複数の処理を記述できるが,値を返すためにはreturnが必要{}
を付けない場合は1つの処理を記述できるが,値を返すためにはreturnは不要
// OK
() -> "hello";
() -> { return "hello"; };
// コンパイルエラー
() -> return "hello";
() -> { "hello"; };
ラムダ式の引数で使える変数名の注意点は何か
ラムダ式を宣言しているブロック内で宣言したローカル変数と同名の変数名は使えない。これは引数に関する制約であって,実際の処理ブロックでは次の問題で述べている条件下においては同名のローカル変数を利用することが可能。
public class Main{
public static void main(String[] args) {
String val = "A";
// ラムダ式を宣言するブロック内に同名のvalが存在するためコンパイルエラー
Function f = (val) -> {};
}
interface Function{
void test(String val);
}
}
ラムダ式の内部から呼び出されるローカル変数の注意点は何か
実質的にfinalとなる必要がある点。finalが付いているか,finalが付いていなくてもラムダ式の内部またはラムダ式が宣言されているブロック内で変数が変更されていたらコンパイルエラーとなる。これはラムダ式が必ずしも同期的に呼び出される訳ではないため。
import java.util.function.Supplier;
public class Sample{
void Sample1() {
int i = 0;
// iが実質的にfinalでないためコンパイルエラー
Supplier<Integer> foo = () -> i;
}
void Sample2() {
final int i = 0;
// iがfinalのためエラーは起きない
Supplier<Integer> foo = () -> i;
// iがfinalのためコンパイルエラー
i++;
}
}
java.util.functionパッケージを説明せよ
頻繁に利用される関数型インタフェースが定義されたパッケージのこと。
関数型インタフェース | 実装必須な抽象メソッド | 意味 |
---|---|---|
Consumer<T> | void accept(T) | 指定された型の引数を受け取って処理をするだけ |
Supplier<T> | T get() | 何も受け取らずに指定された型の結果を返す |
Predicate<T> | boolean test(T) | 指定された型の引数を受け取って真偽値の結果を返す |
Function<T, R> | R apply(T) | 指定された型の引数を受け取って指定された型の結果を返す |
ジェネリクスの<>内に記述するTやRは引数だけでなく返り値も含まれるため注意する。
API
Comparatorインタフェースの扱い方を説明せよ
compareメソッドを実装するが,第一引数の方が「小さい」という順序を定義する場合は-1を返し,第一引数の方が「大きい」という順序を定義する場合は1を返し,第一引数と第二引数が「等しい」という順序を定義する場合は0を返す仕様とする。
import java.util.Comparator;
public class SampleComparator implements Comparator<Sample> {
@Override
public int compare(Sample s1, Sample s2) {
// 第一引数のIdが小さい場合は第一引数の方が大きい
if (s1.getId() < s2.getId()) return 1;
// 第一引数のIdが大きい場合は第一引数の方が小さい
if (s2.getId() < s1.getId()) return -1;
// 第一引数と第二引数のIdが等しい場合は第一引数と第二引数は等しい
return 0;
}
}
Javaにおける日付の扱いについて説明せよ
Javaでは従来java.utile.Dateやjava.utile.Calendarを用いて日付データを扱っていたが,Java SE 8で日付データの扱いを改良したjava.time.LocalDateクラスが誕生した。特徴としては
- immutableなオブジェクト
- インデックスが1始まり
の二点が挙げられる。前者の不変性はLocalDateクラスをスレッドセーフにしている要因でもある。不変なオブジェクトとすることで,マルチスレッド環境でデータの不整合が起きない構造となっている。後者は従来N月を表すのにN-1のインデックスを指定していたため,ソースコードを誤読する可能性が高かった。一方LocalDateクラスではN月はインデックスNであるため,直感的にも分かりやすい仕様となった。
ArrayListはスレッドセーフか
スレッドセーフではない。スレッドセーフな配列はjava.util.Vectorクラスを利用する。
ArrayListの拡張for文におけるスレッドセーフについて説明せよ
ArrayListの拡張for文では,複数スレッドでの処理を禁止するために内部で二つの変数を保持している。
- modCount:ArrayList自身が変更を加えた回数
- expectedModCount:Iteratorが生成された時点でのmodCountの値
反復処理ではカーソルを次に進めるメソッドの中でmodCountがexpectedModCountに等しい場合に「スレッドセーフ」として処理を継続,等しくない場合はConcurrentModificationExceptionがスローされる仕組みになっている。これにより,反復処理中に外部からArrayListに変更が加えられないことを保証している。
この仕組みは単一スレッドでも機能することがある。例えば,拡張for文の中でremoveメソッドを用いると二つの変数が一致せずに例外がスローされる。
import java.util.ArrayList;
public class Main {
public static void main(String[] args){
ArrayList<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
for (String str : list) {
// カーソルをインデックス1から2に進めるメソッド内部でmodCountが不一致となる
// ConcurrentModificationExceptionがスローされる
if ("B".equals(str)) {
list.remove(str);
}
System.out.println(str); // A, B
}
}
}
ただし,最後から一つ前の要素を削除した場合はmodCountの一致チェックの前に「カーソルが最後まで到達した」と判定されて拡張for文が正常終了してしまうため,注意する。
import java.util.ArrayList;
public class Main {
public static void main(String[] args){
ArrayList<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
list.add("D");
for (String str : list) {
// "C"を削除するとカーソルはインデックス3にいる
// リストのサイズとカーソルのインデックスが比較される
// カーソルが最後まで到達したとみなされて正常終了
if ("C".equals(str)) {
list.remove(str);
}
System.out.println(str); // A, B, C
}
}
}
ArrayListで要素数が1のときにインデックス2に要素を挿入すると何が起きるか
実行時にIndexOutOfBoundsExceptionがスローされる
ArrayListでaddのようにremoveの引数にObjectを渡すと何が起きるか
渡されたObjectとequalsメソッドで比較してtrueの要素を削除する。複数ある場合はインデックスが小さい方を削除する。
ArrayListでない固定長の配列で要素を削除するとどのような挙動になるか
そもそも要素の追加や削除ができない。
Javaにおけるリストの種類を述べよ
- 配列(固定長)
- java.util.Arrays$ArrayList(固定長)
- java.util.ArrayList(可変長)
import java.util.ArrayList;
import java.util.List; // インタフェース
import java.util.Arrays;
// 配列 (固定長)
var list = new Integer[] {1,2,3}
// Arrays$ArrayList (固定長)
var list = Arrays.asList(new Integer[] {1,2,3});
var list = List.of(1,2,3);
// ArrayList (可変長)
var list = new ArrayList<Integer>(3);
java.util.Arrays$ArrayList
の生成方法としてはArrays.asList
とList.of
をおさえておけばOKです。Listはインタフェースですが,Java9から使えるようになったstaticファクトリメソッドを使っています。staticファクトリメソッドとは,クラスのインスタンスを返すstaticメソッドのことを指し,staticであるためクラスを実装してインスタンス化しなくても使えます。なお,$はインナークラスを表します。
Arrays.mismatchの返り値は何か
渡された二つの配列のうち要素が一致しないインデックスの最小値。全て一致した場合は-1を返す。
Arrays.compareの挙動を説明せよ
渡された二つの配列を比べるメソッドだが,基準は辞書(Unicode)順である。辞書順で並び替えてから比較するのではなく,比較の基準自体が辞書順である。それぞれが等しい場合には0を,第一引数が先なら-1,第一引数が後ろなら1を返す。
removeifメソッドを説明せよ
Listインタフェースのスーパーインタフェースであるjava.util.Collectionインタフェースのデフォルトメソッド。与えられたラムダ式がtrueを返した要素を削除する。
HashMapクラスのKeyとValueはnullを許容するか
許容する
匿名クラスを簡単に説明せよ
抽象クラスやインタフェースの実装とそのクラス定義を同時に行う仕組み。使い捨てのクラスを記述できるため便利。最近は代わりにラムダ式が使われることが多い。
forEachメソッドを説明せよ
引数として与えられたConsumer関数型インタフェースが唯一実装しているacceptメソッドを繰り返し実行する。Iterableインタフェースに定義されているデフォルトメソッド。
メソッド参照を説明せよ
関数型インタフェースにメソッドをそのまま代入する仕組み。新規実装や単純処理であればラムダ式を使うとメソッドを代入できるが,複雑な既存処理を代入したい場合はメソッド参照を利用すると便利。{クラス名}::{メソッド名}
もしくは{インスタンス名}::{メソッド名}
と記述する。後半はあくまでもメソッド名であり()
は不要であるため注意する。
import java.util.List;
public class Main {
public static void main(String[] args) {
List<String> list = List.of("A", "B", "C");
// メソッド参照
list.forEach(System.out::println);
// ラムダ式
list.forEach(str -> System.out.println(str));
}
}
例外処理
複数のcatchに引っかかった場合の挙動を説明せよ
最初のcatchだけ実行されて後続のcatchは実行されない。
必ずExceptionを投げる関数により到達不能なcatchが存在する場合はコンパイルエラーとなります。
複数のfinallyに引っかかった場合の挙動を説明せよ
そもそもfinallyを複数記述できない。コンパイルエラーとなる。
try-finally-catchの順で記述することはできるか
できない
catchのreturnとfinallyはどちらの方が先に呼び出されるか
finally。上から下に読んでいくとfinallyが一番最後だと勘違いしがちだが,
- tryのreturnでローカル変数とは別に返り値用の変数セット
- finallyの実行
- 返り値用の変数を返す
という順番で実行される。
finallyの内部でreturnに与えたローカル変数を書き換えると何が起きるか
- tryのreturnでローカル変数とは別に返り値用の変数セット
- finallyでローカル変数を書き換える
- 返り値用の変数を返す
という順番で実行されるため,返り値用の変数がmutableな参照型である場合は書き換え後の値が返り,返り値用の変数がimmutableな参照型である場合やプリミティブである場合は書き換え前の値が返る。
public class Main {
// mutableな参照型
public static StringBuilder returnStringBuilder() {
StringBuilder a = new StringBuilder("sample");
try {
throw new RuntimeException();
} catch (Exception e) {
return a;
} finally {
a.append("Added");
}
}
// immutableな参照型
public static String returnString() {
String a = "sample";
try {
throw new RuntimeException();
} catch (Exception e) {
return a;
} finally {
a += "Added";
}
}
// プリミティブ
public static int returnInt() {
int a = 0;
try {
throw new RuntimeException();
} catch (Exception e) {
return a;
} finally {
a += 10;
}
}
public static void main(String[] args) {
System.out.println(returnStringBuilder()); // 書き換え後のsampleAdded
System.out.println(returnString()); // 書き換え前のsample
System.out.println(returnInt()); // 書き換え前の0
}
}
catchとfinallyの両方にreturnが入っていた場合は何が起きるか
- tryのreturnでローカル変数とは別に返り値用の変数セット
- finallyで返り値用の変数を書き換える
- 返り値用の変数を返す
となるため,結果としてfinally側のreturnのみが実行されたようにみえる。ローカル変数の書き換えと返り値用の変数の書き換えでは挙動が異なるため注意する。巷では「finallyのreturnのみが実行されてcatchのreturnは握りつぶされる」と表現される。javaで注意するべき仕様。
public class Main {
// mutableな参照型
public static StringBuilder returnStringBuilder() {
StringBuilder a = new StringBuilder("sample");
try {
throw new RuntimeException();
} catch (Exception e) {
return a;
} finally {
return a.append("Added");
}
}
// immutableな参照型
public static String returnString() {
String a = "sample";
try {
throw new RuntimeException();
} catch (Exception e) {
return a;
} finally {
return a += "Added";
}
}
// プリミティブ
public static int returnInt() {
int a = 0;
try {
throw new RuntimeException();
} catch (Exception e) {
return a;
} finally {
return a += 10;
}
}
public static void main(String[] args) {
System.out.println(returnStringBuilder()); // 書き換え後のsampleAdded
System.out.println(returnString()); // 書き換え後のsampleAdded
System.out.println(returnInt()); // 書き換え後の10
}
}
try-with-resourceはエラー時にどのような順番で処理が行われるか
- リソースの解放
- catchブロックの処理
- finallyブロックの処理
try-with-resourceのtryの()
で宣言された変数のスコープを説明せよ
tryブロックの中。catchやfinallyの中で参照するとコンパイルエラーとなる。
例外処理の種類をまとめよ
種類 | 例 | 意味 | ||
---|---|---|---|---|
Throwable | Error | OutOfMemoryError | catchの必要がない。回復の見込みがないもの。 | |
Exception | IOException | catchするべき。当然備えるべきもの。 | ||
RuntimeException | NullPointerException | catchしなくてもよい。きりがないもの。 |
catchするべきExceptionとcatchしてもよいRuntimeExceptionは,catchの代わりにthrowしてもよいです。また,Errorはcatchの必要がないだけでcatchしようと思えばcatchできます。しかし,致命的な障害を握りつぶしてしまうためErrorは例外処理しない方がよいです。
範囲外の要素アクセス時に発生する例外の種類を述べよ
- IndexOutOfBoundsException:範囲外の要素アクセスのスーパークラス
- ArrayIndexOutOfBoundsException:配列の範囲外の要素アクセス(ArrayListではない)
- StringIndexOutOfBoundsException:Stringの範囲外の要素アクセス
ArrayListはIndexOutOfBoundsExceptionとなる。
再帰呼び出しを無限に行うときに発生する例外の種類を述べよ
StackOverflowError
Exceptionでないことに注意です。
モジュールシステム
モジュールの作成方法を説明せよ
module-info.java
に
- どのパッケージを公開するのか
- 他にどのモジュールを使うのか
を記述した上で,コンパイル時にmodule-info.java
を指定すると結果がモジュール化された状態となる。例えばsrc/hello/com/sample/Main.java
をモジュール化する場合は,モジュールのルートディレクトリであるhello直下にmodule-info.java
を作成し,srcと同じ階層にmodsというディレクトリに結果を出力するようにコンパイルすればよい。
javac -d mods/hello src/hello/module-info.java src/hello/com/sample/Main.java
モジュールの実行方法を説明せよ
ルートディレクトリ・モジュール名・実行クラスの完全修飾クラス名を指定したjavaコマンドを利用する。
# java --module-path {ルートディレクトリ} -m {モジュール名}/{完全修飾クラス名}
java --module-path mods -m hello/com.sample.Main
なお,複数のモジュールをまとめてjarファイルとして出力して実行する方法もある。
# jarファイルの作成
jar --create --file=mlib/hello.jar --main-class=com.sample.Main -C mods/hello .
# jarファイルの実行
java --module-path mlib -m hello
モジュールの利用方法を説明せよ
module-info.javaでパッケージの公開とモジュールの利用を明示する。モジュールを公開する側でexportsしたいパッケージを指定し,モジュールを利用する側でrequiresしたいモジュールを指定する。
module {
exports com.sample; // 公開するパッケージ
requires test; // 利用するモジュール
}
下記の二点に注意。
- exportとrequireにはsが付くことを忘れない
- exportsしないとパッケージは公開されない
- 公開する側はパッケージの粒度,利用する側はモジュールの粒度
推移的な依存関係を説明せよ
自パッケージが必要なモジュールを,自パッケージを必要とするモジュールに利用させる仕組みのこと
module {
requires transitive test; // このパッケージを使うものもtestモジュールを利用できるようになる
}
プラットフォームモジュールを説明せよ
予め用意されているパッケージをモジュール化したもの。代表的なプラットフォームモジュールはjava.baseで,Javaを実行する上で基本となるjava.langなどのパッケージが含まれている。
モジュールの設定内容を調べるための方法を説明せよ
# 方法1: javaコマンドのオプションを利用
java --describe-module (他の引数は割愛)
# 方法2: jmodコマンドのdescribeを利用
jmod describe (他の引数は割愛)
jmodはJava Runtime Environmentを含む特別なモジュールを表すためのファイル形式です。
jdepsコマンドを説明せよ
依存しているモジュールを出力するためのコマンド。
# 何もrequiresしていなければjava.baseのみが出力される
jdeps --list-deps hello.jar
一時的にパッケージを公開する方法を説明せよ
add-exportsオプションを利用する。依存関係をオプションの引数で渡してあげる。
# {対象のモジュール}/{公開するパッケージ}={利用するモジュール} (他の引数は割愛)
# hogeモジュールにあるcom.testパッケージをhelloモジュールで一時的に利用できるようにする
javac --add-exports hoge/com.test=hello
一時的であるためデバッグ時などあくまでも限定的な用途です。
演習問題ひっかけポイント
javacコマンドとjavaコマンドのクラス指定方法の違いを説明せよ
# javacコマンドはpath名
javac com/sample/Main.java
# javaコマンドは完全修飾クラス名 (ただし.javaは不要)
java com.sample.Main
module-info.javaでrequiresをループさせた場合は何が起きるか
コンパイルエラー
switch文のフォロースルーを説明せよ
上からswitch文を実行していき,該当するcaseまたはdefaultが見つかった時点でbreakするまで全ての命令を実行していく仕組みのこと。
public class Main {
public static void main(Str[] args) {
int data = 1;
// default010と表示される
switch(data) {
default: System.out.print("default")
case 0: System.out.print("0")
case 10: System.out.print("10")
break;
case 20: System.out.print("20")
}
}
}
jmod describeはモジュールの依存関係を調べられるか
調べられない。代わりにjdeps --list-deps
やjava --show-module-resolution
を使う。
switch文の()
にnullを渡すと何が起きるか
NullPointerException。これはswitch文の内部では()
に渡された変数の.hashCode()
を呼び出しているため。
クラスパス直下でないとクラスは認識されないか
直下という言い方が難しいが,Mainという完全修飾クラス名が与えられていればクラスパス直下のMainを探しにいき,sample.Mainという完全修飾クラス名が与えられていればクラスパス直下のsample/Mainを探しにいく。クラスパスのイメージはクラス探索の起点となるディレクトリ。クラスパスをカレントディレクトリとして指定された完全修飾クラス名をpathに変換して探しにいくイメージ。
javaコマンドとjavacコマンドでクラスファイルに関する代表的なオプションを説明せよ
# -dでクラスファイルの出力先を指定
javac -d build sample/Main.java
# -cpでクラスパスを指定
java -cp build sample.Main
インスタンス変数とクラス変数の違いを説明せよ
- インスタンス変数:初期値やコンストラクタによって生成されるインスタンスに紐づく変数
- クラス変数:クラス内でstaticを付けて宣言されている変数
CollectionとListの関係を述べよ
CollectionはListのスーパーインタフェース。Listもインタフェース。Collectionの方が親と覚える。
ListよりもCollectionの方が語義的にも抽象度が高いことからも自然に理解できます。
シグネチャが複数のメソッドに該当する際のJVMの挙動を説明せよ
より厳密な方を実行する。Collectionを引数に取るメソッドよりListを引数に取るメソッドを優先する。
「abstractにできるか」と問われた際の思考回路を説明せよ
「インスタンス化されているかどうか」を調べる。abstractを付けるとインスタンス化できなくなる。
親クラスと子クラスのコンストラクタはどちらが先に呼び出されるか
親クラス
返り値がvoidのメソッドでreturn;
すると何が起きるか
何も起きない。「呼び出し元に制御を戻す」という命令と解釈される。
継承前後で同名のフィールドを定義することはできるか
できる。変数の型で宣言されているフィールドが使われる。上書きのような挙動は示さない。
コンストラクタ系のエラーで出題されがちなものを二つ挙げよ
- 独自定義のコンストラクタでsuper()が自動追加されるが,呼び出し先で引数なしのコンストラクタが定義されていないとコンパイルエラーとなる
- superとthisは同時に記述するとコンパイルエラーとなる
public class Sample {
String name;
public Sample(String name) {
this.name = name
}
}
public class SubSample extends Sample {
String name;
public SubSample() {}
public SubSample1(String name) {
// ここにsuper()が自動追加される
// Sampleに引数なしコンストラクタが存在しないためコンパイルエラー
}
public SubSample1(String name) {
// superとthisは同時に記述できないためコンパイルエラー
super(name);
this(name);
}
superとthisを同時に記述できないのはコンストラクタを重複して呼び出すことを防ぐためです。
抽象メソッドを持たない抽象クラスは宣言可能か
宣言可能
catchブロックを一般的な型から特定の型への順で記述すると何が起きるか
コンパイルエラー。一般的な型でcatchが引っかかり特定の型は到達不能となるため。
Math.roundに二つの引数を渡して出力の小数点を調整することはできるか
できない。pythonと混同しない。なお,roundは四捨五入であることにも注意。
float x = 1.95;
を実行すると何が起きるか
コンパイルエラー。右辺がデフォルトのdoubleであるため。float x = 1.95f;
とすればよい。
int x = 12_34;
を実行すると何が起きるか
何も起きない。_
は区切り文字。
数値でcharを宣言する方法を述べよ
char c = (char) 100;
のようにキャストが必要
intは(String)
でキャストできるか
できない。文字列の足し算とキャストは別物と考える。
初期化しないローカル変数を参照すると何が起きるか
コンパイルエラー。初期値が入るのはインスタンス変数とクラス変数
JDKは全てのOSに対応しているか,それとも特定のOSごとにセットアップが必要か
特定のOSごとにセットアップが必要
引数に与えたString[]
とString...
にはどのような違いがあるか
違いはない。String...
はコンパイル時にString[]
に置き換わる。
親子間でキャストを行う際の注意点を述べよ
親から子へのキャストは明示的なキャスト()
が必要であるということ
モジュール化されていないアプリをモジュールシステムに対応したJDKで実行できるか
実行できる。パッケージ指定を行わないクラスは無名パッケージに属するため。
JVM/JRE/JDKの違いは何か
- JVM:クラスファイルを各OS上で実行するための仮想マシン
- JRE:JVM+Java SEなどのAPI群
- JDK:JRE+コンパイラ+デバッガ
throw・throwsとcatchの注意点を述べよ
throwで投げられている例外をcatchするのではなく宣言のthrowsで投げられる可能性のある例外をcatchする
数値型で暗黙的な型変換ができないケースの覚え方を説明せよ
情報欠損が起こる場合は暗黙的なキャストができないと理解する
フィールドにvarを使うと何が起きるか
コンパイルエラー。varはローカル変数しか利用できない。
var a = {1.0, 2.0};
を実行すると何が起きるか
コンパイルエラー。初期化子{}
とvarがコンフリクトしてしまうため。
配列のサイズを指定した場合,各要素は初期化されるか
初期化される。フィールドと同じ規則。
アクセス修飾子の問題で引っかかりがちなポイント
メソッドのアクセス修飾子ばかりに気を取られてクラス自体のアクセス修飾子を意識できないこと
フィールドのfinalを初期化しないと何が起きるか
コンパイルエラー。フィールドであってもfinalは必ず初期化しないといけない。
インタフェースで定義された抽象メソッドのアクセス修飾子は緩められるか
インタフェースの抽象メソッドはpublicであるため緩められない。クラスの継承では緩められる。両者とも厳しくすることはできない。
インタフェースで抽象メソッドを実装できるか
デフォルトメソッドを使えば実装できる
test(new int[3])
はtest(long[] val)
とtest(Object val)
のどちらを呼び出すか
test(Object val)
を呼び出す。intとlongはbit数が異なるため,配列全体をObjectと捉えた方が「近い」とみなされて優先的に呼び出される。
コンパイラはプラットフォームごとの違いを意識するか
意識しない。JVMが意識する。
ラムダ式の()
内でフィールドで宣言したfinalの変数は使えるか
使えない
インタフェースにはフィールドを定義できるか
定数であれば定義できる
Stringのreplaceメソッドやsubstringメソッドの注意点を述べよ
Stringはimmutableであるため,返り値を受け取らないと編集後の文字列は得られないこと。メソッドを適用したStringが破壊的に変更されることはない点が注意点。
intとIntegerを==
で比較すると何が起きるか
アンボクシングでintに揃えて比較される。参照は関係なく値が等しければtrueが返る。
staticなフィールドを{インスタンス名}.{フィールド名}
で呼び出すことはできるか
できる。コンパイル時に{クラス名}.{フィールド名}
に変換される。
Javaはパイプライン処理をサポートするような標準ライブラリを提供しているか
提供していない
Javaは頻繁に使われるコードをキャッシュするか
キャッシュする。Javaではコンパイルの成果物はクラスファイルのバイトコードであり,そのバイトコードをJVMが以下の二つの方法で実行する仕組みである。
- インタープリタ
- 動的コンパイラ
インタープリタは逐次実行を実現し,動的コンパイラは特定CPU向けの機械語であるネイティブコードを生成する。「頻繁に使われるコードのキャッシュ」は後者の動的コンパイラのネイティブコード生成時に行われる。
StringとCharSequenceの関係を説明せよ
StringがCharSequenceインタフェースを実装している
配列を扱うメソッドのオーバーライドとオーバーロードについて説明せよ
import java.util.*;
public class A {
public List<Number> test(Set<CharSequence> s) {
return null;
}
}
このメソッドをオーバーライドするためには,
- Set<CharSequence>を引数として与えること
- 返り値はListもしくはそのサブクラスであること(汎化させてはダメ)
を満たす必要がある。引数がSetの時点でオーバーロードではなくオーバーライドの扱いとなる。この場合,引数の配列の要素がCharSequrnceと完全一致しない場合は定義することができない。「Setは同じでもCharSequrnceではない型を格納するSetと捉えれば,全く別の型の引数を受け取るからオーバーロードとみなせるのではないか」という疑問を持つかもしれないが,引数にSetを与えている時点でコンパイラはオーバーライドの土俵にのせてしまうためオーバーロードとみなすことはできない。
// Setの時点でオーバーライドとなるが,その要素がCharSequenceでないため定義できない
public List<Number> test(Set<String> s)
仮に引数の型が完全一致している場合は,返り値はListもしくはそのサブクラスであればよい。Listの要素の型が異なる場合は,そもそもオーバーライドの土俵に上がることもできない。汎化させたObjectでも,特化させたIntegerでも土俵に上がれない。
// 引数が完全一致し,ArrayListはListの実装クラス,Numberも完全一致するためオーバーライド
public ArrayList<Number> test(Set<CharSequence> s)
// いずれも配列の要素がNumberでないためオーバーライドとみなされない
public List<Integer> test(Set<CharSequence> s)
public ArrayList<Integer> test(Set<CharSequence> s)
public ArrayList<Object> test(Set<CharSequence> s)
同名メソッドで引数の型が異なる場合はオーバーロードとみなされるため,返り値がどのような型であっても定義可能である。
// オーバーロードとみなされる
public List<Integer> test(TreeSet<CharSequence> s)
オーバーロードとみなされるメソッドにOverrideアノテーションを付けるとコンパイルエラーとなる。
// コンパイルエラー
@Override
public List<Integer> test(TreeSet<CharSequence> s)
オーバーライドともオーバーロードともみなされない同名のメソッド定義はエラーとなる。
Object型のequalsはどのような挙動か
オーバーライドされていなければ同値性ではなく同一性をチェック(参照を比較)する。
多次元配列におけるcloneの注意点を述べよ
一番外側のcloneはshallowコピーとなり,内側のcloneは要素の型によってshallowコピーとdeepコピーが決まる。配列全体で1つの参照となっているからであり,内側の配列をインスタンス化するためには別の領域を確保しなければならないことに注意する。
ローカル変数を初期化しないと何が格納されるか
ローカル変数は必ず初期化しないといけない。クラス変数とインスタンス変数の場合はデフォルトの初期値が格納される。ただし,コンパイルエラーが起きる訳ではなく参照時にエラーが起きる。
フィールドとローカル変数の名前が完全一致した場合の挙動を説明せよ
ローカル変数とみなされて何も起きない。
public class Item {
public int number;
// 本来フィールドと同名の引数を定義しない方がよい
public void setNumber(int number) {
// 正しいsetter
this.number = number
// この場合は左辺のnumberも右辺のnumberも引数のnumberを指す
number = number;
}
// フィールドと別名の引数を与えた場合
public void setNumber(int num) {
this.number = num
}
}
Stringのtrimメソッドの挙動を説明せよ
先頭と末尾の空白を削除する。破壊的変更は行わない。
StringとStringBuilderを+で結合した際の挙動を説明せよ
StringBuilderのtoString()が呼び出されてString同士の+演算となる。つまり,新しいStringオブジェクトが生成される。