31:次のコード断片のうちコンパイルエラーが生じるものを4個選べ。
A:byte i=100l;
B:byte i=(short)120;
C:byte i=100+28;short j=-30000-2769;
D:short i=1;byte j=i;
E:byte i=(byte)2000;byte j=i;
F:double i=10000d;byte j=(byte)(1*i);
G:byte i=1;short j=i+i;
H:char i=100;
正解を確認する
コンパイルエラーが生じるコードは以下の4つです。 A: byte i=100l; 理由: 100l は long 型のリテラルです。long 型の値をより小さい byte 型の変数に代入するには、明示的なキャスト (byte) が必要です。 C: byte i=100+28;short j=-30000-2769; 理由:前半の byte i=100+28; は、128 が byte の範囲 (-128~127) を超えているためエラーになります。 後半の short j=-30000-2769; は、-32769 が short の範囲 (-32768~32767) を超えているためエラーになります。 D: short i=1;byte j=i; 理由: 変数 i は short 型です。より大きいデータ型 (short) からより小さいデータ型 (byte) へ代入するには、たとえ値が byte の範囲内であっても、(byte) という明示的なキャストが必要です。 G: byte i=1;short j=i+i; 理由: Javaでは、byte、short、char 型の値を算術演算(+、-、*、/)で使用すると、自動的に int 型に格上げ(整数プロモーション)されます。そのため、i+i の結果は int 型になります。int 型の値を short 型の変数 j に代入するには、(short) という明示的なキャストが必要です。 コンパイルエラーが生じないコードは B、E、F、H です。それぞれの理由は以下の通りです。 B: byte i=(short)120; 理由:120 は byte 型の範囲内 (-128 から 127) の値です。 120 はまず short 型にキャストされていますが、その値は byte 型の変数 i に代入可能です。より大きい型 (short) から小さい型 (byte) への代入ですが、リテラル値が代入先の型の範囲内であれば、コンパイラが自動で変換してくれるためエラーになりません。 E: byte i=(byte)2000;byte j=i; 理由:byte i=(byte)2000;: 2000 は byte の範囲を超えていますが、(byte) という明示的なキャスト(強制的な型変換)を行っています。これにより、値が byte の範囲に収まるように変換されます。 byte j=i;: byte 型の変数 i の値を、同じ byte 型の変数 j に代入しているため、問題なくコンパイルされます。 F: double i=10000d;byte j=(byte)(1*i); 理由:1*i の計算結果は double 型の 10000.0 になります。通常、double 型の値を byte 型の変数に代入することはできません。 しかし、E と同様に (byte) という明示的なキャストがあるため、コンパイラは 10000.0 を byte 型に強制変換しようとします。 H: char i=100; 理由:char 型は内部的に数値を扱っており、整数リテラルを代入することができます。100 は char 型の範囲内 (0 から 65535) です。この場合、Unicode(文字コード)で 100 に対応する文字(アルファベットの 'd')が変数 i に格納されます。このように、範囲内の整数リテラルであれば char 型に直接代入できるため、エラーは発生しません。
32:次のコードをコンパイル、実行したときの結果として正しいものを選びなさい。
public class Main{
public static void main(String[] args) {
int i=check((char)(1+11));i+=check('D');i+=check('a');
}
private static int check(char i) {
return switch(i) {
case 'D'->' ';
case 12->{
System.out.print('D'+""+'D');
System.out.print('D'+'D');
yield 'D'+'D';
}
default->check((char)('D'+'D'));
};
}
}
A:mainメソッドでコンパイルエラーが生じる
B:checkメソッドでコンパイルエラーが生じる
C:DD136
D:DDDD
E:DD136と表示された後StackOverflowErrorがスローされる
F:DDDDと表示された後StackOverflowErrorがスローされる
正解を確認する
プログラムはまず「DD136」と表示し、その後に StackOverflowError という例外をスローして停止します。
このコードの動作を順を追って見ていきましょう。
check((char)(1 + 11)) の呼び出し:1 + 11 は 12 です。これを char 型にキャストして check メソッドを呼び出します。
switch 文の case 12: に一致します。
System.out.print('D' + "" + 'D');
'D' (文字) + "" (空文字列) + 'D' (文字) という計算です。
文字列と文字を + で連結すると、文字は文字列に変換されて結合されます。結果として "DD" という文字列がコンソールに出力されます。
System.out.print('D' + 'D');
'D' (文字) + 'D' (文字) という計算です。
文字同士を + で計算すると、それぞれの文字コード(ASCII値)の足し算が行われます。
'D'の文字コードは68なので、68 + 68 = 136 となります。結果として 136 という数値がコンソールに出力されます。
この時点での出力は DD136 となります。この case は yield 'D' + 'D'; で 136 を返します。
check('D') の呼び出し
switch 文の case 'D': に一致します。この case は ' ' (スペース文字)を返します。
check('a') の呼び出し
'a' は case 'D' にも case 12 にも一致しないため、default の処理が実行されます。
default は check((char)('D' + 'D')) を呼び出します。これは check((char)136) を呼び出すのと同じです。
再帰呼び出し: 呼び出された check((char)136) も、どの case にも一致しないため、再度 default に入り、再び check((char)136) を呼び出します。これが無限に繰り返される無限再帰に陥ります。
結論:プログラムは最初の check メソッドの呼び出しで DD136 を画面に出力します。その後、3回目の check('a') の呼び出しで無限再帰が発生し、最終的にスタック領域を使い果たして java.lang.StackOverflowError という例外がスローされ、プログラムは異常終了します。
33:次のコードをコンパイル、実行したときの結果として正しいものを選びなさい。
public class Main{
public static void main(String[] args) {
A a=new B();
var i=a.useA();var j=a.a_(a);
System.out.println(""+i.i+j.i);
}
}
sealed class A permits B{
Byte i;
private A a() {
Byte i=1;
return new A(i);
}
protected A a_(Object i) {
return new A(3);
}
A(Byte i){
this.i=i;
}
A useA(){
return a();
}
A(){}
}
final class B extends A{
public A a() {
return new B();
}
public B a_(Object j) {
return new B();
}
protected A a_(A a) {
return new A(2);
}
}
A:1null
B:null2
C:null3
D:class Aでコンパイルエラー
E:class Bでコンパイルエラー
F:実行時に例外がスローされる
正解を確認する
A a = new B();
B クラスのインスタンスを作成し、それを A 型の変数 a に代入しています。変数の宣言された型は A ですが、実際のインスタンスの型は B です。
var i = a.useA(); の評価
a の useA() メソッドを呼び出します。B クラスは useA() をオーバーライドしていないため、親クラスである A の useA() が呼び出されます。A の useA() メソッド内では return a(); が実行されます。
ここで注意すべき点は、A クラスの a() メソッドが private で宣言されていることです。privateメソッドはオーバーライドの対象になりません。
したがって、a の実際のインスタンスが B であっても、A のコード内から a() を呼び出すと、必ず A 自身が持つ private な a() メソッドが実行されます。A の private A a() メソッドは new A(i) (iはByteの1) を返します。
結果として、main メソッドの変数 i には i フィールドが 1 である A のインスタンスが代入されます。したがって i.i は 1 です。
var j = a.a_(a); の評価
これは最も重要なポイントです。Javaでは、どのオーバーロードされたメソッドを呼び出すかはコンパイル時に、変数の宣言された型に基づいて決定されます。コンパイラは a.a_(a) という呼び出しを見ます。変数 a の宣言された型は A です。引数 a の宣言された型も A です。
コンパイラは A 型の変数で呼び出せる a_ メソッドを探します。A クラスには a_(Object i) というメソッドしかありません。(Bクラスで定義されている a_(A a) は、変数の型が A である現時点では見えません)。
したがって、コンパイル時には a_(Object i) メソッドを呼び出すことが決定されます。次に、実行時に、どのオーバーライドされたメソッドを実行するかが決まります。a の実際のインスタンスは B です。
B クラスは a_(Object) メソッドを public B a_(Object j) としてオーバーライドしています。そのため、実際に実行されるのは B クラスの a_(Object j) メソッドです。
このメソッドは return new B(); を実行します。B のインスタンスが生成される際、引数なしのコンストラクタ B() が呼ばれ、暗黙的に親クラスの引数なしコンストラクタ A() が呼ばれます。
A() コンストラクタでは、Byte i フィールドは初期化されません。Byte はラッパークラス(オブジェクト)なので、初期化されないフィールドのデフォルト値は null となります。
結果として、main メソッドの変数 j には i フィールドが null である B のインスタンスが代入されます。したがって j.i は null です。
System.out.println(""+i.i+j.i); の実行
i.i は 1 です。j.i は null です。式は "" + 1 + null と評価されます。
文字列連結により、"1" + null となり、最終的に "1null" という文字列が出力されます。
34:次のコードをコンパイル、実行したときの結果として正しいものを選びなさい。
public class Main{
protected static int i;
public static void main(String[] args) {
Main[]i= {new Main(),new A(),null};
i[2]=count(i);
int k=0;
while(k<i.length) {
if(i[k] instanceof A)Main.i++;
k++;
}
System.out.println(Main.i);
}
static void a(Main j) {
if(j instanceof A)Main.i++;
else if(j instanceof Main)--i;
else Main.i+=2;
}
static Main count(Main[] i) {
for(Main j:i) {
a(j);
return Main.i>0?new A():new Main();
}
}
}
class A extends Main{
static void a(Main j) {
if(j instanceof A)i++;
else if(j instanceof Main)--Main.i;
else Main.i-=2;
}
}
A:0
B:1
C:2
D:3
E:コンパイルエラー
F:実行時に例外が生じる
正解を確認する
戻り値の型がvoid以外(この場合は Main)に設定されているメソッドは、どのような実行経路をたどっても必ず return 文に到達しなければなりません。 count メソッドでは、唯一の return 文が for-each ループの中にあります。もし、引数として渡された配列 i が空の配列(要素数が0)だった場合、この for-each ループは一度も実行されません。 その結果、プログラムの実行はループを素通りしてメソッドの終わりに到達してしまいますが、そこに return 文が存在しないため、「値を返さずにメソッドが終わってしまう可能性がある」とコンパイラが判断します。 これにより、「missing return statement」(return文がありません)というコンパイルエラーが発生します。そのため、このプログラムは実行ファイル(.classファイル)を生成できず、実行することができません。
35:次のメソッドの定義として正しいものを選びなさい。
A:void $(){throw new OutOfMemoryError();}
B:void _() throws Exception{return;throw new Exception();}
C:float $$(float i){return 2.0/i;}
D:float _$(){throw new RuntimeException();System.out.println();}
E:AとC
F:上記のいずれも正しくない
正解を確認する
A:正しい:OutOfMemoryErrorはErrorのサブクラスであり、非検査例外(Unchecked Exception)です。throws句の記述は不要で、コンパイルエラーは起きません。 B: 誤り:return;が実行されるとメソッドは終了します。その後に続くthrow new Exception();は決して実行されない到達不可能なコード (Unreachable Code) となり、コンパイルエラーとなります。 C:誤り:2.0はdouble型リテラルです。2.0 / iの演算結果はdouble型になります。double型の値を、明示的なキャストなしでより小さいfloat型の戻り値として返すことは、縮小変換にあたり、コンパイルエラーとなります。(正しくはreturn (float)(2.0/i);が必要です。) D:誤り:throw new RuntimeException();が実行されるとメソッドは直ちに終了します。その後に続くSystem.out.println();は到達不可能なコードとなり、コンパイルエラーとなります。
36:次のコードをコンパイル、実行したときの結果として正しいものを選びなさい。
import java.util.List;
public class Main{
private static int p;
public static void main(String[] args) {
List<Integer>i=List.of(0,1,2,3,4);
a:do {
b:for(;;) {
System.out.print(";");
c:for(int j=0;j<1;) {
for(int k:i) {
if(k%2==1)break;
else System.out.print(k);
}
for(int k:i) {
if(k%2==0)break c;
else System.out.print(k);
}
}
for(int k:i) {
if(k%3==0&p==0) {p++;continue a;}
else System.out.print(k);
break a;
}
}
}while(true);
}
}
A:;0
B:;00
C:;0;00
D:無限ループが生じる
E:コンパイルエラーが生じる
F:実行時に例外がスローされる
正解を確認する
1回目の do-while ループ(ラベル a)
無限 for ループ(ラベル b)に入ります。System.out.print(";"); が実行されます。
for ループ(ラベル c)に入ります。
最初の拡張 for ループ
k が 0 の場合:0 % 2 == 1 は偽(0 が偶数)。else 側で System.out.print(k);、つまり 0 を出力します。
k が 1 の場合:1 % 2 == 1 は真(1 が奇数)。break; が実行され、この拡張 for ループを抜けます。
次の拡張 for ループ
k が 0 の場合:0 % 2 == 0 は真(0 が偶数)。break c; が実行され、ラベル c が付いたfor ループ全体を強制的に抜けます。処理は for ループ(ラベル c)の次の行に進みます。
最後の拡張 for ループ
k が 0 の場合:条件 if (k%3==0 & p==0) を評価します。0 % 3 == 0 は真 かつ p == 0 も真です。
p++; で p の値が 0 から 1 に更新されます。continue a; が実行され、ラベル a が付いた do-while ループの先頭に戻り、次の繰り返しを開始します。
2回目の do-while ループ(ラベル a)
無限 for ループ(ラベル b)に入ります。
System.out.print(";"); が実行されます。for ループ(ラベル c)に入ります。
最初の拡張 for ループ
k が 0 の場合: 0 を出力します。k が 1 の場合: break; でこのループを抜けます。
次の拡張 for ループ
k が 0 の場合: break c; が実行され、ラベル c が付いたfor ループ全体を強制的に抜けます。
最後の拡張 for ループ
k が 0 の場合:条件 if (k%3==0 & p==0) を評価します。0 % 3 == 0 は真ですが、p == 0 は偽(p は 1 のため)です。条件は全体として偽です。else System.out.print(k); で 0 を出力します。break a; が実行され、ラベル a が付いた do-while ループ全体を抜け、プログラムが終了します。
37:次のコードをコンパイル、実行したときの結果として正しいものを選びなさい。
import java.util.*;
public class Main extends A {
private long i;
public static void main(String[] args) {
Main i = new Main();i.i=i.a(2, 2);
if(i.b(new ArrayList()))System.out.println(i.i);
else System.out.println(-i.i);
}
}
class A {
final byte a(float i, long j) {return 2;}
protected int a(int i, double j) {return 1;}
boolean b(Collection i) {return true;}
boolean b(List i) {return false;}
}
A:1
B:2
C:-1
D:-2
E:コンパイルエラー
F:実行時に例外がスローされる
正解を確認する
このコードがコンパイルエラーになる理由は、mainメソッド内の i.i=i.a(2, 2); という行にあります。 ここで a メソッドを呼び出していますが、親クラス A には a という名前のメソッドが二つ定義されています。 一つは引数が (int i, double j) のメソッドです。 もう一つは引数が (float i, long j) のメソッドです。 i.a(2, 2) のように、引数として二つの整数 2 と 2 を渡した場合、コンパイラはどちらの a メソッドを呼び出すべきか判断できません。 引数の 2 は int 型ですが、int 型は double 型、float 型、long 型のいずれにも変換できます。そのため、コンパイラにとって a(int i, double j) と a(float i, long j) の両方が呼び出しの候補となり、どちらか一方を優先する明確なルールがないため、「呼び出しが曖昧です」というエラーが発生します。 ちなみに、もしこのコンパイルエラーがなかった場合、次の if 文の i.b(new ArrayList()) という呼び出しでは、b(List i) メソッドが選択されます。ArrayList は Collection でもありますが、より具体的な型である List を引数に持つメソッドが優先されるためです。
38:次のコードをコンパイル、実行したときの結果として正しいものを選びなさい。
public class Main {
private static byte count;
public static void main(String[] k) {
A a=new B();
var b=(B)a;
C c=(C)b;
var d=a.a();var e=c.a();
A[]i= {a,b,c,d,e};
for(var j:i) {
if(j instanceof C)count++;
else if(j instanceof B)--count;
else count/=0.5;
}
}
}
sealed abstract class A permits B{abstract A a();}
non-sealed class B extends A{B a() {return new B();}}
class C extends B{C a() {return new C();}}
A:-2
B:-1
C:0
D:1
E:コンパイルエラー
F:実行時に例外がスローされる
正解を確認する
問題となるのは main メソッド内の C c=(C)b; の行です。 A a=new B(); 変数 a は A 型ですが、中身は B クラスのインスタンスです。 var b=(B)a; 変数 a の中身(B のインスタンス)を B 型へキャスト(型変換)しています。これは問題なく成功します。変数 b は B 型となります。 C c=(C)b; 変数 b の中身(B のインスタンス)を C 型へキャストしようとしています。 ここで問題が発生します。C は B を継承した子クラスですが、b の中身はあくまで new B() で作成された B のインスタンスであり、C のインスタンスではありません。 親クラス(B)のインスタンスを、子クラス(C)の型に強制的に変換することはできないため、この行が実行される時点で ClassCastException という実行時例外がスローされ、プログラムは強制終了します。
39:次のコード断片のうちコンパイルエラーが生じないものはどれか。
A:private record A(int j) implements B{static int i;}
public interface B{}
B:abstract record A(String i, int j){private static int k=1;}
C:sealed record A(int i)permits B{A(int i){if(i<0)throw new RuntimeException();}}
D:public record A(String i, int...j){public int k;}
E:record A(int i) implements B{}
interface B{default void i(){}}
F:final record A extends Record{}
G:どれも正しくない
正解を確認する
A: private record A(int j) ... トップレベルの型(クラス、インターフェース、レコードなど)に private アクセス修飾子を指定することはできません。public またはパッケージプライベート(何も書かない)のみが許可されます。
B: abstract record A(...) レコード(record)は暗黙的に final であり、継承されることを前提としていません。そのため、abstract(抽象)キーワードを付けて定義することはできません。
C: sealed record A(int i){...} レコードは常に final であるため、それ以上継承されることはありません。したがって、レコードに対して sealed(封印された)修飾子を使用することはできません。sealed は継承を制御する目的でクラスに使用されます。
D: public record A(...){public int k;} レコードは、コンポーネント(括弧 () の中に定義された変数)以外のインスタンスフィールド(静的でないフィールド)を宣言することができません。public int k; はインスタンスフィールドを追加しようとしているためエラーとなります。
E: record A(int i) implements B{} と interface B{default void i(){}} record A(int i) は、暗黙的に public int i() という名前のアクセサメソッド(値を取得するメソッド)を生成します。 一方、インターフェース B は default void i() というメソッドを持っています。 A が B を実装すると、同じ名前(i)で戻り値の型が異なる(int と void)二つのメソッドが衝突するため、コンパイルエラーとなります。
F: final record A extends Record{} すべてのレコードは、開発者が明示的に書かなくても、自動的に java.lang.Record クラスを継承します。開発者がコード内で extends Record と明示的に書くことは文法上禁止されています。また他のclassをextendsすることもできません。
40:次のコード断片のうち例外がスローされるものを3個選べ。なお、それぞれのコードでjava.utilパッケージがインポートされているものとする。
A:List i=List.of(new Object(),1,"2");i.add(false);i.remove(3);
B:Collection i=Arrays.asList(new Object[]{new Object(), true, ' ',3});i.add(false);i.remove(3);
C:Collection<Object> i=List.of(new ArrayList(), new List(), new Collection());i.get(0);
D:List i=new ArrayList();i.add(1);i.remove(1);
E:List i=Arrays.asList(new Number[]{1,2.0,100});i.get(0);
F:ArrayList<Integer> i=new ArrayList<>();i.add(2.0);i.remove(0);
正解を確認する
例外がスローされる三つのコードの再解説
実行時に例外(RuntimeException)がスローされるコード断片は、A、B、D の三つです。
A: List i=List.of(new Object(),1,"2");i.add(false);i.remove(3);
List.of() メソッドは、変更不可能なリスト(イミュータブルリスト)を作成します。i.add(); やi.remove();のように要素を追加しようとすると、リストのサイズを変更できないため、実行時に UnsupportedOperationException がスローされます。
B: Collection i=Arrays.asList(new Object[]{new Object(), true, ' ',3});i.add(false);i.remove(3);
Arrays.asList() メソッドは、基になる配列をラップした固定サイズのリストを作成します。同様にi.add(); やi.remove();のように要素を追加しようとすると、リストのサイズを変更できないため、実行時に UnsupportedOperationException がスローされます。
D: List i=new ArrayList();i.add(1);i.remove(1);
最初の二行で、リスト i は [1] という要素が一つだけ入った状態になります(要素 1 はインデックス 0 に存在します)。
i.remove(1); の引数 1 は、要素の値ではなくインデックスとして解釈されます。リストにはインデックス 0 の要素しか存在しないのに、存在しないインデックス 1 を削除しようとするため、実行時に IndexOutOfBoundsException がスローされます。
例外がスローされないコード断片(またはコンパイルエラー)
C: Collection
コメント