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