Java ジェネリクスの概念

JavaJavaBeginner
今すぐ練習

💡 このチュートリアルは英語版からAIによって翻訳されています。原文を確認するには、 ここをクリックしてください

はじめに

Java におけるジェネリクスは、追加の抽象化レイヤーを提供することで、柔軟で再利用可能なコードを記述できるようにします。複数の型のオブジェクトに対して単一のアルゴリズムを作成できます。ジェネリクスは型安全性を提供し、ランタイムエラーを回避します。この実験では、Java におけるジェネリクスのさまざまな側面を理解します。


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL java(("Java")) -.-> java/BasicSyntaxGroup(["Basic Syntax"]) java(("Java")) -.-> java/DataStructuresGroup(["Data Structures"]) java(("Java")) -.-> java/ProgrammingTechniquesGroup(["Programming Techniques"]) java(("Java")) -.-> java/ObjectOrientedandAdvancedConceptsGroup(["Object-Oriented and Advanced Concepts"]) java/BasicSyntaxGroup -.-> java/output("Output") java/BasicSyntaxGroup -.-> java/type_casting("Type Casting") java/DataStructuresGroup -.-> java/arrays("Arrays") java/ProgrammingTechniquesGroup -.-> java/method_overloading("Method Overloading") java/ObjectOrientedandAdvancedConceptsGroup -.-> java/classes_objects("Classes/Objects") java/ObjectOrientedandAdvancedConceptsGroup -.-> java/arraylist("ArrayList") java/ObjectOrientedandAdvancedConceptsGroup -.-> java/generics("Generics") subgraph Lab Skills java/output -.-> lab-117688{{"Java ジェネリクスの概念"}} java/type_casting -.-> lab-117688{{"Java ジェネリクスの概念"}} java/arrays -.-> lab-117688{{"Java ジェネリクスの概念"}} java/method_overloading -.-> lab-117688{{"Java ジェネリクスの概念"}} java/classes_objects -.-> lab-117688{{"Java ジェネリクスの概念"}} java/arraylist -.-> lab-117688{{"Java ジェネリクスの概念"}} java/generics -.-> lab-117688{{"Java ジェネリクスの概念"}} end

ジェネリッククラスの作成

Java では、ダイヤモンド演算子 (<>) を使ってジェネリッククラスを作成できます。次のコードブロックでは、任意の型のオブジェクトを受け付ける MyGenericClass という名前のジェネリッククラスを作成します。また、整数型と文字列型をパラメータとして持つ MyGenericClass 型のオブジェクトも作成します。

// ~/project/MyGenericClass.java

class MyGenericClass<T> {
    T field1;

    public MyGenericClass(T field1) {
        this.field1 = field1;
    }

    public T getField1() {
        return field1;
    }
}

public class Main {
    public static void main(String[] args) {
        MyGenericClass<Integer> myIntObj = new MyGenericClass<Integer>(100);
        MyGenericClass<String> myStringObj = new MyGenericClass<String>("Hello World");

        System.out.println(myIntObj.getField1());
        System.out.println(myStringObj.getField1());
    }
}

コードを実行するには、ターミナルを開き、~/project ディレクトリに移動します。次のコマンドでコードをコンパイルします。

javac MyGenericClass.java

コンパイルが成功したら、次のコマンドでコードを実行します。

java Main

コードの出力は次のようになります。

100
Hello World

ジェネリックメソッドの作成

Java ではジェネリックメソッドも作成できます。ジェネリックメソッドは異なる型のオブジェクトとも動作します。次のコードブロックでは、任意の型の配列を出力できる printArray という名前のジェネリックメソッドを作成します。

// ~/project/Main.java

public class Main {
    public static <T> void printArray(T[] array) {
        for (T element : array)
            System.out.println(element);
    }

    public static void main(String[] args) {
        Integer[] intArray = {1, 2, 3, 4, 5};
        Double[] doubleArray = {1.1, 2.2, 3.3, 4.4};
        String[] stringArray = {"Hello", "World"};

        printArray(intArray);
        printArray(doubleArray);
        printArray(stringArray);
    }
}

コードを実行するには、ターミナルを開き、~/project ディレクトリに移動します。次のコマンドでコードをコンパイルします。

javac Main.java

コンパイルが成功したら、次のコマンドでコードを実行します。

java Main

コードの出力は次のようになります。

1
2
3
4
5
1.1
2.2
3.3
4.4
Hello
World

境界付きジェネリックメソッドの作成

境界付きジェネリックメソッドも作成できます。境界付きジェネリックは、メソッドが受け付ける型の範囲を制限します。制約を課すには extends キーワードを使用します。次のコードブロックでは、Number 型およびそのサブクラスのオブジェクトの数値を出力できる printNumbers という名前の境界付きジェネリックメソッドを作成します。

// ~/project/Main.java

public class Main {
    public static <T extends Number> void printNumbers(T[] numbers) {
        for (T number : numbers)
            System.out.println(number);
    }

    public static void main(String[] args) {
        Integer[] intNumbers = {1, 2, 3, 4, 5};
        Double[] doubleNumbers = {1.1, 2.2, 3.3, 4.4};

        printNumbers(intNumbers);
        printNumbers(doubleNumbers);
    }
}

コードを実行するには、ターミナルを開き、~/project ディレクトリに移動します。次のコマンドでコードをコンパイルします。

javac Main.java

コンパイルが成功したら、次のコマンドでコードを実行します。

java Main

コードの出力は次のようになります。

1
2
3
4
5
1.1
2.2
3.3
4.4

ジェネリクスにおける型安全性の理解

ジェネリクスは型安全性を提供し、一度に扱うオブジェクトの型を1つだけ定義することを保証します。次のコードブロックでは、型パラメータを定義せずに整数の ArrayList を作成します。型パラメータなしでジェネリクスを使用するのは良いプラクティスではありません。コードはコンパイルされますが、他の型の要素にアクセスしようとするとランタイムエラーが返されます。

// ~/project/Main.java

import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList integerList = new ArrayList();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
        integerList.add("Hello World");

        for (Object element : integerList) {
            System.out.println((Integer)element);
        }
    }
}

コードを実行するには、ターミナルを開き、~/project ディレクトリに移動します。次のコマンドでコードをコンパイルします。

javac Main.java

コンパイルが成功したら、次のコマンドでコードを実行します。

java Main

コードの出力は次のようになります。

1
2
3
Exception in thread "main" java.lang.ClassCastException: java.base/java.lang.String cannot be cast to java.base/java.lang.Integer
    at Main.main(Main.java:13)

integerList には Integer 型と String 型の要素が含まれているため、ランタイムエラーが発生します。これは、integerList を作成する際に型パラメータを定義しなかったためです。

ジェネリクスにおける型消去の理解

Java はジェネリクスにおいて型消去を使用して、実行時に追加のオーバーヘッドが必要ないことを保証します。コンパイラは型消去の過程で、型パラメータを Object クラスまたは親クラス(境界付きジェネリクスの場合)に置き換えます。次のコードブロックでは、ジェネリクスがコンパイル時と実行時にプリミティブ型とどのように動作するかを見てみましょう。

// ~/project/Main.java

import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList<Integer> integerList = new ArrayList<Integer>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);

        int sum = 0;
        for (int i = 0; i < integerList.size(); i++) {
            sum += integerList.get(i);
        }
        System.out.println(sum);
    }
}

コードを実行するには、ターミナルを開き、~/project ディレクトリに移動します。次のコマンドでコードをコンパイルします。

javac Main.java

コンパイルが成功したら、次のコマンドでコードを実行します。

java Main

コードの出力は次のようになります。

6

上記のコードブロックでは、integerList の要素を追加しようとしました。既に知っている通り、int のようなプリミティブ型はジェネリクスと共に使用できません。型消去のため、型パラメータ Integer はコンパイル時に Object クラスに置き換えられます。ただし、オートボクシングとアンボクシングにより、コンパイル時に整数の加算を行うことができました。実行時には、コードは通常のコード実行と同じように動作し、追加のオーバーヘッドなしでした。

まとめ

この実験では、Java のジェネリクスについて学びました。ジェネリッククラス、ジェネリックメソッド、境界付きジェネリックメソッド、ジェネリクスにおける型安全性、およびジェネリクスにおける型消去に関する基本概念を扱いました。また、ジェネリクスを使用して柔軟で再利用可能なコードを書く方法も見ました。この実験があなたの Java のジェネリクスの理解に役立ったことを願っています。