プログラミングその他 PR

Java Silver SE11 に黒本だけで合格!! – 覚える知識を整理する –

主に暗記するしかない部分をまとめました。

多少はあれど、暗記からは逃れられないのだ…。

一旦問題集を一周していたりなど、最低限知識がある人向けです。

いわゆる「黒本」に沿った構成にしています。

自分用に2,3日で記事を書き終えるつもりが、書いていくうちにボリュームが増え、気づけば完成が試験後になりました(笑)

供養のためにアップします…。

いわゆる「黒本」はこちら↓
これやるだけで問題ないと思います。

第1章 簡単なJavaプログラムの作成

基本構成

package -> import -> classの順番で配置する

// このクラスはパッケージaaaに属します宣言
package aaa;

// パッケージbbbのBクラスをimport
import bbb.B;

// パッケージcccのすべてのクラスをimport
// 孫クラスはimportされない!!
import ccc.*;

class A{
    public static void main(String[] args){
     // メインの処理
    // ・String[]はString...でも可
    // ・argsはただの変数名なのでなんでもいいが、それ以外は固定
    }
}
  • package指定しない場合は「無名パッケージ」に属する
    無名パッケージのクラスは無名パッケージのクラスからしかアクセスできない!!
  • java.langパッケージに属するクラスと同じパッケージに属するクラスは自動的にimportされる。

ファイルの実行

実行方法①

// コンパイルの実行
// javac -d [出力ディレクトリ] [ソースファイルパス]
javac -d bin MyClass.java

// ファイルの実行
// java -cp [クラスディレクトリ] [完全修飾クラス名]
java -cp bin MyClass

特定のパッケージに属している場合は以下のようになります。

なお、ソースファイルはsrcフォルダにまとめてある想定です。

// コンパイルの実行
javac -d bin src/com/example/MyClass.java

// ファイルの実行
// [パッケージ名].[クラス名] が完全修飾クラス名です
java -cp bin com.example.MyClass

実行方法②

// SE11からは直接javaファイルを実行できるようになった
java MyClass.java

// ちなみにsourceオプションを指定すれば、.java拡張子でなくても実行可能
// java --source [バージョン] [ファイル名]
java --source 11 MyClass.txt

なお、内部的には一時的にコンパイルファイルを作成しているようです。

実行時のパラメータ

  • 引数はスペースで区切って指定します。
  • “” で囲うことで、スペースも文字列として判断されます。
  • ” 自体を文字列として扱う場合は¥を直前に着けることでエスケープできます。
java Sample a ¥" a¥" "a "b c

// Sampleが引数を順に出力するクラスだとすると以下が出力される
a
"
a"
a b
c

第2章 Javaの基本データ型と文字列操作

リテラルについて

基本

  • ‘A’ -> char型
  • “A” -> String列
  • 10 -> int型(10進数)
  • 0b1010 -> int型(2進数) 頭に0b (たぶんbinary)
  • 012 -> int型(8進数) 頭に0 (たぶんOctal. 0oだとわかりづらかったのかな?)
  • 0xa -> int型(16進数) 頭に0x (たぶんHexadecimal. なぜ0hじゃない?)
  • 10l, 10L -> long型. おしりにl or L
  • 10.0 -> double型.
  • 10f, 10F -> float型. おしりにf or F

使用できる数値型

ちょっと脱線するけど、知っておく必要はあるので整理しときます。

TypeSize (bytes)Minimum ValueMaximum Value
byte1-128127
short2-32,76832,767
int (1)4-2,147,483,6482,147,483,647
long (1L)8-9,223,372,036,854,775,8089,223,372,036,854,775,807
float (1.0F)4-3.4e+383.4e+38
double (1.0)8-1.7e+3081.7e+308
char 2065,535

Sizeはlong:8byte > float:4byte なのに、扱える値の範囲はfloatの方が広いの面白くないですか?
私は面白かったので調べた。

詳しくは調べてくださいですが、floatとdoubleは浮動小数だからで、実はでかい数値だと精度が悪くなっているので、その分Sizeが小さくて済みます。

ちなみに整数型がマイナス側に範囲が広い理由も、面白いので調べてみると楽しい(?)ですよ。

char型も2byte数字として扱えます。

リテラルにおける暗黙の型変換

型変換の基本は大は小を兼ねるです。

基本ではない大の型→小の型の場合、キャスト式で型変換できることを教えてあげないといけません。

  • アップキャスト(小の型→大の型) -> 明示は不要(暗黙的な型変換)
  • ダウンキャスト(大の型→小の型) -> 明示が必要(キャスト式)
// 範囲の広いdoubleにより狭いintを代入しているので暗黙的にアップキャストされる
double a = 10;

// 下はコンパイルエラー
int b = 10L;

// これならOK
int c = (int) 10L;

しかし例外的に、大の型→小の型でもint型リテラル→byte型, short型, char型への型変換は範囲内であれば暗黙的に実施されます。

そういう例外めんどくさいなと思っていたのですが、よく考えたらbyteやshortはLongのLやfloatのFのような直接リテラル指定ができないので、intから変換できないとそれはそれで困ることに気が付きました。

// 下はダウンキャストだが、byte型の範囲内なので暗黙的にキャストされる
byte a = 127;
        
// 下はbyteの範囲外なのでコンパイルエラー
byte b = 128;
        
// ちなみに範囲内でもLongだとエラーになった
byte c = 127L;

// shortだとエラーにならなかったので、int以下の整数なら大丈夫そう
byte d = (short) 127;

char型もまったく同様です。

扱いとしては範囲の異なるshort型みたいなもんです。

// 下はダウンキャストだが、char型の範囲内なので暗黙的にキャストされる
char a = 97;

// 下はcharの範囲外なのでコンパイルエラー
char b = -1;

// charからのshortへの変換も範囲内なら暗黙的に行われる
short c = 'a';

// 当然範囲外ならコンパイルエラー
// ちなみに下の漢字は32,768の整数. 人名で見ますね.
short d = '耀';

おまけ

数字リテラルには任意の場所にアンダーバー(_)を加えることができます。

例外は、「先頭」「末尾」「記号の隣」です。

変数名のルール

したかないので覚えましょう。

  • 変数名に使える記号は「$」と「_」のみ。
  • 変数名の先頭に数字はNG。

var宣言のルール

しかたがないので覚えましょう。

ただ、var宣言時に型推論ができるかどうか?でおよそ見当はつきます。

varが使えない場面

  • classのフィールド(初期化しないことができるしね)
  • メソッドの引数
  • メソッドの戻り値
  • 初期化されていない変数(var a; みたいな)
  • null
  • ラムダ式(というかラムダ式自体が何の関数型インターフェースかという型情報を必要としていますしね)
  • 配列の初期化子(何の配列かわかりません)

代表的なStringのメソッド

Stringはイミュータブル(不変)な型なので、インスタンス自体の変更はされません。

個人的にはイミュータブル = 不変 ということをしょっちゅう忘れていました。
まあ、英単語の問題ですが。

突然異体(mutant)に否定の接頭辞imがついたもの、で覚えられるといいな。

MethodDescription
length()文字列の長さを返す。
charAt(int index)指定されたインデックスの文字を返す。
substring(int beginIndex)部分文字列を返す(beginIndexから終わりまで)。
substring(int beginIndex, int endIndex)部分文字列を返す(beginIndexからendIndexまで)。
indexOf(String str)指定された文字列が最初に出現するインデックスを返す。
lastIndexOf(String str)指定された文字列が最後に出現するインデックスを返す。
toLowerCase()文字列を小文字に変換する。
toUpperCase()文字列を大文字に変換する。
trim()前後の空白を除去する。
replace(char oldChar, char newChar)文字列内の特定の文字を他の文字で置き換える。
replaceAll(String regex, String replacement)文字列内の正規表現に一致するすべての部分を新しい文字列で置き換える(replaceと異なり正規表現を使用)。
startsWith(String prefix)文字列が指定された接頭辞で始まるかどうかを判定する。
endsWith(String suffix)文字列が指定された接尾辞で終わるかどうかを判定する。
contains(CharSequence s)文字列が特定の文字列を含むかどうかを判定する。
split(String regex)文字列を正規表現に基づいて分割する。
equals(Object anObject)指定されたオブジェクトと文字列が等しいかどうかを判定する。
equalsIgnoreCase(String anotherString)別の文字列との比較を大文字小文字を区別せずに行う。

代表的なStringBuilderのメソッド

StringBuilderは変更可能なStringみたいなやつです。

デフォルトではインスタンス生成時に指定した文字列+16文字分のバッファーをもっています。

インスタンス自体を変更する、いわゆる破壊的なメソッドがあるので注意が必要です。

文字列を抜き出すsubstring以外のStringを返すメソッドは大体破壊的っぽいですね。

MethodDescriptionインスタンスを変えるか?
append(…)文字列を追加する。Yes
insert(int offset, …)指定された位置に文字列を挿入する。Yes
delete(int start, int end)開始位置から終了位置までの範囲を削除する。Yes
deleteCharAt(int index)指定された位置の文字を削除する。Yes
replace(int start, int end, String str)開始位置から終了位置までの範囲を指定された文字列で置換する。Yes
reverse()文字列の内容を逆順にする。Yes
setLength(int newLength)StringBuilder の長さを設定する。Yes
charAt(int index)指定された位置の文字を返す。No
indexOf(String str)指定された文字列が最初に出現するインデックスを返す。No
lastIndexOf(String str)指定された文字列が最後に出現するインデックスを返す。No
substring(int start)開始位置から文字列の終わりまでの部分文字列を返す。No
substring(int start, int end)開始位置から終了位置までの部分文字列を返す。No
toString()StringBuilder の内容を String に変換する。No

第3章 演算子と判定構造

演算の基本

基本的には数学と同じと思えばなんとかなります。

なんとかならなそうな者たち↓

  • = が2つ重なってもOK. 右側から処理される。
    int a = 3;
    int b = a = a + 5;
    →a=8, b=8 になっています。
  • インクリメントは記号のあとの変数の値が変わる。
    int a = 1;としたとき
    ++a → このaは2で以後のaも2
    a++ → このaは1で以後のaは2
  • Stringの結合時、nullは”null”という文字列として扱われる。
    String a = null + “A”; → ことのきaは”nullA”

論理演算子

  • and : &, &&
  • or : |, ||
  • not : !

同じ記号が2つくっついた&&と||はショートサーキット演算子と呼ばれるもので、左側の結果で確定する場合は右側を評価しません。

// falseと2が出力されます。
int a = 1;
System.out.println(false & ++a>1);
System.out.println(a);

// falseと1が出力されます。
int b = 1;
System.out.println(false && ++b>1);
System.out.println(b);

同一性と同値性

同一性

  • 同じインスタンスを参照しているか調べる
  • A == B でチェック

同値性

  • 同じ値かを調べる
  • Objectのequalsメソッドをオーバーライドして、具体的に何の値を確認するか定義する
  • A.equals(B)でチェック
  • オーバーライドしない場合は同一性を確認している

おまけ

プリミティブな型とそのラッパークラスを比較する際は、ラッパークラスがアンボクシングされて比較されます。

// 下のコードはtrueになります
int a = 1;
Integer b = 1;
System.out.println(a==b);

Stringのコンスタントプール

文字列リテラル” “はコンスタントプールという領域に保存されており、同じ値に対しては同じ参照が使いまわされています。

つまり、== でも実質的には同値性の確認となっています。

String a = "test";
String b = "test";

// 下はtrueです
a == b;

// internというメソッドで、明示的にコンスタントプールの参照を取得できます。
String c = new String("test");

// 下はfalseです
a == c;

// 下はどちらもコンスタントプールの参照を示しているのでtrueです
a == c.intern();

第4章 制御構造

if, for, while, switchの細かなルール

まあ、覚えるしかないですね。

if

if後の{}は省略できます。

この場合、続く1文のみがifの処理として認識されます。

// Bだけが出力されます。
if(false) 
System.out.println("A");
System.out.println("B");

for

  • 初期化子と更新文は複数記述できる
  • 初期化子は同じ型でないといけない
    →2つ目の初期化子に型名を書いてはいけない(コンパイルエラー)
  • 条件式は1つしか記述できない。 
    複数記述するとコンパイルエラー
  • 初期化式・判定式・更新文どれも省略できる
    判定式がない場合はtrue扱い

初期化子は同じ型という決まりがあるんだから、型を2回宣言するとエラーになるのは理解できます。
ところどころ、余計なことや重複することはさせない!というJavaの意思を感じますね。

ただの感想ですが、こういう言語のイメージをもっておくと、どんなことがエラーになるか覚えやすい気がします。

ちなみに、それぞれ省略できるので以下のような式もOKです。

// 初期化さえしていれば、宣言がfor外でもOK
int i;
for(i=0;;){
    break;
}

// まったく意味はないが、全部省略してもOK
// while(true)の代わりにはなるか?笑
for(;;){
    break;
}

while

処理の{} は省略できます。

この場合、続く1文のみが処理として認識されます。

ifと同じですね。

// doのあとの{}省略
// ちなみにもう一文加えるとコンパイルエラーになる
do
System.out.println("A");
while(false);

// whileのあとの{}省略
// 続く1文のみがwhileの処理になるので、下の結果は「0 1 B」となりBは一回のみ表示
int a=0;
while(a<=1)
System.out.println(a++);
System.out.println("B");

switch

  • 使用できる型は、
    String, enum, char含むint以下の整数及びそれらのラッパークラス
  • switch文の引数にある値と、同じ型/互換性がある型でないとコンパイルエラー
  • 定数(final宣言された変数)か、リテラルでないとコンパイルエラー
  • nullを使用すると例外が発生

型が一致しないとそもそもコンパイルエラーになるあたり、やはり無駄なことはさせないという強い意志を感じます。

定数でないとダメなのは、曖昧さを排除しようとする思想ですね。
学習中ちょくちょく感じます。

ちなみに、nullは型チェックはすり抜けますが、比較の過程でエラーを吐きます。
『ぬるぽ』『ガッ』

ラベル

特定のforループを抜ける際に使用したりするやつですね。

可読性も下がるのでそれほど使い道はないかなー?

あまりにも何にでもつけられるので、変数やメソッドの宣言時以外にはつけられると覚えるのが早いような気がします。

// 代入
int a;
a : a = 1;

// コードブロック
b : {int b = 1;}

// if,for,while,switch
c : for(int c : new int[]{1,2,3}){System.out.println(c);}

// 式. 例えばメソッドの実行
Supplier<String> sup = () -> "メソッドの実行";
d : sup.get();

// try
e : try{
    // return
    f : return;
}finally{
    // throw
    g : throw new Exception();
}

// これはコンパイルエラー
h : int h = 1;

第5章 配列の操作

配列の初期化

なかなかにややこしいです。

覚えるしかないですが一貫性がなくはないので、以下のポイントを押さえておきましょう。

基本は「左辺(型宣言側)で次元数を指定して、右辺(初期化側)で要素数を指定するです。

そして、Javaは余計なことをしたらコンパイルエラーも押さえておきます。

基本の初期化

まずは左辺(型宣言側)のみのコード例です。
下のコードのeの宣言が問題ないのトリッキーすぎるでしょ…。
まったく、変なところで寛容なんだから…。

// OK
int a[];   // 1次元配列
int[] b;   // 1次元配列
int c[][]; // 2次元配列
int[][] d; // 2次元配列
int[] e[]; // 2次元配列. なんだって!?

// NG コンパイルエラー
int f[3];  // 宣言側で要素数を指定してはいけない!

では、続いて右辺(初期化側)です。

抑えるべきポイントは以下です。

  • 多次元配列の場合、1次元目の要素数指定は必須
  • 要素数はint以下の整数で指定
// OK
int a[] = new int[3];
int b[][][] = new int[3][][];
short num = 3;
int c[] = new int[num];

// NG. コンパイルエラー

//// 1次元目の要素数が未指定
int d[][] = new int[][3]; 

//// int以下の整数で要素数を指定していない
int e[] = new int[3.3];

long longNum = 3L;
int f[] = new int[longNum];

初期化子を使った初期化

初期化子{}を使って、宣言と同時に値のセットができます。

注意点は、初期化子を使った場合要素数の指定は初期化子にまかせる必要があることです。
つまり、インスタンスの生成で要素数を指定するとエラーになります。(new int[3]など)
(Javaの余計なことするなの原則)

また、初期化子だけだと型情報(何の何次元配列か)がわかりません。
つまり、型情報が同一文の中にないとエラーになります。

// OK
int a[] = new int[]{1,2,3};
int b[] = {1,2,3};
int c[][] = {};
int d[];
d = new int[]{1,2,3};

// NG. コンパイルエラー
//// 初期化子を使う場合はインスタンス生成時に要素数の指定不可
int e[] = new int[3]{1,2,3};

//// 同一文に型情報が必要
int f[];
f = {1,2,3};

初期化時のデフォルト値

初期化子を使って値を指定しなかった場合、配列の型に応じてデフォルト値が格納されます。

Data TypeDefault Value
byte0
short0
int0
long0
float0.0
double0.0
char‘\u0000’
booleanfalse
Objectnull
ちなみに、char 型のデフォルト値は、Unicodeの空文字(null文字)を表す ‘\u0000’ です。

第6章 インスタンスとメソッド

staticやinstanceの違いは分かっている前提だと、知識的な暗記項目はそれほどありません。

ただ特に解説しませんが、試験の際は参照まわりの理解は深めるのがベターだと思います。

インスタンスの生成

コンストラクタ

インスタンス生成時に実行される部分ですね。

細かな仕様
  • 返り値はなし
    返り値がある場合はメソッドとして解釈される
  • アクセス修飾子は何でもOK
    →newで生成されたくないクラスはprivateにしたりなど、生成時のアクセスを制御できる
  • this(引数)を使用することでコンストラクタ内で別のコンストラクタを実行できる
    この場合、コンストラクタの一番最初に一度だけしか記述できない!!

おまけですが、classに初期化されていないfinalなインスタンスフィールドがある場合、全てのコンストラクタで初期化されないといけません。

さらにおまけですが、フィールドが初期化されていない場合、以下のデフォルト値が設定されます。
配列のデフォルト値と同じですね。

データ型デフォルト値
byte0
short0
int0
long0
float0.0
double0.0
char‘\u0000’
booleanfalse
Objectnull

初期化子

インスタンス初期化子
  • クラスのインスタンスが作成されるたびに実行されます。
  • {} で囲まれたブロックでコードを記述します。
静的初期化子
  • クラスが最初にロードされるときに一度だけ実行されます。
  • static キーワードを使用してブロックを定義します。

ちなみに初期化子は複数の記述しても問題ありません。
つまり、{}ブロックが複数あってもOKで、上から順にどちらも実行されます。

Java的には「余計な記述をせずに一つにまとめろ」コンパイルエラーが出た方が一貫性があって綺麗だと個人的には思うので、この仕様はJavaとしては好きではありません(笑)

// インスタンス初期化子の例
class MyClass {
    // インスタンス初期化子
    {
        // このメッセージは、インスタンスが作成されるたびに出力されます。
        System.out.println("Instance initializer called");
    }

    MyClass() {
        // このメッセージは、コンストラクタが呼び出されるたびに出力されます。
        System.out.println("Constructor called");
    }
}

public class Main {
    public static void main(String[] args) {
        MyClass obj1 = new MyClass(); // インスタンス初期化子が先に呼び出される
        //  -> 出力: "Instance initializer called"  "Constructor called"
        MyClass obj2 = new MyClass(); // 同様に、初期化子が呼び出される
        //  -> 出力: "Instance initializer called"  "Constructor called"
    }
}
// 静的初期化子の例
class MyClass {
    // 静的初期化子
    static {
        // このメッセージは、クラスが最初にロードされる時に一度だけ出力されます。
        System.out.println("Static initializer called");
    }

    MyClass() {
        // このメッセージは、コンストラクタが呼び出されるたびに出力されます。
        System.out.println("Constructor called");
    }
}

public class Main {
    public static void main(String[] args) {
        // クラスのロード時に出力
        // -> 出力: "Instance initializer called"
        MyClass obj1 = new MyClass(); 
        // -> 出力: "Constructor called"
        MyClass obj2 = new MyClass(); 
        // -> 出力: "Constructor called" (静的初期化子は再び呼ばれない)
    }
}

細かい仕様

classのアクセス修飾子

  • public
  • デフォルト(修飾子無し)

の2種類しか使えません。

個人的な解釈(押したら開くよ)

「メソッドに対するclass」が「classに対するパッケージ」と解釈してアクセス修飾子の役割を書き出してみます。(当然厳密には違いますが、イメージのため)

  • デフォルト : 同一パッケージ内ならそのclassにアクセス可能
  • protected : 同一パッケージ内、及びそのパッケージを継承したパッケージからならclassにアクセス可能
  • private : 同一パッケージ内ならそのclassにアクセス可能

パッケージに継承の概念はないので、3つとも「同一パッケージ内ならアクセス可能」という同じ意味になったね!(迫真)

Javaの余計なことはさせないの原則に基づき、一番記述の少ないデフォルトを採用!

ちなみにこのように3つのアクセス修飾子が同じ意味になるのは、classの上位要素がパッケージだからです。

実際、クラス内クラス(内部クラス)では、内部クラスの上位要素はメソッドと同じようにクラスなので、privateなどのアクセス修飾子をつけることもできます。

可変長引数

void test(int... nums){}
のように、型に「…」をつけることで、その型の変数を任意の個数を受け入れることができます。

  • 引数の最後に1つだけ使用できる。
  • 当然複数の型を可変長引数に入れることはできない。
  • 受け取った引数は、指定した型の配列としてメソッド内で使用できる。

ちなみに、メソッド内では配列として使用されるので、mainメソッドの引数として使用することもできます。

// よくあるやつ
public static void main(String[] args){}

// ↓でもOK
public static void main(String... args){}

nullのprint

System.out.println(null); などでnullをprintするとnullと表示されます。

nullにはtoStringメソッドが定義されていないですが、例外的な処理ですね。

ガベージコレクション

参照されなくなったインスタンスのメモリを自動で開放する機能です。

到達不能コード

到達不能コードがあるとコンパイルエラーになります。
余計なことをさせないJavaらしいなと思います。

// returnでメソッドの制御が終わるので、以下はコンパイルエラー
// error: unreachable statement
return;
String a = "A";

第7章 クラスの継承、インターフェース、抽象クラス

オーバーライドとオーバーロード

ご存じの通り、

  • オーバーライド : メソッドを上書き
  • オーバーロード : 同名の新しいメソッドを作成

です。

オーバーライドとして扱われるか、オーバーロードとして扱われるかは、引数の型・数・順番(=シグネチャ)が既存の同名メソッドと同じかどうかで判定されます。(同じならオーバーライド

オーバーライド判定された場合(つまりシグネチャが同じ場合)、以下のオーバーライドの条件を満たさないとコンパイルエラーになります。

  • アクセス修飾子が同じ or より広い
  • 返り値の型が同じ or サブクラス
  • throws する例外やエラーの型が同じ or サブクラス

考え方としては、オーバーライド前のメソッドとして使用してもコンパイルエラーが生じないことが条件ってことですね。

頭の中でイマジナリープログラマーを作って、仮にオーバーライドされていることを知らずにメソッドを使おうとしたときに不都合が生じるか確認してみてください。

例えばアクセス修飾子の条件がないと、publicだと思ってメソッドを使おうとしたら、privateでオーバーライドされていて使えなかった!となりますね。

インターフェースと抽象クラス

インターフェースも抽象クラスも、処理内容を持たない抽象メソッドをもつことができるクラスです。

その特徴から、処理内容を記述するオーバーライドを前提としており、そのままではインスタンス化できません。

インターフェースと抽象クラスの違い

インターフェースは、さながら「全てのメソッドが抽象メソッドである抽象クラス」です。

従って、インターフェースには以下のような特徴があります。

  • 全てが抽象メソッドであるため、メソッドにabstructをつけななくてもabstructとして扱われる
  • オーバーライドを前提としており、アクセス修飾子を省略してもすべてpublicとして扱われる

そのほかの違いは以下の表にまとめました!

特徴インターフェース抽象クラス
クラスの種類インスタンス化できないインスタンス化できない
多重継承サポートする(複数のインターフェースを使用可能)サポートしない(単一継承のみ)
メソッドの実装抽象メソッドのみ
(Java 8以降ではdefaultメソッドとstaticメソッドが許可)
抽象メソッドと具体的なメソッドの両方が可能
インスタンス変数保持できない
(static で finalなフィールドは定義できる)
保持できる
コンストラクタ保持できない保持できる
アクセス修飾子メソッドとフィールドはpublicのみ任意のアクセス修飾子が可能

インターフェースの細かな仕様

java.lang.Objectのメソッドはオーバーライドできない
インターフェースのデフォルトメソッドの呼び出し
  • 直接の親のデフォルトメソッドしか呼び出せない
  • [インターフェース名].super.[メソッド] の形で実行する
    → 複数のインターフェースを継承できるので、インターフェース名の指定が必要なのは妥当ですね。

サブクラスのインスタンスの生成

  • super(引数)でスーパークラスのコンストラクタを使用できます。
  • サブクラスのコンストラクタの一番最初に一度だけスーパークラスのコンストラクタを呼び出す必要があります。(super(引数))
  • スーパークラスのコンストラクタを明示しない場合、super()がコンストラクタの一番最初に挿入されます。
    → スーパークラスに引数無しのコンストラクタが定義されていない場合、コンパイルエラーになります!

ポリモーフィズム

ポリモーフィズムって結局何なんでしょうね?

文脈で使われ方が違う気がするので、よくわかっていません(笑)

ただ局所的には、「同じ型で同じ名前のメソッドなのに違う処理を実装できる」ことだと思っています。

すなわち、その特徴を実現するオーバーライドこそがポリモーフィズムの肝であり、オーバーライドは特別!と考えると、いろいろな仕様が覚えやすいと感じます。

という前置きをしたうえ、継承の仕様を整理してみます。

継承の仕様

スーパークラスがA、サブクラスがBだとします。

  • 宣言した型のフィールドとメソッドが参照されます。
    • A a = new B(); → a.fieldやa.method()でAのフィールドやメソッドにアクセスする
    • B b = new B(); → b.fieldやb.method()でBのフィールドやメソッドにアクセスする
  • メソッドがオーバーライドされている場合は、インスタンス化したクラスのメソッドが参照されます。
    • A a = new B(); → a.method()でmethodがオーバーライドされている場合Bのmethodが呼び出される
    • B b = new B(); → b.method()でmethodがオーバーライドされている場合Bのmethodが呼び出される(こっちは当然だが…)

第8章 関数型インターフェース、ラムダ式

暗記項目はそれほどありません。

ただ、「クラス定義を簡略化した無名クラス」をさらに簡略化したものがラムダ式、というそもそもの構造がややこしいので、基本の理解が大切だと思います。

いわゆる「関数」のないJavaで関数を使おうと思うとこんなややこしくなるのか…とため息がでますね。

いろいろな省略

基本形は、

Function<String,String> function = (String s)->{return s;}

のようなイメージです。

省略できるものはこちら↓

  • パラメータの型:
    • ラムダ式のパラメータで型を省略できます。型はコンテキストから推論されます。
    • 例:(String s) -> s.length() は (s) -> s.length() と書けます。
  • 括弧(単一のパラメータの場合):
    • 単一のパラメータの場合、パラメータを囲む括弧を省略できます。
    • 例:(s) -> s.isEmpty() は s -> s.isEmpty() と書けます。
  • 波括弧とreturn文(単一の式の場合):
    • ラムダ本体が単一の式の場合、波括弧と return 文を省略できます。
    • 例:(a, b) -> { return a + b; } は (a, b) -> a + b と書けます。

java.lang.functionの代表的なクラス

インターフェースメソッド引数戻り値の型概要
Supplier<T>get()なしT何も受け取らず、結果を供給します。
Consumer<T>accept(T t)T tvoid単一の入力引数を受け取り、結果を返しません。
Function<T,R>apply(T t)T tR単一の入力引数を受け取り、結果を返します。
Predicate<T>test(T t)T tboolean単一の入力引数を受け取り、ブール値(真偽値)を返します。

第9章 API

くっそ楽しくない暗記の権化の章です。

とはいっても、ほかの言語と似ている部分も多いので、試験としてはノリで解けなくもないですが。

こういうのは、実務でよく使うものは勝手に覚えるものだと思っています。

Math

pow

  • 概要:ある数(ベース)を指定された値(指数)で累乗します。
  • 引数:二つの double 値。最初の引数がベースで、二つ目の引数が指数です。
  • 戻り値:ベースを指数で累乗した結果の double 値。
double result = Math.pow(2, 3); // 2の3乗は8

sqrt

  • 概要:指定された値の平方根を計算します。
  • 引数:一つの double 値。
  • 戻り値:引数の平方根の double 値。
double result = Math.sqrt(16); // 16の平方根は4

round

  • 概要:指定された浮動小数点数を最も近い整数に丸めます。
  • 引数:一つの double または float 値。
  • 戻り値:丸められた整数値。double 型の引数の場合、戻り値は long 型になります。float 型の場合は int 型です。
long roundedDouble = Math.round(3.5); // 3.5は4に丸められる
int roundedFloat = Math.round(2.1f); // 2.1fは2に丸められる

List.sort()

Javaの List インターフェースには sort メソッドが用意されており、これを使用してリスト内の要素を並び替えることができます。

概要

// メソッドシグネチャ
void sort(Comparator<? super E> c)

Eはメソッドを使用するListの型です。

自分もまったくなれない標記ですが、<? super E>は「EまたはEのsuperクラス」ということ。

例えば、Numberクラスで定義したComparatorをIntergerクラス(Numberのサブクラス)でも使用できるよってことですね。

  • 引数:
    • Comparator<? super E> c:リストの要素を比較するための Comparator オブジェクト。
  • 戻り値:
    • なし(void)。メソッドはリスト自体を変更(並び替え)します。
      つまり、インスタンス自体を変更する破壊的なメソッドです。
  • 動作:
    • 与えられた Comparator に基づいてリストの要素を並び替えます。
      Comparator が null の場合、要素が自然順序付けをサポートしていると見なされ、その順序に従って並び替えられます。

使用例

自然順序での並び替え(Comparatorなし)

IntegerString のように自然順序付けをサポートする型のリストの場合、単純に null を渡すことで並び替えを行えます。

基本は昇順です。(小→大)

List<Integer> numbers = Arrays.asList(3, 1, 4, 1, 5, 9);
numbers.sort(null);
// numbers は [1, 1, 3, 4, 5, 9] に並び替えられる
カスタムComparatorを使用した並び替え

特定の順序で並び替えたい場合、または自然順序付けをサポートしていないオブジェクトを並び替える場合には、Comparator を実装して渡します。

List<String> words = Arrays.asList("apple", "pineapple", "banana", "orange");
words.sort((s1,s2)->s1.length()-s2.length()); // 文字列の長さに基づいて並び替え
// words は ["apple", "banana", "orange", "pineapple"] に並び替えられる

Comparatorは関数型インターフェース(メソッドが1つのインターフェース)なので、ラムダ式で直接Comparatorのメソッド(compare)の処理を記述できます。

compareは2つの引数を持ち、正の整数を返す場合は順番を入れ替えます。

よく入れ替える場合は「1」を、入れ替えない場合は「-1」が使われますね。

どっちがどっちだっけと毎回忘れていたのですが、1(い)れかえるで無理やり覚えました。

List.forEach()

forEach メソッドは、リストの各要素に対して指定されたアクション(通常はラムダ式またはメソッド参照)を実行します。

シグネチャ↓

void forEach(Consumer<? super T> action)

ここで Consumer<? super T> は、引数として受け取ったオブジェクトに対して操作を行う関数型インターフェースです。

ラムダ式のとこでもでてきますが、Consumerは代表的な関数型インターフェースであり、メソッドの実装をラムダ式で行うことができます。

List<String> list = Arrays.asList("Apple", "Banana", "Cherry");

// ラムダ式を使用して、リストの各要素を出力
list.forEach(element -> System.out.println(element));

// メソッド参照を使用して、リストの各要素を出力
list.forEach(System.out::println);

メソッド参照について

メソッド参照(Method Reference)は、Java 8で導入された機能で、既存のメソッドやコンストラクタを簡潔な形式で参照するための構文です。

これはラムダ式をさらに簡略化したものであり、特に関数型インターフェースの実装として使用されます。

つまりは、関数型インターフェースの実装に他のメソッドを流用するということで、便利は便利なのですが、ラムダ式で流用したいメソッドを呼び出す方がわかりやすいですし、なにより仕様が結構複雑なので私は使わないと思います!(笑)

大きくは、呼び出すメソッドがstaticメソッドかinstanceメソッドかでメソッドの呼び出され方が変わります。。。
詳しくはこちら↓↓↓

  • 静的メソッド参照:ClassName::staticMethodName
    • 例:Arrays::sort(a) -> Arrays.sort(a) と同じ意味です。
  • 任意オブジェクトのインスタンスメソッド参照:ClassName::instanceMethodName
    • 例:String::lengths -> s.length() と同じ意味です。
  • コンストラクタ参照:ClassName::new
    • 例:ArrayList::new() -> new ArrayList() と同じ意味です。

まあ、試験対策としては記述方法(Class名::メソッド)を覚えればOKだと思います。

ArrayList

ミュータブル(変更可能)な代表的なListのサブクラスです!

問題として出てくる知識的な面でいうと、

  • ミュータブルなのでスレッドセーフではない
  • for文でList内を読み出し中にListを操作する(要素の削除など)と例外が発生

こんなところでしょうか。

あとは、代表的なメソッドです。
Eはジェネリクス(<ここ>)で指定した型が入ります。

メソッド説明
add(E e)指定された要素をリストの末尾に追加する。
add(int index, E element)指定された位置に要素を挿入する。
remove(int index)指定された位置にある要素を削除する。
remove(Object o)最初に出現する指定された要素をリストから削除する。
get(int index)指定された位置にある要素を返す。
set(int index, E element)指定された位置にある要素を指定された要素で置き換える。
size()リストに含まれる要素の数を返す。
clear()リストからすべての要素を削除する。
contains(Object o)リストが指定された要素を含んでいるかどうかを判定する。
isEmpty()リストが空かどうかを判定する。

「remove」や「contains」などで、指定したObjectと一致するかどうかは同値性、つまりObjectのequalsメソッドで判定されます。

Arrays

Arraysはjava.utilパッケージに属するクラスで、便利な関数的なstaticメソッドを提供する役割です。

メソッド説明
Arrays.asList(T… a)指定された要素を持つ固定サイズのリストを返す。
Arrays.sort(T[] a)指定された配列の要素を自然順序に従ってソートする。
Arrays.equals(T[] a, T[] a2)二つの配列が等しいかどうかを判定する。
Arrays.mismatch(T[] a, T[] a2)二つの配列を比較し、最初に異なる要素のインデックスを返す。
すべて一致している場合は「-1」。
Arrays.compare(T[] a1, T[] a2)二つの配列を辞書式の順序で比較し、比較結果を整数で返す。
a2が先 : 1
a1が先 : -1
同じ : 0

Characterのstaticメソッド

メソッド名を見ればなんとなく予想できるのでたぶん大丈夫です。

メソッド説明
isLetter(char ch)指定された文字が文字(アルファベット)かどうかを判定する。
isDigit(char ch)指定された文字が数字かどうかを判定する。
isWhitespace(char ch)指定された文字が空白文字かどうかを判定する。
isAlphabetic(int codePoint)指定されたコードポイントがアルファベットかどうかを判定する。
isUpperCase(char ch)指定された文字が大文字かどうかを判定する。
isLowerCase(char ch)指定された文字が小文字かどうかを判定する。
toUpperCase(char ch)指定された文字を大文字に変換する。
toLowerCase(char ch)指定された文字を小文字に変換する。
toString(char c)指定された文字を文字列に変換する。

LocalDate

LocalDate クラスは、日付を表すための不変クラスであり、時間やタイムゾーンは含まれません。

不変(イミュータブル)クラスなので、日付を操作するメソッドでもインスタンス自体は変更せず、変更された新しいインスタンスを生成して返すことに注意!

メソッドは↓にまとめています。
いろいろありますが、雰囲気でなんとかなると思います!(適当)

ofメソッドは月(month)が1始まりなので、0だと例外が発生します。

「なにを当たり前な…」と思われるかもしれませんが、JavaのCalendarクラスという日時を扱うクラス(現在は非推奨)では月が0始まりでした。

なので、ひっかけのつもりで問題にでることがあると思います。
Calendarクラスを作った人は何を考えていたのだろうか…。

メソッド説明
now()現在の日付を取得する。
of(int year, int month, int dayOfMonth)指定された年、月、日から LocalDate オブジェクトを生成する。
plusDays(long daysToAdd)指定された日数を加えた日付を返す。
minusDays(long daysToSubtract)指定された日数を減じた日付を返す。
getYear()年を取得する。
getMonth()月を取得する。
getDayOfMonth()月の日を取得する。
getDayOfWeek()曜日を取得する。
with(TemporalAdjuster adjuster)指定された調整器で日付を調整する。
isBefore(LocalDate other)指定された日付より前かどうかを判定する。
isAfter(LocalDate other)指定された日付より後かどうかを判定する。

HashMap

Javaにおける HashMap は、Map インターフェースを実装したコレクションクラスの一つで、キーと値のペアを格納するためのデータ構造です。

Pythonでいう辞書、javascriptでいう連想配列と似た概念ですね。

主な特徴

  • ハッシュテーブルに基づく実装:
    HashMap は内部的にハッシュテーブルを使用しており、キーのハッシュコードに基づいて値を格納します。
  • キーの一意性:
    各キーはユニークでなければならず、一つのキーに対して一つの値が関連付けられます。
    同じキーで新しい値をputすると、古い値は新しい値で上書きされます。
  • null 値のサポート:
    HashMap はキーとして null を許容し、値としても null を格納できます。
  • 順序保証なし:
    HashMap は要素の挿入順序を保持しません。
    キーの順序はハッシュコードに基づいており、予測不可能です。

試験対策のみならず、どれも実用上重要です!

基本的なメソッド

操作説明
put(K key, V value)指定されたキーと値をマップに追加する。
get(Object key)指定されたキーに関連付けられた値を取得する。
remove(Object key)指定されたキーに関連付けられた値を削除する。
containsKey(Object key)マップが指定されたキーを含んでいるかどうかを確認する。
containsValue(Object value)マップが指定された値を含んでいるかどうかを確認する。
size()マップに含まれるキー-値ペアの数を返す。
isEmpty()マップが空かどうかを確認する。
clear()マップからすべてのキー-値ペアを削除する。

第10章 例外処理

Javaでは、エラーや予期せぬ状況を「例外」として扱います。

どれがコンパイル時チェックされるかが少しややこしいですが、それほど覚えることは多くないので気楽にいきましょう。

例外とエラーの種類

Javaにおける「例外」と「エラー」は、両方ともThrowableクラスから派生していますが、その用途と対処方法には大きな違いがあります。

  • 例外 (Exceptions):
    • チェック例外 (Checked Exceptions): メソッドが投げる可能性のある例外で、コンパイル時にチェックされます。これらは一般的に回復可能で、適切な処理が期待されます。
    • 非チェック例外 (Unchecked Exceptions): 実行時例外(RuntimeExceptionとそのサブクラス)は、コンパイラによるチェックが不要です。これらは通常、プログラミングエラーによって発生します。
      (つまり、書いているあなたが原因なので、ちゃんと対処すれば生じないはずだからチェックしませんよという例外)
  • エラー (Errors): エラーは、アプリケーションが処理すべきでない重大な問題を表します。これには、OutOfMemoryErrorやStackOverflowErrorなど、通常はアプリケーションが回復できないようなシステムレベルの問題が含まれます。エラーは、通常、JVMやハードウェアの異常によって発生し、アプリケーションコードでキャッチして処理することは推奨されません。
    throwやcathcは必須ではない

要するに、例外はアプリケーションレベルの問題で、適切に処理することが期待されています。

一方、エラーはシステムレベルの重大な問題であり、通常はアプリケーションが対処できるものではありません。

試験的に重要なことは、チェック例外はthrowsで例外を出す可能性を明示するか、catchで例外を処理する必要があるということです。

ぶっちゃけこれだけ覚えておけばOKではある(笑)

try-cathc-finally

例外を捕捉し処理する主要な方法です。

tryブロック内には例外が発生する可能性のあるコードを配置し、catchブロックにはその例外を捕捉し処理するコードを記述します。

finallyブロックは例外の有無に関わらず、tryブロックの後に常に実行されるブロックです。

try {
    // 例外が発生する可能性のあるコード
} catch (ExceptionType name) {
    // 例外を処理するコード
} finally {
    // 常に実行されるコード
}

catchブロックは複数配置することができます。

注意点は、より具体的な例外タイプを先に配置し、より一般的な例外タイプは後に配置すること!

これは、Javaが最初に一致するcatchブロックを実行し、残りは無視するためです。
(絶対無視されるcatchを書くな←余計なことをするなエラー)

ちなみにfinallyブロックを複数配置するのはNGです。
(1つにまとめるのと変わらない←余計なことをするなエラー)

try-with-resource

try-with-resourcesはリソース(ファイル、ネットワーク接続など)の自動クローズをサポートします。

リソースを扱うクラスはAutoCloseableまたはCloseableインターフェースを実装している必要があります。

try-with-resourcesブロックでは下のコードのように、リソースを宣言しブロックの終了時に自動的にリソースがクローズされます。
(つまり実行順番は、try → リソースのクローズ → cathc処理(あれば) → finally)

try (ResourceType resource = new ResourceType()) {
    // リソースを使用するコード
}

代表的なエラーと例外

主な例外とその説明

例外タイプ説明
NullPointerExceptionオブジェクト参照がnullの場合に発生する例外です。
ArrayIndexOutOfBoundsException配列の範囲外のインデックスにアクセスした場合に発生します。
ちはみに類似の例外で↓のようなものもあります。
・文字列に関するStringIndexOutOfBoundsException
・コレクションに関するIndexOutOfBoundsException
ClassCastException不適切な型変換(キャスト)を試みた場合に発生します。
ちなみに、継承関係にない土台無理なキャストをした場合は例外ではなくコンパイルエラーになります。
IllegalArgumentExceptionメソッドに不適切または不正な引数が渡された場合に発生します。
ArithmeticException算術計算例外(例えば、ゼロによる除算)の場合に発生します。
FileNotFoundException要求されたファイルが存在しない場合に発生します。
IOException入出力操作で例外が発生した場合に使用される一般的なクラスです。
SQLExceptionデータベースアクセス中のエラーに関連する情報を提供します。

主なエラーとその説明

エラータイプ説明
OutOfMemoryErrorJVMがオブジェクト用にメモリを割り当てることができない場合に発生します。
StackOverflowErrorスタックオーバーフローが発生した場合に発生します(通常、再帰的なメソッド呼び出しによる)。
試験では無限ループの際に発生することがよく問われます。
NoClassDefFoundError要求されたクラスがランタイムに見つからない場合に発生します。
Error一般的なエラークラスで、JVMが回復不能な状況に遭遇したことを示します。
AssertionErrorアサーションが失敗した場合にスローされます。
LinkageErrorクラスの依存関係が破損している場合に発生します。
VirtualMachineErrorJVMが操作を続行できない致命的なエラーに遭遇した場合に使用されます。
OutOfMemoryErrorとStackOverflowErrorの違い(押したら開くよ)

読み飛ばしていいコーナーです。

自分もにわか知識なのですが、OutOfMemoryErrorはメモリ割り当てに関連するエラーであり、ヒープメモリが枯渇した場合に発生します。一方で、StackOverflowErrorスタックメモリの使用量が限界を超えた場合、特に深い再帰呼び出しによって発生するエラーです。

ヒープメモリ

実行時に動的に割り当てられるオブジェクトと配列のためのメモリ領域です。
Javaでは、新しいオブジェクトは常にヒープ上に作成されます。

スタックメモリ

各スレッドごとに割り当てられるメモリ領域です。
メソッドの呼び出しに伴って生成されるスタックフレーム(ローカル変数、引数、戻り値など)のために使用されます。

第11章 モジュールシステム

モジュールはクラスが入ったパッケージの、さらに親玉のような存在ですね。

モジュールに関する情報はmodule-info.javaファイルに記述され、モジュールの名前、必要な依存関係、エクスポートするパッケージなどを定義しています。

module myModule {               // ← ここがモジュール名
    requires another.module;    // ← requiresで使用する「モジュール」を定義
    exports somePackage;        // ← exportsで公開する「パッケージ」を定義

    // ↓ transitveをつけると、myModuleをrequiresするモジュールは、
    // myModuleがrequires transitive したモジュールもrequires(依存)することになります。
    requires transitive example.anotherlibrary;
}

基本的なコマンド

コンパイル(javacコマンド)

javac --module-path <モジュールのパス> --module-source-path <sourcepath> -d <output-directory> <module-info.java and other sources>

Javaのモジュールをコンパイルするには、javacコマンドに–module-path(または-p)オプションを使用してモジュールパスを指定します。

また、モジュール記述子module-info.javaもコンパイル対象に含める必要があります。

// 例
// --module-path lib : libディレクトリにあるモジュールを使うよ
// --module-source-path src : コンパイルするソースファイルはsrcにあるよ
// -d out : javac結果をoutディレクトリに出力するよ
// 残り : コンパイルするファイルのパス
javac --module-path lib --module-source-path src -d out src/com.example.myModule/module-info.java src/com.example.myModule/com/example/myModule/MyClass.java

実行(javaコマンド)

java --module-path <モジュールのパス> --module <モジュール名>/<完全修飾メインクラス>

Javaのモジュールシステムでアプリケーションを実行する際は、javaコマンドに–module-path(または-p)オプションと–module(または-m)オプションを使用します。

また、<モジュール名>/<完全修飾クラス> というように、モジュール名とクラス名を「/」で区切って記述します。

// 例
// --module-path mods : modsディレクトリにあるモジュールを使うよ
// --module com.example.myapp : com.example.myapp モジュールのクラスを実行するよ
// /com.example.myapp.Main : com.example.myapp.Main クラスを実行するよ
java --module-path mods --module com.example.myapp/com.example.myapp.Main

また、add-exports <対象モジュール>/<公開するパッケージ>=<利用するモジュール>とすることで、その実行時のみ対象モジュールを暫定的に使用することができます。

わかりにくいのですが、「=」の右側のモジュールが左側のパッケージを利用します。
(「=」じゃなくて「=>」の方が分かりやすくない?笑)

いまいちいつ使うのかわかりませんが、デバッグ用ですかね。

// 例
java --add-exports java.base/sun.security.x509=ALL-UNNAMED -m com.example.myModule/com.example.myModule.Main

その他コマンド

試験に出てくるのはそう多くないですが、2つほど紹介します。

モジュールの設定情報を調べる
// 方法1 : java --describe-module <モジュール名>
java --describe-module java.base

// 方法2 : jmod describe <.jmodファイルパス>
jmod describe path/to/module.jmod
モジュールの依存関係を調べる
// 方法1 : java --show-module-resolution <モジュール名>
java --show-module-resolution --module-path mods --module mymodule/com.example.Main

// 方法2 : jdeps <options> <jarファイル or モジュール名>
jdeps myapp.jar

思ったより長くなった!!

しかし、このページの内容でまず間違いなく知識を問う問題は解けると思います!!