LoginSignup
0
0

Java11 学習

Last updated at Posted at 2024-06-01

Java 11 Silver 受験対策 & 今後の備忘録

Javaのデータ型にはプリミティブ型参照型がある。

参照型には更にクラス型、配列型、列挙型などの分類がある。

  • プリミティブ型
    • boolean
    • char
    • byte
    • short
    • int
    • long
    • float
    • double
  • 参照型
    • クラス型
    • インターフェース型
    • 配列型
    • 列挙型

プリミティブ型

サイズ 説明
boolean 仕様無 真偽値 true, false
char 16bit Unicode文字 ¥u0000 ~ ¥uFFFF
byte 8bit 整数 -128 ~ 127
short 16bit 整数 -32768 ~ 32767
int 32bit 整数 -2137383648 ~ 2147483647
long 64bit 整数 -9223372036854775808 ~ 9223372036854775807
float 32bit 単精度浮動小数点数 ±3.40282347E+38 ~ 1.40239846E-45
double 64bit 倍精度浮動小数点数 ±1.79769313486231570E+308±4.94065645841246544E-324

String型はプリミティブ型ではなく参照型

参照型

クラス型、配列型、列挙型がある。

リテラル

整数リテラル

デフォルトではint型として扱われる。

整数リテラルをlong型として扱いたい場合、末尾にlもしくはLの接尾辞をつける。

long a = 100L;

また整数リテラルをshort型やbyte型として扱うための接尾辞は存在しない。その代わりにint型とshort型などは自動変換される。

// 10はint型として扱われる整数リテラルだが、short型に代入できる(short型に自動変換される)
short i = 10;

10進数、2進数、8進数、16進数

デフォルトでは10進数として扱われる。

2進数で表したい場合、0bを接尾辞としてつける。

byte ichi = 0b0001;  // 1
short ni = 0b0010;   // 2
int san = 0b00011;   // 3

System.out.println(ichi);
System.out.println(ni);
System.out.println(san);
// >> 1
// >> 2
// >> 3

8進数で表したい場合、0を接尾辞としてつける。

byte nana = 07;     // 7
short hachi = 010;  // 8
int kyu = 011;      // 9

System.out.println(nana);
System.out.println(hachi);
System.out.println(kyu);
// >> 7
// >> 8
// >> 9

16進数で表したい場合、0xを接尾辞としてつける。

byte jugo = 0xF;     // 15
short juroku = 0x10; // 16
int junana = 0x11;   // 17

System.out.println(jugo);
System.out.println(juroku);
System.out.println(junana);
// >> 15
// >> 16
// >> 17

浮動小数点数リテラル

デフォルトではdouble型として扱われる。

float型として扱いたい場合、末尾にfもしくはFの接尾辞をつける。

float a = 100.0F;

真偽リテラル

boolean型として扱われる。

文字リテラル

char型として扱われる。

文字リテラルはシングルコーテーション')で囲う必要がある。

またJavaは Unicode を文字コードとして採用していて、Unicodeエスケープシーケンスを使って文字を表現する際にはバックスラッシュ\)を使用する。(Windows環境では¥

Unicode の文字は U+0000 ~ U+FFFFまでの文字コードが存在するが、Javaでは\u0000 ~ \uFFFFで表現する。

char komoji = '\u0061';
char omoji = '\u0041';

System.out.println(komoji);
System.out.println(omoji);
// >> a
// >> A

文字リテラルは直接16進数や10進数でも表現できる。(0x0000 ~ 0xFFFF は10進数では 0 ~ 65535であるため、次のような表現も可能。

char komoji = 0x0061;
char komoji2 = 97; // 0x0061 = 97

char omoji = 0x0041;
char omoji2 = 65; // 0x0041 = 65

System.out.println(komoji);
System.out.println(komoji2);
// >> a
// >> a

System.out.println(omoji);
System.out.println(omoji2);
// >> A
// >> A

char型の変数に文字列リテラルは代入できない。

char ok = 'a'; // 有効
char error = "a"; // コンパイルエラー

Javaはシングルコーテーションとダブルコーテーションに明確な違いがあるため注意。

文字列リテラル

String型として扱われる。

文字列リテラルはダブルコーテーション")で囲う必要がある。

String

String型はプリミティブ型ではなく参照型(java.lang.String)に分類される。

Stringクラスは内部で文字列をchar型の配列として扱っている。

String型はインターフェースCharSequenceの実装クラスでもある。

String型のインスタンス化

参照型のデータ型はnew演算子によってインスタンス化を行う。

MyClass a = new MyClass();

ただし、String型だけは特別扱いで、文字列リテラルによってString型のインスタンスが生成できるようになっている。

String a = "hello world"; // String型はプリミティブ型ではなく参照型

immutable

Stringクラスは immutable なクラスであるため、一度生成したインスタンスは中身を変更することができない。

String型変数への再代入は、インスタンスの中身変更ではなく実際にはインスタンス新規生成を意味する。

// インスタンスに変更を加えているように見えるが、実際にはインスタンスを変更している訳ではない
String a;
a = "hello world";
a = "ABCD";
// 実際は immutable なインスタンスを新規生成している
String a;
a = new String("hello world");
a = new String("ABCD");

このため、subString()concat()toUpperCase()などのメソッドが返すインスタンスは、実行されたメソッドのインスタンスではなく、新しいインスタンスということになる。

コンスタントプール

前述の通り、「文字列リテラルの使用」は「java.lang.Stringクラスのインスタンス生成」を意味する。

しかし実際の文字列リテラルは、同じものが頻繁に使用されることが多い。同じ文字列であるのにわざわざ別のインスタンスとして扱うメリットはなく、逆に新たなインスタンス生成の度にメモリ領域が必要になってしまい非常に非効率になる。

String a = "hello world";
String b = "hello world";
String c = "hello world";
String d = "hello world";

そのため文字列リテラルにはコンスタントプールという仕組みがある。

コンスタントプールは文字列リテラルから生成されたStringインスタンスを管理するためのメモリ領域のことを指す。

通常、文字列リテラルが使用されるとまず最初に定数用のメモリ空間(コンスタントプール)にその文字列リテラルから生成されたインスタンスが既に存在しないかがチェックされる。存在しない場合に限り、String型インスタンスが生成され、生成されたインスタンスがコンスタントプールに配置される。

この仕組みにより、同じ文字列リテラルからString型インスタンスを生成する際は、過去に生成されたStringインスタンスへの参照が再利用される。

// 同じインスタンスへの参照が再利用されていることがわかる
String a = "hello world"; // コンスタントプール生成
String b = "hello world"; // コンスタントプールに存在するインスタンスへの参照を再利用
String c = "hello world"; // コンスタントプールに存在するインスタンスへの参照を再利用
String d = "hello world"; // コンスタントプールに存在するインスタンスへの参照を再利用

if (a == b && b == c && c == d) {
    System.out.println("各変数は同じインスタンスを参照している");
}
// >> 各変数は同じインスタンスを参照している

ただし、プログラムの中で動的に生成された文字列はコンスタントプールに配置されない。また明示的にnew演算子によって生成されたインスタンスが生成された場合、Stringインスタンスはコンスタントプールからの再利用は行われない。

Stringクラスのintern()メソッドは、コンスタントプール(を含むメモリ領域)内からStringインスタンスを検索してその参照を返すことができる。

String a = "hello world";
// 明示的にnewしているので、コンスタントプールからのインスタンスの再利用、またはコンスタントプールへの保存がされない
String b =  new String("hello world");

if (a == b) {
    System.out.println("同じインスタンスを参照している");
}else{
    System.out.println("別のインスタンスを参照している");
}
// >> 別のインスタンスを参照している

if (a == b.intern()) {
    System.out.println("同じインスタンスを参照している");
}else{
    System.out.println("別のインスタンスを参照している");
}
// >> 同じインスタンスを参照している

初期値

クラスのフィールドは、デフォルトで初期化されるため、int型のメンバ変数は0boolean型はfalse、参照型のメンバ変数はnullに初期化される。

一方メソッド内のローカル変数は、明示的に初期化されない限り初期値が設定されない。

class MyClass {
    int a; // 0に初期化される
    boolean b; // falseに初期化される
    String c; // nullに初期化される

    void myMethod() {
        int x; // 初期化されない
        System.out.println(x); // コンパイルエラー
    }
}

型推論

varによる型推論は、ローカル変数にしか利用できない。

型推論はコンパイル時に代入演算子の右辺の値を元に行われる。

宣言時に型を指定しなかった場合、Object型を指定したものとして扱われる。

// 型を指定していない
ArrayList list = new ArrayList<>();

// Object型を指定したものとして扱われる
ArrayList<Object> list = new ArrayList<>();

また、配列の初期化時にも型推論が働く。

int[] array = {1, 2, 3};

初期化子(Initializer)

クラスの初期化時に特定のコードを実行するための特殊なブロックのこと。

static初期化子(static initializer)とインスタンス初期化子(instance initializer)がある。

static初期化子

クラスがロードされるときに一度だけ実行されるコードブロック。

class MyClass {
    static {
        System.out.println("hello world");
    }
}

静的なフィールドを初期化する際に有効。

class MyClass {
    static int a;
    
    static {
        a = 10;
    }
}

インスタンス初期化子

インスタンス生成の度に実行されるコードブロック。

コンストラクタの前に実行される。

class MyClass {
    {
        System.out.println("hello world");
    }
}

オーバーロードによるコンストラクタが複数あるような場合に、全てのコンストラクタに共通の処理をまとめたい時などに有効。

class MyClass {
    {
        // コンストラクタの前に実行される
        System.out.println("共通処理");
    }

    MyClass(int a) {
        System.out.println("コンストラクタ1");
    }

    MyClass(int a, int b) {
        System.out.println("コンストラクタ2");
    }
}

実行順

  1. スーパークラスのstatic初期化子
  2. サブクラスのstatic初期化子
  3. スーパークラスのインスタンス初期化子
  4. スーパークラスのコンストラクタ
  5. サブクラスのインスタンス初期化子
  6. サブクラスのコンストラクタ
SuperClass
class SuperClass {
    static {
       System.out.println("①static initializer of super class");
    }

    {
        System.out.println("③instance initializer of super class");
    }

    SuperClass(){
        System.out.println("④constructor of super class");
    }
}
SubClass
class SubClass extends SuperClass{
    static {
        System.out.println("②static initializer of sub class");
    }

    {
        System.out.println("⑤instance initializer of sub class");
    }

    SubClass(){
        System.out.println("⑥constructor of sub class");
    }
}
実行順
SuperClass a = new SubClass();
// >> ①static initializer of super class
// >> ②static initializer of sub class
// >> ③instance initializer of super class
// >> ④constructor of super class
// >> ⑤instance initializer of sub class
// >> ⑥constructor of sub class

ワイルドカード

型パラメータを柔軟に指定するための機能。

ワイルドカードを使うことで、特定の型に依存しない汎用的なコードを記述することができる。

class Animal { }
class Dog extends Animal { }
class Cat extends Animal { }
未指定ワイルドカード(?)
List<?> list = new ArrayList<>();
上限境界ワイルドカード(? extends T)
List<Dog> dogs = new ArrayList<>(){
    {
        add(new Dog());
        add(new Dog());
        add(new Dog());
    }
};
List<? extends Animal> animals = dogs;
下限境界ワイルドカード(? super T)
List<Animal> animals = new ArrayList<>(){
    {
        new Animal();
        new Animal();
        new Animal();
    }
};
List<? super Dog> dogs = animals;

共変性・反変性

ジェネリクスや型パラメータにおいて、型の代入互換性に関する性質として共変性反変性がある。

共変性

Covariance

型パラメータ(T)がその型のサブタイプ(?)に対して有効である性質。

? extends T(上限境界ワイルドカード)で表現する。

主に読み取り専用の操作に使用される。リストからの要素の取得はできるが、要素の追加はできない。

class Animal { }
class Dog extends Animal { }
class Cat extends Animal { }
共変性
List<Dog> dogs = new ArrayList<>(){
    {
        add(new Dog());
        add(new Dog());
        add(new Dog());
    }
};

// 共変性が成り立つ
List<? extends Animal> animals = dogs;

// 要素の取得はできる
Animal animal = animals.get(1);

// 要素の追加ができない(コンパイルエラー)
animals.add(new Dog());

反変性

Contravariance

ある型パラメータ(T)がその型のスーパータイプ(?)に対しても有効である性質。スーパータイプにはインターフェースも含む。

? super T(下限境界ワイルドカード)で表現する。

主に書き込み専用の操作に使用される。リストへの要素の追加はできるが、型安全が失われるため要素の取得はできない。

class Animal { }
class Dog extends Animal { }
class Cat extends Animal { }
List<Animal> animals = new ArrayList<>(){
    {
        new Animal();
        new Animal();
        new Animal();
    }
};

// 反変性が成り立つ
List<? super Dog> dogs = animals;

// 要素の追加はできる
dogs.add(new Dog());

// 要素の取得は型安全が保証されないためキャストが必要(コンパイルエラー)
Animal animal = dogs.get(1);

// Object型として扱うならOK
Ocject obj = dogs.get(1);

共変戻り値

サブクラスでオーバーライドされたメソッドの戻り値型が、親クラスで定義されたメソッドの戻り値型のサブタイプにすることができるという概念のこと。

共変戻り値
class Animal {
    // 戻り値型: Animal型
    Animal getAnimal() {
        return new Animal();
    }
}

class Dog extends Animal {
    // 戻り値型: Dog型(アニマルのサブクラス)
    @Override
    Dog getAnimal() {
        return new Dog();
    }
}
ジェネリクスと共変戻り値
class SuperClass<T> {
    // 戻り値型: T型
    T getItem(T t) {
        return t;
    }
}

class SubClass extends SuperClass<String> {
    // 戻り値型: String型(ジェネリクス T の具体型)
    @Override
    String getItem(String arg) {
        return "hello";
    }
}

ネストしたクラス

クラスの内部でさらにクラスを定義した場合、外側のクラスはEnclosingクラスと呼ばれ、内側のクラスはネストしたクラスと呼ばれる(enclose=囲う)。

いくつかの分類がある。

ネストしたクラス public protected private abstract final stataic
インナークラス クラス内で定義
ローカルクラス メソッド内で定義 × × × ×
匿名クラス メソッド内で定義 × × × × × ×

インナークラス

クラス内で定義されたクラスをインナークラスと言う。

インナークラス
class OuterClass {
    class InnerClass {
    
    }
}

インナークラスはEnclosingクラスと同様のアクセス修飾子(public, protected, private, abstract, final)を使用する事ができる。

またインターフェースを実装(implements)することもできる。

インナークラスはEnclosingクラスのインスタンスに属しているため、Enclosingクラスのインスタンスを介してアクセスを行う。

OuterClass outer = new MyClass();
OuterClass.InnerClass inner = outer.new InnerClass();

以下のように1行にまとめて記述することもできる。

OuterClass.InnerClass inner = new OuterClass().new InnerClass();

またimportを行えば、このような記述も可能。

インナークラスのimportもできる
import パッケージ名.OuterClass.InnerClass;

InnerClass inner = new OuterClass().new InnerClass();

特徴

インナークラスはEnclosingクラスで定義されたフィールドやメソッドと同様に、Enclosingクラスのインスタンス変数に直接アクセスする事ができる(ここが個人的に最初全然理解できていなかったところで、インナークラスを理解する上での肝となるところ)。

Enclosingクラスのインスタンス変数に直接アクセスできる
class OuterClass {
    String outerField = "hello";

    class InnerClass {
        String innerFiled = outerField + " world";
    }
}

この特徴からわかるように、インナークラスはEnclosingクラスのインスタンスに依存している。

インナークラスはEnclosingクラスのインスタンスに依存している

staticインナークラス

インナークラスをstatic修飾したものを特にstaticインナークラスと呼ぶ。

staticインナークラス
class OuterClass {
    static class InnerClass {

    }
}

staticインナークラスは、インスタンス化にEnclosingクラスのインスタンスを必要としない。

OuterClass.InnerClass inner = new OuterClass.InnerClass();
import パッケージ名.OuterClass.InnerClass;

InnerClass inner = new InnerClass();

staticインナークラスはEnclosingクラスのインスタンスではなく、Enclosingクラス自体に属するため、staticインナークラスはEnclosingクラスのインスタンスに依存しない。

そのため、Enclosingクラスの非staticフィールドや、非staticメソッドにはアクセスする事ができない。

class OuterClass {
    String outerField = "hello";

    static class InnerClass {
        // コンパイルエラー
        String innerFiled = outerField + " world";
    }
}

staticインナークラスはEnclosingクラスのインスタンスに依存しない。
Enclosingクラス自体に依存している。

インナークラスでstaticフィールドは定義できない

インナークラス内でstaticなフィールドを定義しようとした場合、コンパイルエラーが発生する。

class OuterClass {
    class InnerClass {
        // コンパイルエラー
        static int staticField = 0;

        // 定数は定義可能
        static final int CONSTANT_FIELD = 10;
    }  
}

staticなフィールドは、インスタンスではなくクラス自体に依存するため、クラスをstatic修飾しなければならない。

class OuterClass {
    static class InnerClass {
        static int staticField = 10;
    }        
}

ローカルクラス

メソッド内で定義されるクラスをローカルクラスと言う。

(メソッド内で定義される変数をローカル変数と言う)

ローカルクラス
class OuterClass {
    void outerMethod() {
        class LocalClass {
        
        }
    }
}

ローカルクラスは自身が定義されたスコープ内でのみ、インスタンス化を行う事ができる。

class OuterClass {
    void outerMethod() {
        class LocalClass {
           
        }

        LocalClass local = new LocalClass();
    }
}

Factoryパターンを使用する際に、クラス定義をメソッド内に隠蔽させる使い方などがある。

Factoryパターンについてはこちら。

ローカルクラスのインスタンスは、メソッドの実行後もメモリ上に存在し続ける可能性がある。

interface MyInterface {
    void myMethod();
}
class OuterClass {
    MyInterface outerMethod() {
        class LocalClass implements MyInterface {
            @Override
            public void myMethod() {
            }
        }

        return new LocalClass();
    }
}
MyInterface local = new OuterClass().outerMethod();
// outerMethod()実行後も、LocalClassのインスタンスはまだ生きている
local.myMethod();    

一方で、ローカル変数のスコープはメソッドの終了とともに消滅する(メモリ上から削除される)ため、ローカルクラス内でローカル変数を使用した場合、再代入を行う事ができない実質的にfinal(effectively final)な定数扱いになる(ラムダ式と同様)。

ローカルクラスに対しては、ローカル変数の参照ではなく値のコピーが渡っているため、ローカル変数のスコープを抜けた後も定数として使用することができる。

class OuterClass {
    void outerMethod() {
        String localVal = "hello";
        class LocalClass {
            void innerMethod(){
                // コンパイルエラー
                localVal = "world";
            }
        }
    }
}

ちなみにJavaScriptではクロージャーと呼ばれる、外側のスコープ(レキシカルスコープ)を保持し続ける関数を定義する事ができる。

JavaScript
function outer() {
    // レキシカルスコープ
    let outerVar = "hello";
    
    function inner() {
        outerVar = "world";
        console.log(outerVar);
    }
    
    return inner;
}

const innerFn = outer();
innerFn();  
// >> "world"

匿名クラス

名前のないクラスのインスタンスを生成するための特殊な構文。

普通、Javaのクラスは名前とともに宣言され、宣言された名前とnewキーワードを使ってインスタンス化が行われる。

インスタンス化
MyClass obj = new MyClass();

匿名クラスは宣言と同時にインスタンスが生成される。

また、匿名クラスはインターフェースに使用した場合にはインターフェースを「実装(implements)」したクラスになり、通常のクラスに使用した場合にはそのクラスをスーパークラスとして「継承(extends)」したサブクラスになる。

インターフェースに対して使用する匿名クラス

インターフェースに対して使用する例
public interface MyInterface {
    void method();
}

public class Main {
    public static void main(String[] args) {
        MyInterface obj = new MyInterface() {
            // ここが MyInterface を「実装」するクラス
            @Override
            public void method() {
                System.out.println("ここでインターフェースを実装する");
            }
        };
    }
}

スレッドの生成時にも利用される。

// java.lang.Thread、java.lang.Runnable を使用

// 匿名クラスを使ってRunnableインターフェースを実装する
var thread = new Thread(new Runnable(){
    @Override
    public void run() {
        System.out.println("非同期的に処理を実行します。");
    }
});

thread.start();
// >> 非同期的に処理を実行します。

クラスに対して使用する匿名クラス

クラスに対して使用する例
class SuperClass {}

public class Main {
    public static void main(String[] args) {
         SuperClass obj = new SuperClass() {
            // ここが SuperClass を「継承」するクラス
            public void method() {
                System.out.println("ここでスーパークラスを継承する");
            }
        };
    }
}

匿名クラスは、クラス名やコンストラクタは定義する事ができないため、初期化を行いたい場合インスタンス初期化子を使用する。

List<String> list = new ArrayList<String>(){
    // ここが匿名クラス部分
    {
        // ここが匿名クラスのインスタンス初期化子部分
        add("Apple");
        add("Orange");
        add("Melon");
    }
};

Enum

JavaのEnumはクラス。

定義したEnumはjava.lang.Enumクラスのサブクラスとして扱われる。

定義した列挙子は、それぞれが列挙型から生成されたインスタンスとして扱われる。

public enum Fruits {
    Apple, // Fruits型インスタンス
    Banana, // Fruits型インスタンス
    Orange, // Fruits型インスタンス
}

コンストラクタやフィールドを定義する事ができる。

public enum Fruits {
    Apple(1), // Fruits型インスタンス
    Banana(2), // Fruits型インスタンス
    Orange(3); // Fruits型インスタンス
    
    private int id;
    
    Fruits(int id){
        this.id = id;
    } 
}

例外

Error.png

モジュールシステム

複数のパッケージをまとめる仕組みをモジュールシステムという。

モジュールの定義はmodule-info.javaに記述する。

module-info.java
module モジュール名 {
    exports 公開するパッケージ名;
    requires このモジュールが依存する別のモジュール名;
}

このmodule-info.javaを配置したフォルダがモジュールとして扱われる。

パッケージ内ではクラス単位でアクセス制御を行うのに対して、モジュールシステムではパッケージ単位でアクセス制御を行うことができる。

モジュール名はパッケージと同様に一意であることが必要。そのため他のモジュール名と被らない名前にする必要がある。

通常は、モジュールに含めるパッケージ名に共通の文字列があれば、その部分をモジュール名として使用する。共通部分がない場合は、モジュールの目的を表現する名前にする事が望ましい。

ちなみにモジュール名とパッケージ名は重複しても良い。

ただし、ドット「.」で区切ったパッケージがフォルダ階層にマッピングされるのに対して、モジュール名にドット「.」を含めてもフォルダ階層にマッピングされない。そのため、モジュールを配置するフォルダ名はキャメルケースなどで命名する、もしくはmodsなどのプロジェクト毎に変わらない名前にする。

また、標準クラスライブラリをモジュールとして提供するjava.baseモジュールについては、requiresを記述しなくても使用することができる。このようなモジュールをプラットフォームモジュールと呼ぶ。

  • モジュール名: a.b.c
  • モジュールに含めるパッケージ
    • x.y.z パッケージ
    • x.y.zz パッケージ

(通常はパッケージの共通部分であるx.yを使用してモジュール名はx.y.myModuleなどとした方が良い。)

project
   |
   |--- src
   |    |
   |    |--- abc  ◀️ モジュールのルートディレクトリ(階層にしない。キャメルケースが適切?)
   |          |
   |          |--- x  ◀️ パッケージのルートディレクトリ
   |          |    |
   |          |    |--- y
   |          |         |
   |          |         |--- z
   |          |         |    |
   |          |         |    |--- Main.java
   |          |         |
   |          |         |--- zz
   |          |              |
   |          |              |--- MyClass.java
   |          |
   |          |--- module-info.java
   |
   |--- mods  ◀️ コンパイルの出力先( ◀️ ◀️ ◀️ コンパイル後のモジュールのルートディレクトリ)
          |
          |--- abc  ◀️ コンパイルしたモジュール用のフォルダ

コンパイル

コンパイル時には-dオプションで出力先を指定することができる。

コンパイル時の構文
javac \
-d コンパイルしたクラスファイルの格納先 コンパイル対象のソースファイル
コンパイル
javac \
-d mods/abc \
src/abc/module-info.java  \
src/abc/x/y/z/Main.java  \
src/abc/x/y/zz/MyClass.java

実行

コンパイルしたクラスファイルを指定して実行する。

モジュールを実行したい場合、--module-path-p)オプションでモジュールのルートディレクトリを指定し、-module-m)オプションで実行したいモジュールのクラスを指定する。

モジュール名とパッケージはスラッシュ「/」で区切る。

モジュール実行時の構文
java \
--module-path コンパイル後のモジュールのルートディレクトリ \
-module モジュール名/FQCN(エントリーポイント)
モジュールの実行
java \
--module-path mods \
-module a.b.c/x.y.z.Main

JARファイル化

モジュールをJARファイルにまとめたいときには以下のようにする。

  • --create: 新しくJARファイルを作成する
  • --file-name-f): JARファイルの名前
  • --main-class: エントリポイントのFQCN
  • -C: JARファイル化するフォルダ(フォルダ名をドット「.」にすることでフォルダ内の全てのファイルを対象にする)
JARファイル化の構文
jar --create \
--file-name JARファイル名 \
--main-class エントリポイント \
-C JARファイルにするフォルダパス
JARファイル化
jar --create \
--file-name jars/a.b.c.jar \
--main-class x.y.z.Main \
-C mods/abc .

JMODファイル化

JMODファイルは、モジュールをバンドル化して配布するための形式の1つ。

バンドル化とは、ソフトウェアの複数の要素(例えば、クラスファイル、リソースファイル、ライブラリなど)を一つのアーカイブファイルにまとめることを指す。

JavaのモジュールシステムにおいてJMOD形式のファイルはこのバンドル化の一種であり、モジュールに関連するすべてのファイルを一つの単位として扱いやすくする。

モジュールをJMOD形式化する構文
jmod create \
--class-path クラスパス \
出力JMODファイル名 モジュール名
モジュールをJMOD形式化する
jmod create \
--class-path mods/abc \
abc.jmod a.b.c

クラスパスについては以下をにまとめています。

情報を確認する1

モジュールが公開するパッケージや依存するモジュールを調べる方法。

モジュールの情報を確認する構文1
java \
--module-path コンパイル後のモジュールのルートディレクトリ \
--describe-module モジュール名
モジュールの情報を確認する1
java \
-module-path mods \
--describe-module a.b.c

情報を確認する2

対象のモジュールがJMOD形式の時の使用できる、モジュールが公開するパッケージや依存するモジュールを調べる方法。

モジュールの情報を確認する構文2
jmod describe モジュール名
モジュールの情報を確認する2
jmod describe a.b.c

情報を確認する3(依存関係のみ調べる)

jdepsコマンドを使うと、モジュールに限らずJARファイルやクラス同士の依存関係を調べることができる。

JARの依存関係を調べる
jdeps JARファイル名
モジュールの依存関係を調べる構文
jdeps --module-path コンパイル後のモジュールのルートディレクトリ -module モジュール名
モジュールの依存関係を調べる構文
jdeps --module-path mods -module a.b.c

モジュールグラフ

モジュール間の依存関係を表現した図をモジュールグラフと言う。

moduleAmoduleB に依存し、moduleBmoduleC に依存している場合、モジュールグラフは以下のようになる。

module.png

この状況で moduleBmoduleC への依存を moduleA に伝播させることができる。

モジュールの依存を伝播させるためにはtransitiveを使用する。(transitive=推移的)

module-info.java(moduleB)
module moduleB {
    requires transitive moduleC;
}

これによりモジュールAはrequires moduleCを記述しなくても直接モジュールCが使用できるようになる。

この時、moduleAmoduleC には推移的な依存関係があると表現される。

module_2.png

試験対策としての早見表

文法的な暗記系知識。

実行

起動パラメータにスペースを含めたい場合、ダブルコーテーション「"」で囲う

起動パラメータにスペースを含めたい場合、ダブルコーテーション「"」で囲う
java Main.java "hello world"

起動パラメータにダブルコーテーション「"」を使いたい場合、エスケープする

LinuxOSの場合はバックスラッシュ「\」、Windowsの場合は「¥」を使用する

起動パラメータにダブルコーテーション「"」を使いたい場合、エスケープする
java Main.java \"

上の例では「"」がパラメータとして渡される。

エスケープされないダブルコーテーション「"」は無視される

エスケープされないダブルコーテーション「"」は無視される
java Main.java a"bc"

上の例では「abc」がパラメータとして渡される。

アクセス修飾子

protectedとデフォルトの違いに注意して覚える

修飾子 可視性
private - 同じクラス内からのみ
なし(デフォルト) ~ 同じパッケージ内からのみ
protected # 同じパッケージ内 + 継承関係にあるサブクラス からのみ
public + 全て

for文

初期化文と更新文はコンマ(,)で区切って複数記述することができる

初期化文と更新文はコンマ(,)で区切って複数記述することができる
for (int i = 0, j = 0; i < 10; i++, j--) {
    // 処理 
}

ただし初期化では同じ型しか記述できない

コンパイルエラー
// コンパイルエラー
for (int i = 0, long j = 0; i < 10; i++, j--) {
    // 処理 
}

条件式と更新式は省略できる

条件式を省略した場合
for (int i = 0; ; i++) {
    // 処理
}
更新式を省略した場合
for (int i = 0; i < 10; ) {
    // 処理
}

breakで抜けるのは直近のループだけ

breakで抜けるのは直近のループだけ
 for (int i = 0; i < 10; i++) {
    for (int j = 0; j < 10; j++) {
        if(条件){
           braek; 
        }
    }
    // ここに抜ける
}
// ここには抜けない

switch文

switch文の条件式にはboolean型やlong型、float型、double型を使用することができない。
参照型(String型と列挙型を除く)についても使用することができない。

fianlではない変数はcaseに使用できない

fianlではない変数はcaseに使用できない
int target = 100;
int a = 10;
final int B = 10;

switch (target) {
    case B:
        // 処理
        break;
    case a: // コンパイルエラー
        // 処理
        break;
}

配列

要素数は必ず右辺に書く

配列宣言時に左辺の型として使用する配列型(int[]や int[][]など)は配列オブジェクトへの参照を保持するためのものであり、配列の要素数については関与しない。

各要素については配列オブジェクトが管理を行う。

要素数は必ず右辺に書く
int[] array = new int[10];

// コンパイルエラー
int[10] array = new int[];

// コンパイルエラー
int[10] array = new int[10];

[]は変数名の後ろにも置ける

[]は変数名の後ろにも置ける
int[] array = new int[10];

int array2[] = new int[10];

int array3[][] = new int[10][20];

多次元配列は[]をデータ型の後ろと変数名の後ろに分散して置ける

多次元配列は[]をデータ型の後ろと変数名の後ろに分散して置ける
int[] array2[] = new int[10][20];  // 2次元配列

int[][] array3[] = new int[10][20][30]; // 3次元配列

多次元配列の1次元目の要素数は省略できない

多次元配列の1次元目の要素数は省略できない
int[][] array = new int[10][20];

int[][] array2 = new int[10][];

// コンパイルエラー
int[][] array3 = new int[][20];        

多次元配列の2次元目以降は要素数を揃える必要はない

多次元配列の2次元目以降は要素数を揃える必要はない
int[][] array = new int[3][];

array[0] = new int[10];
array[1]= new int[20];
array[2] = new int[30];

配列の初期子{}を使用する場合、要素数は指定できない

配列の初期子{}を使用する場合、要素数は指定できない
int[] array = {1, 2, 3, 4, 5};

int[] array2 = new int[]{1, 2, 3, 4, 5};

// コンパイルエラー
int[] array3 = new int[5]{ 1, 2, 3, 4, 5};

明示的に初期化しなかった配列の各要素は、型のデフォルト値になる

明示的に初期化しなかった配列の各要素は、型のデフォルト値になる
int[] a = new int[1];
System.out.println(a[0]);
// >> 0

double[] b = new double[1];
System.out.println(b[0]);
// >> 0.0

boolean[] c = new boolean[1];
System.out.println(c[0]);
// >> false

char[] d = new char[1];
System.out.println(d[0]); // \u0000
System.out.println((int)d[0]);
// >> �
// >> 0

String[] e = new String[1];
System.out.println(e[0]);
// >> null

Object[] f = new Object[1];
System.out.println(f[0]);
// >> null

コンストラクタ

コンストラクタに見えるメソッド

コンストラクタに見えるメソッド
class MyClass {
    // 戻り値を指定しているためコンストラクタ扱いにならず、メソッド扱いになる
    void MyClass(){
        System.out.println("hello world");
    }
}

public class Main {
    public static void main(String[] args) {
        MyClass a = new MyClass();
        a.MyClass();
        // >> hello world
    }
}

自身のコンストラクタはthis()で呼び出せる

自身のコンストラクタはthis()で呼び出せる
class MyClass {
    MyClass() {
        this(10);
    }

    MyClass(int a) {
    
    }
}

自身のコンストラクタを呼び出す場合、thisより前に処理は実行できない

自身のコンストラクタはthisで呼び出せる
class MyClass {
    MyClass() {
        // thisは最初に実行しなければならない
        System.out.println("hello world");
        // コンパイルエラー
        this(10);
    }

    MyClass(int a) {
    
    }
}

インターフェース

finalかつstaticなフィールドなら定義できる

finalかつstaticなフィールドなら定義できる
interface MyInterface {
    public static final int A_MAX = 10;
}

以下のように記述することも可能。

interface MuInterface {
    // 内部的には public static final int A_MAX = 10; として扱われる
    int A_MAX = 10;
}

staticなメソッドも定義できる

staticなメソッドも定義できる
interface MyInterface {
    public static void myMethod(){
        
    };
}

privateなメソッドも定義できる

privateなメソッドも定義できる
public interface MyInterface {
    private void method() {
        System.out.println("hello world");
    }
}

複数のdefaultメソッドの共通処理をまとめる事ができる。

Objectクラスのメソッドをオーバーライドできない

インターフェースは通常、中身のない抽象メソッドのみが定義可能であり、中身のあるメソッドを定義する場合defaultを使用する必要がある。

interface MyInterface {
    public void myMethodA();

    default public void myMethodB() {
        System.out.println("hello world");
    }

    // コンパイルエラー
    public void myMethodC() {
        System.out.println("hello world");
    }
}

このdefaultを使用してメソッドの定義をするとき、java.lang.Objectのメソッド(equals(), hashCode(), toString())をオーバーライドすることはできない。

Objectクラスのメソッドをオーバーライドできない
interface MyInterface {
    // コンパイルエラー
    @Override
    default public String toString() {
        return "hello world";
    }
}

インターフェースのdefaultメソッドを呼び出す

次の構文でインターフェースのdefaultメソッドを実装クラスから呼び出す事ができる。

インターフェース名.super.メソッド名()
スーパークラスのdefaultメソッドを呼び出す
interface MyInterface {
    default public void a() {
        System.out.println("hello world");
    }
}

class ConcreteClass implements MyInterface {
    @Override
    public void a() {
        // インターフェース名.super.メソッド名()
        MyInterface.super.a();
        System.out.println("hello world");
    }
}

ただし、直接実装(implements)していないスーパークラスのdefaultメソッドは呼び出せない(孫から祖父母のメソッドは呼べない)。

直接実装(implements)していないスーパークラスのdefaultメソッドは呼び出せない
interface SuperInterface {
    default public void a() {
        System.out.println("hello world");
    }
}

interface SubInterface extends SuperInterface {   
}

class ConcreteClass implements SubInterface {
    @Override
    public void a() {
        // コンパイルエラー
        SuperInterface.super.a();
        System.out.println("hello world");
    }
}

「もしインターフェース名を指定しなくても良い」とすると、多重に実装(implements)したインターフェースに同じ名前のdefaultメソッドが定義されていたらどちらのメソッドかが判別できなくなってしまう。

もしインターフェース名を指定しなくてもよかったら
interface InterfaceA {
    default public void hello() {
        System.out.println("hello world");
    }
}

interface InterfaceB {
    default public void hello() {
        System.out.println("hello world");
    }
}

class ConcreteClass implements InterfaceA, InterfaceB {
    @Override
    public void hello() {
        // コンパイルエラー(どっちのhello()?)
        super.hello();
        System.out.println("hello world");
    }
}

defaultメソッド > 通常メソッド

インタフェースの本来の目的は「型を提供すること」であり、defaultメソッドはあくまでもオプション的な位置付けであるため、同じ名前のメソッドがインターフェースとスーパークラス両方で定義されている場合、スーパークラスのメソッドが優先される。

public interface MyInterface {
    default void method() {
        System.out.println("インターフェース");
    }
}
public class SuperClass {
    public void method() {
        System.out.println("スーパークラス");
    }
}
public class MyClass extends SuperClass implements MyInterface {}
MyClass obj1 = new MyClass();
obj1.method();
// >> スーパークラス

SuperClass obj2 = new MyClass();
obj2.method();
// >> スーパークラス

MyInterface obj3 = new MyClass();
obj3.method();
// >> スーパークラス

継承

サブクラスではスーパークラスのフィールド名と同じフィールド名を使える

サブクラスではスーパークラスのフィールド名と同じフィールド名を使える。(オーバーライドではなく、実際には新しいフィールドが追加される)

ただし、実行時どちらにアクセスされるかは変数の宣言型に依存する。スーパークラス型で宣言した変数を通してアクセスを行った場合にはスーパークラスで宣言したフィールドが使用される。

同じ名前のフィールドが存在する場合
class SuperClass {
    String a = "super";
}

class SubClass extends SuperClass {
    String a = "sub";
}
実行時のアクセスは変数の宣言型に依存する
SuperClass obj1 = new SubClass();
System.out.println(obj1.a);
// >> super

SubClass obj2 = new SubClass();
System.out.println(obj2.a);
// >> sub

メソッドからのアクセスの場合、以下のようになる。

メソッドを通じたアクセス ~パターン1~
class SuperClass {
    String a = "super";

    public void b() {
        System.out.println(a);
    }
}

class SubClass extends SuperClass {
    String a = "sub";
}
メソッドの所有者のフィールドが使用される ~パターン1~
SuperClass obj1 = new SubClass();
obj1.b();
// >> super

SubClass obj2 = new SubClass();
obj2.b();
// >> super
メソッドを通じたアクセス ~パターン2~
class SuperClass {
    String a = "super";

    public void b() {
        System.out.println(a);
    }
}

class SubClass extends SuperClass {
    String a = "sub";

    // オーバーライドしている
    @Override 
    public void b() {
        System.out.println(a);
    }
}
メソッドの所有者のフィールドが使用される ~パターン2~
SuperClass obj1 = new SubClass();
obj1.b(); // オーバーライドしているためSubClassのメソッドが使用される
// >> sub

SubClass obj2 = new SubClass();
obj2.b();
// >> sub

スーパークラスのコンストラクタはコンパイル時に自動で追加される

スーパークラスのコンストラクタはコンパイル時に自動で追加される
class SuperClass {
    public SuperClass() {
        System.out.println("super class constructor");
    }
}

class SubClass extends SuperClass {
    public SubClass() {
        // コンパイル時にここに super(); が自動挿入される
        System.out.println("sub class constructor");
    }
}
SuperClass obj = new SubClass();
// >> super class constructor
// >> sub class constructor

継承させたくないクラスはfinalで修飾する

継承させたくないクラスは final で修飾する
final class myClass {
    
} 

Objectequals()

Objectequals()はオーバーライドして同値性を検証させるものだが、オーバーライドをしなかった場合には同一性==と同じ)を検証するようになっている。

staticメソッドはオーバーライドできない

staticメソッドはオーバーライドできない
public class SuperClass {
    static void method() {
    }
}
public class SubClass extends SuperClass {
    // コンパイルエラー
    @Override
    static void method() {
    }
}

ただし@Overrideアノテーションをつけなかった場合、スーパークラスのstaticメソッドを隠蔽したことになり、コンパイルエラーが起きない。

public class SubClass extends SuperClass {
    // 親クラスのメソッドを隠蔽
    static void method() {
    }
}

ラムダ式

基本事項はこちら。

ラムダ式内で利用するローカル変数は実質的にfinalでなければならない

ラムダ式は宣言場所と同じスコープを持つため、ラムダ式内でラムダ式を宣言したスコープからアクセス可能なローカル変数にアクセスすることができる。

一方で、ラムダ式が実行されるのは宣言場所と別のタイミングになるため、ラムダ式内でローカル変数へのアクセスを行うと、そのローカル変数は実質的にfinal(effectively final)でなければならなくなる。

ラムダ式内で利用するローカル変数は実質的にfinalでなければならない
String a = "hello";

// ラムダ式内で a を参照
Runnable runnable = () -> System.out.println(a);

// aに再代入しようとするとコンパイルエラーが起きる
a = "Goodbye";

runnable.run();

コレクション

Arrays.asList()が返すListは固定サイズ

配列をListに変換することができるArrays.asList()が返すListは、要素数が固定の特殊なListになっている。

通常のListと同じだと勘違いして要素を追加しようとすると、実行時例外が発生する。

Arrays.asList() が返す List は固定サイズ
List<String> list = Arrays.asList(new String[]{"aaa", "bbb", "ccc"});
list.add("ddd"); // 実行時例外が発生する

要素数が固定のListは明示的に生成することも可能。

List<String> list = new ArrayList<>();
list.add("aaa");
list.add("bbb");
list.add("ccc");

// 要素数が固定のListに変換
List<String> list2 = Collections.unmodifiableList(list);

list2.add("ddd"); // 実行時例外が発生する

動的サイズのArrayListを初期化したい場合、ArrayListのコンストラクタに固定サイズのListを渡す。

List<String> list = new ArrayList<>(
        Arrays.asList(new String[]{"aaa", "bbb", "ccc"})
);
list.add("ddd");

API

累乗はpow、平方根はsqrt

pow : power(累乗)

power(累乗)
// x の y 乗
Math.pow(x, y)

sqrt: square root(平方根)

square root(平方根)
// x の平方根
Math.sqrt(x)

try-catch / try-with-resources

catchfinallyブロックでどちらも戻り値をreturnしたときはfinallyの戻り値が優先される

catchとfinallyブロックでどちらも戻り値をreturnしたときはfinallyの戻り値が優先される
String a() {
    try {
        throw new Exception();
    } catch (Exception e) {
        return "catch";
    } finally {
        return "finally";
    }
}

System.out.println(a());
// >> finally

tryブロックで宣言した変数はfinallyブロックからアクセスできない

try ブロックで宣言した変数は finally ブロックからアクセスできない
try (FileInputStream is = new FileInputStream("sample.txt")) {
            
} catch (Exception e) {
    
} finally {
    // コンパイルエラー
    if (is != null) {
    }
}

リソースのクローズ時の実行順

リソース
class MyResource implements AutoCloseable {
    @Override
    public void close() {
        System.out.println("①close()が実行される");
    }
}
リソースのクローズ時の実行順
try (MyResource r = new MyResource()) {
    throw new Exception();
} catch (Exception e) {
    System.out.println("②catchブロックが実行される");
} finally {
    System.out.println("③finallyブロックが実行される");
}

// >> ①close()が実行される
// >> ②catchブロックが実行される
// >> ③finallyブロックが実行される
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0