やあ!ジェネリック医薬品に関する一連のレッスンを続けます。それらが何であるか、そしてなぜそれらが必要なのかについては、以前に概要を理解しました。今日は、ジェネリックのいくつかの機能とその使用方法について詳しく学びます。さあ行こう! 前回のレッスン
では、ジェネリック型と生の型の違いについて説明しました。raw 型は、型が削除されたジェネリック クラスです。
ドキュメントには、「T - この Class オブジェクトによってモデル化されたクラスの型」と記載されています。これをドキュメントの言語から普通の音声に翻訳すると、オブジェクトのクラスが単なる ではなく であることがわかり

List list = new ArrayList();
ここに一例を示します。ここでは、どのタイプのオブジェクトが に配置されるかについては示しませんList
。List
このようなものを作成してそれにオブジェクトを追加しようと すると、IDEA に警告が表示されます。
"Unchecked call to add(E) as a member of raw type of java.util.List".
しかし、ジェネリックが Java 5 でのみ登場したという事実についても話しました。このバージョンがリリースされるまでに、プログラマはすでに raw 型を使用して大量のコードを作成していたため、この言語の機能が機能しなくなることはありませんでした。 Java での raw タイプの作成は保持されました。しかし、問題はさらに広範囲に及ぶことが判明しました。ご存知のとおり、Java コードはバイトコードと呼ばれる特別にコンパイルされた形式に変換され、Java 仮想マシンによって実行されます。しかし、変換プロセス中に型パラメータに関する情報をバイトコードに含めると、以前に記述されたコードがすべて壊れてしまいます。Java 5 より前には型パラメータが存在しなかったためです。ジェネリックを使用する場合、覚えておく必要がある非常に重要な概念が 1 つあります。型消去といいます。これは、クラスに型パラメーターに関する情報が含まれていないことを意味します。この情報はコンパイル中にのみ利用可能であり、実行前に消去されます (アクセスできなくなります)。間違ったタイプのオブジェクトを に入れようとするとList<String>
、コンパイラはエラーを生成します。これはまさに、言語の作成者がジェネリックスを作成したときに達成したいこと、つまりコンパイル時のチェックです。しかし、すべての Java コードがバイトコードに変わると、型パラメーターに関する情報は含まれなくなります。バイトコードでは、List<Cat>
cats リストは文字列と何ら変わりませんList<String>
。バイトコードでは、それがオブジェクトcats
のリストであるとは言えませんCat
。このような情報はコンパイル中に消去されます。リストがあるという事実のみがList<Object> cats
プログラムのバイトコードに残ります。これがどのように機能するかを見てみましょう:
public class TestClass<T> {
private T value1;
private T value2;
public void printValues() {
System.out.println(value1);
System.out.println(value2);
}
public static <T> TestClass<T> createAndAdd2Values(Object o1, Object o2) {
TestClass<T> result = new TestClass<>();
result.value1 = (T) o1;
result.value2 = (T) o2;
return result;
}
public static void main(String[] args) {
Double d = 22.111;
String s = "Test String";
TestClass<Integer> test = createAndAdd2Values(d, s);
test.printValues();
}
}
独自のジェネリックTestClass
クラスを作成しました。これは非常に単純です。実際には 2 つのオブジェクトの小さな「コレクション」であり、オブジェクトが作成されるとすぐに保存されます。2つのフィールドがありますT
。メソッドが実行されるとcreateAndAdd2Values()
、渡された 2 つのオブジェクト (Object a
および) を型にキャストしてからオブジェクトに追加する必要Object b
があります。メソッドでは を作成します。つまり、型引数が型パラメータを置き換えます。また、 aと aを に渡します。メソッドです。私たちのプログラムは機能すると思いますか? 結局のところ、型引数として指定しましたが、 a をにキャストすることは絶対にできません! を実行しましょう。T
TestClass
main()
TestClass<Integer>
Integer
Integer
Double
String
createAndAdd2Values()
Integer
String
Integer
main()
方法とチェック。コンソール出力:
22.111
Test String
それは予想外でした!なぜこのようなことが起こったのでしょうか? 型消去の結果です。Integer
オブジェクトのインスタンス化に使用された type 引数 に関する情報は、TestClass<Integer> test
コードのコンパイル時に消去されました。フィールドは になりますTestClass<Object> test
。引数Double
とString
引数は簡単にオブジェクトに変換され(期待したようにオブジェクトObject
に変換されません!)、静かに に追加されました。次に、型消去の単純だが非常にわかりやすい例をもう 1 つ示します。 Integer
TestClass
import java.util.ArrayList;
import java.util.List;
public class Main {
private class Cat {
}
public static void main(String[] args) {
List<String> strings = new ArrayList<>();
List<Integer> numbers = new ArrayList<>();
List<Cat> cats = new ArrayList<>();
System.out.println(strings.getClass() == numbers.getClass());
System.out.println(numbers.getClass() == cats.getClass());
}
}
コンソール出力:
true
true
String
3 つの異なる型の引数、 、Integer
、そして独自のクラス を使用してコレクションを作成したようですCat
。しかし、バイトコードへの変換中に 3 つのリストはすべて になるList<Object>
ため、プログラムを実行すると、3 つのケースすべてで同じクラスを使用していることがわかります。
配列およびジェネリックを操作する場合の型消去
配列やジェネリック クラス ( など) を使用する場合、明確に理解しておく必要がある非常に重要な点がありますList
。プログラムのデータ構造を選択するときも、それを考慮する必要があります。ジェネリックは型消去の対象となります。型パラメータに関する情報は実行時には入手できません。対照的に、配列はプログラムの実行時にそのデータ型に関する情報を認識し、使用できます。無効な型を配列に入れようとすると、例外がスローされます。
public class Main2 {
public static void main(String[] args) {
Object x[] = new String[3];
x[0] = new Integer(222);
}
}
コンソール出力:
Exception in thread "main" java.lang.ArrayStoreException: java.lang.Integer
配列とジェネリックには大きな違いがあるため、互換性の問題が発生する可能性があります。何よりも、汎用オブジェクトの配列や、パラメーター化された配列だけを作成することはできません。少しわかりにくいと思いませんか? 見てみましょう。たとえば、Java では次のことは実行できません。
new List<T>[]
new List<String>[]
new T[]
オブジェクトの配列を作成しようとするとList<String>
、汎用配列の作成に関するコンパイル エラーが発生します。
import java.util.List;
public class Main2 {
public static void main(String[] args) {
// Compilation error! Generic array creation
List<String>[] stringLists = new List<String>[1];
}
}
しかし、なぜこれが行われるのでしょうか?このような配列の作成が許可されないのはなぜですか? これはすべて、タイプ セーフティを提供するためです。コンパイラーがこのような汎用オブジェクトの配列を作成できるようにすると、私たち自身で大量の問題を引き起こす可能性があります。以下は、Joshua Bloch の著書「Effective Java」からの簡単な例です。
public static void main(String[] args) {
List<String>[] stringLists = new List<String>[1]; // (1)
List<Integer> intList = Arrays.asList(42, 65, 44); // (2)
Object[] objects = stringLists; // (3)
objects[0] = intList; // (4)
String s = stringLists[0].get(0); // (5)
}
のような配列の作成がList<String>[] stringLists
許可され、コンパイル エラーが生成されないと想像してみましょう。これが本当であれば、次のようなことができます。 1 行目で、リストの配列を作成しますList<String>[] stringLists
。配列には 1 つが含まれていますList<String>
。2 行目では、数値のリストを作成しますList<Integer>
。List<String>[]
3 行目では、変数にour を代入しますObject[] objects
。X
Java 言語では、これが可能です。オブジェクトの配列には、X
オブジェクトとすべてのサブクラスのオブジェクトを格納できますX
。したがって、配列には何でも入れることができますObject
。objects()
4 行目では、配列の唯一の要素 (a List<String>
) を a に置き換えますList<Integer>
。したがって、List<Integer>
格納することだけを目的とした配列に を入れました。List<String>
オブジェクト!行 5 を実行するときのみエラーが発生します。ClassCastException
実行時に A がスローされます。したがって、このような配列の作成に対する禁止事項が Java に追加されました。これにより、そのような状況を回避できます。
タイプの消去を回避するにはどうすればよいですか?
さて、文字の消去について学びました。システムを騙してみよう! :) タスク: ジェネリックTestClass<T>
クラスがあります。createNewT()
新しいオブジェクトを作成して返すこのクラスのメソッドを作成したいと考えていますT
。でもそれは無理ですよね?型に関するすべての情報はT
コンパイル中に消去されるため、実行時にはどの型のオブジェクトを作成する必要があるかを判断できません。実はこれを行うには難しい方法が 1 つあります。Java にはクラスがあることを覚えているでしょうClass
。これを使用して、任意のオブジェクトのクラスを決定できます。
public class Main2 {
public static void main(String[] args) {
Class classInt = Integer.class;
Class classString = String.class;
System.out.println(classInt);
System.out.println(classString);
}
}
コンソール出力:
class java.lang.Integer
class java.lang.String
しかし、ここでまだ話していない側面が 1 つあります。Oracle のドキュメントでは、Class クラスがジェネリックであることがわかります。 
https://docs.oracle.com/javase/8/docs/api/java/lang/Class.html
Integer.class
ます。オブジェクトの型は、単なる ではなく、 などです。それでも明確でない場合は、前の例に型パラメーターを追加してみてください。 Class
Class<Integer>
String.class
Class
Class<String>
public class Main2 {
public static void main(String[] args) {
Class<Integer> classInt = Integer.class;
// Compilation error!
Class<String> classInt2 = Integer.class;
Class<String> classString = String.class;
// Compilation error!
Class<Double> classString2 = String.class;
}
}
そして今、この知識を使用して、型の消去を回避してタスクを完了することができます。 型パラメータに関する情報を取得してみましょう。type 引数は次のようになりますMySecretClass
。
public class MySecretClass {
public MySecretClass() {
System.out.println("A MySecretClass object was created successfully!");
}
}
実際にソリューションを使用する方法は次のとおりです。
public class TestClass<T> {
Class<T> typeParameterClass;
public TestClass(Class<T> typeParameterClass) {
this.typeParameterClass = typeParameterClass;
}
public T createNewT() throws IllegalAccessException, InstantiationException {
T t = typeParameterClass.newInstance();
return t;
}
public static void main(String[] args) throws InstantiationException, IllegalAccessException {
TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
MySecretClass secret = testString.createNewT();
}
}
コンソール出力:
A MySecretClass object was created successfully!
必要なクラス引数をジェネリック クラスのコンストラクターに渡しただけです。
TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
これにより、型引数に関する情報を保存し、完全に消去されるのを防ぐことができました。その結果、作成することができました。T
物体!:) これで今日のレッスンは終了です。ジェネリックスを使用するときは、型の消去を常に覚えておく必要があります。この回避策はあまり便利とは思えませんが、ジェネリックスは Java 言語が作成された時点ではその一部ではなかったことを理解する必要があります。この機能は、パラメーター化されたコレクションを作成し、コンパイル中にエラーを検出するのに役立ちますが、後から追加されました。最初のバージョンのジェネリックスを含む他の言語では、型の消去がありません (C# など)。ところで、ジェネリック医薬品の勉強はこれで終わりではありません。次のレッスンでは、ジェネリックの機能をさらにいくつか学習します。とりあえず、いくつかのタスクを解決すると良いでしょう。:)
GO TO FULL VERSION