こんにちは!ジェネリック医薬品に関する講座を継続していきます。以前は、それが何であるか、そしてなぜそれが必要なのかを一般的な観点から理解しました。今日は、ジェネリックの機能のいくつかについて説明し、ジェネリックを使用する際のいくつかの落とし穴について見ていきます。行く! 前回の講義では、ジェネリック型と生の型の違いについて話しました。忘れた方のために付け加えておきますが、Raw Type は、その型が削除されたジェネリック クラスです。
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 Type の作成と操作は維持されました。しかし、この問題はさらに広範囲にわたることが判明しました。ご存知のとおり、Java コードは特殊なバイトコードに変換され、Java 仮想マシンによって実行されます。また、変換プロセス中にパラメータの型に関する情報をバイトコードに配置すると、以前に記述されたコードがすべて壊れてしまいます。Java 5 より前にはパラメータの型が存在しなかったためです。ジェネリックを使用する場合、覚えておく必要がある非常に重要な機能が 1 つあります。それを型消去といいます。その本質は、パラメータの型に関する情報がクラス内に格納されないという事実にあります。この情報はコンパイル段階でのみ利用可能であり、実行時には消去されます (アクセスできなくなります)。間違った型のオブジェクトを に入れようとするとList<String>
、コンパイラはエラーをスローします。これはまさに、言語の作成者がジェネリックスを作成することによって達成したこと、つまりコンパイル段階でのチェックです。しかし、作成したすべての Java コードがバイトコードになると、パラメーターの型に関する情報がなくなります。バイトコード内では、List<Cat>
猫のリストは文字列と変わりません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
。メソッドが実行されると、渡された 2 つのオブジェクトがtypeにcreateAndAdd2Values()
キャストされ、その後 object に追加されます。私たちが作成する方法では、つまり品質ではが得られます。しかし同時に、数値とオブジェクトをメソッドに渡します。私たちのプログラムはうまくいくと思いますか? 結局のところ、パラメータ type として指定しましたが、確かに!にキャストすることはできません。メソッドを実行して確認してみましょう。コンソール出力: 22.111 テスト文字列 予期しない結果です。なぜこのようなことが起こったのでしょうか? まさに活字消去のせいです。コードのコンパイル中に、オブジェクトの パラメーターの型に関する情報が消去されました。彼は に変わりました。パラメータは問題なく に変換され(期待したように には変換されませんでした)、静かに に追加されました。次に、型消去の単純だが非常にわかりやすい別の例を示します。 Object a
Object b
T
TestClass
main()
TestClass<Integer>
T
Integer
createAndAdd2Values()
Double
String
Integer
String
Integer
main()
Integer
TestClass<Integer> test
TestClass<Object> test
Double
String
Object
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 trueString
3 つの異なるパラメータ タイプ、 、Integer
、および作成したクラス を使用 してコレクションを作成したように見えますCat
。しかし、バイトコードへの変換中に 3 つのリストはすべて に変わったList<Object>
ため、プログラムを実行すると、3 つのケースすべてで同じクラスを使用していることがわかります。
配列およびジェネリックを操作する場合の型消去
配列とジェネリック (たとえば、List
) を使用する場合、明確に理解しておく必要がある非常に重要な点が 1 つあります。プログラムのデータ構造を選択する際にも考慮する価値があります。ジェネリックは型消去の対象となります。パラメータのタイプに関する情報は、プログラムの実行中には取得できません。対照的に、配列はプログラムの実行中にそのデータ型に関する情報を認識し、使用できます。間違った型の値を配列に入れようとすると、例外がスローされます。
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 の配列を作成しようとするとList<String>
、一般的な配列作成コンパイル エラーが発生します。
import java.util.List;
public class Main2 {
public static void main(String[] args) {
//ошибка компиляции! 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 行目で、 Sheets の配列を作成しますList<String>[] stringLists
。配列には 1 つが含まれていますList<String>
。2 行目で数値のリストを作成しますList<Integer>
。3 行目で、配列をList<String>[]
変数に代入しますObject[] objects
。Java 言語を使用すると、オブジェクトとすべての子クラスのオブジェクトのX
両方を オブジェクトの配列 に入れることができます。したがって、配列には何でも入れることができます。4 行目では、配列の 1 つの要素をlist に置き換えます。その結果、配列に!を格納することだけを目的とした配列を配置しました。コードが 5 行目に達した場合にのみエラーが発生します。プログラムの実行中に例外がスローされます。したがって、そのような配列の作成の禁止が Java 言語に導入されました。これにより、そのような状況を回避できるようになります。 X
Х
Objects
objects (List<String>)
List<Integer>
List<Integer>
List<String>
ClassCastException
タイプ消去を回避するにはどうすればよいですか?
さて、文字の消去について学びました。システムを騙してみよう!:) タスク: ジェネリック クラスがありますTestClass<T>
。createNewT()
タイプの新しいオブジェクトを作成して返すメソッドをその中に作成する必要がありますТ
。でもそれは無理ですよね?型に関するすべての情報はТ
コンパイル中に消去され、プログラムの実行中は、作成する必要があるオブジェクトの型を見つけることができなくなります。実は、難しい方法が 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 がジェネリック クラスであることがわかります。 ドキュメントには、「T は、この Class オブジェクトによってモデル化されたクラスの型です。」と記載されています。これをドキュメント言語から人間の言語に翻訳すると、オブジェクトのクラスは単なる ではなく であることを意味しInteger.class
ます。オブジェクトの種類は、、 などだけではありません。それでも不明な場合は、前の例に type パラメーターを追加してみてください。 Class
Class<Integer>
string.class
Class
Class<String>
public class Main2 {
public static void main(String[] args) {
Class<Integer> classInt = Integer.class;
//ошибка компиляции!
Class<String> classInt2 = Integer.class;
Class<String> classString = String.class;
//ошибка компиляции!
Class<Double> classString2 = String.class;
}
}
そして今、この知識を使用して型消去を回避し、問題を解決することができます。パラメータの型に関する情報を取得してみましょう。その役割はクラスによって果たされますMySecretClass
。
public class MySecretClass {
public MySecretClass() {
System.out.println("Объект секретного класса успешно создан!");
}
}
実際にソリューションを使用する方法は次のとおりです。
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();
}
}
コンソール出力:
Объект секретного класса успешно создан!
必要なクラス パラメーターをジェネリック クラスのコンストラクターに渡すだけです。
TestClass<MySecretClass> testString = new TestClass<>(MySecretClass.class);
このおかげで、パラメータのタイプに関する情報が保存され、消去されないように保護されました。その結果、オブジェクトを作成することができましたT
。:) これで今日の講義は終わりです。ジェネリックスを扱うときは、型の消去を常に念頭に置く必要があります。これはあまり便利そうには見えませんが、ジェネリクスは Java 言語が作成された時点ではその一部ではなかったことを理解する必要があります。これは、型付きコレクションを作成し、コンパイル段階でエラーをキャッチするのに役立つ、後から追加された機能です。ジェネリックスがバージョン 1 から存在する他の一部の言語には型消去がありません (C# など)。ただし、ジェネリック医薬品の研究が終わったわけではありません。次の講義では、それらを使用する際のさらにいくつかの機能について学びます。それまでに、いくつかの問題を解決できれば幸いです。:)
GO TO FULL VERSION