こんにちは!今日のレッスンでは、引き続きジェネリックについて学習していきます。これはたまたま大きなトピックですが、どこにも行き場がありません。これは言語の非常に重要な部分です :) ジェネリックスに関する Oracle のドキュメントを調べたり、インターネット上のガイドを読んだりすると、次の用語に遭遇するでしょう。非具体化可能タイプと具体化可能タイプ。「具現化」とはどんな言葉でしょうか?たとえ英語がすべてうまくいったとしても、あなたはそれを達成することはできないでしょう。翻訳してみましょう!
*Google に感謝します。とても助かりました -_-*
refiable-type は、実行時に情報が完全に利用できる型です。Java 言語では、これらにはプリミティブ、生の型、および非ジェネリック型が含まれます。対照的に、非具体化可能型は、実行時に情報が消去され利用できなくなる型です。これらは単なるジェネリックです - List<String>、List<Integer>など。
ところで、可変引数とは何か覚えていますか?
忘れた方のために付け加えておきますが、これらは可変長の引数です。これらは、メソッドに渡せる引数の数が正確にわからない状況で便利です。たとえば、電卓クラスがあり、それにメソッドがあるとしますsum
。メソッドには、sum()
2 つの数値、3 つ、5 つ、または必要な数を渡すことができます。sum()
考えられるすべてのオプションを考慮するために毎回メソッドをオーバーロードするのは非常に奇妙です。代わりに、次のようにすることができます。
public class SimpleCalculator {
public static int sum(int...numbers) {
int result = 0;
for(int i : numbers) {
result += i;
}
return result;
}
public static void main(String[] args) {
System.out.println(sum(1,2,3,4,5));
System.out.println(sum(2,9));
}
}
コンソール出力:
15
11
したがって、varargs
ジェネリックと組み合わせて使用すると、いくつかの重要な機能があります。このコードを見てみましょう:
import javafx.util.Pair;
import java.util.ArrayList;
import java.util.List;
public class Main {
public static <E> void addAll(List<E> list, E... array) {
for (E element : array) {
list.add(element);
}
}
public static void main(String[] args) {
addAll(new ArrayList<String>(), // здесь все нормально
"Leonardo da Vinci",
"Vasco de Gama"
);
// а здесь мы получаем предупреждение
addAll(new ArrayList<Pair<String, String>>(),
new Pair<String, String>("Leonardo", "da Vinci"),
new Pair<String, String>("Vasco", "de Gama")
);
}
}
このメソッドはリストと任意の数のオブジェクトをaddAll()
入力として受け取り、これらすべてのオブジェクトをリストに追加します。このメソッドでは、メソッドを 2 回呼び出します。初めて、2 つの通常の行を追加します。ここではすべてが順調です。2 回目は2 つのオブジェクトを追加します。そしてここで突然警告が表示されます。 List<E>
E
main()
addAll()
List
List
Pair<String, String>
Unchecked generics array creation for varargs parameter
それはどういう意味ですか?警告が表示される理由とそれとの関係は何ですかarray
? Array
- これは配列ですが、コードには配列がありません。2番目から始めましょう。コンパイラが可変長引数 (varargs) を配列に変換するため、警告では配列について言及しています。言い換えれば、私たちのメソッドのシグネチャは次のとおりですaddAll()
。
public static <E> void addAll(List<E> list, E... array)
実際には次のようになります。
public static <E> void addAll(List<E> list, E[] array)
つまり、メソッド内でmain()
、コンパイラはコードを次のように変換します。
public static void main(String[] args) {
addAll(new ArrayList<String>(),
new String[] {
"Leonardo da Vinci",
"Vasco de Gama"
}
);
addAll(new ArrayList<Pair<String,String>>(),
new Pair<String,String>[] {
new Pair<String,String>("Leonardo","da Vinci"),
new Pair<String,String>("Vasco","de Gama")
}
);
}
配列に関してはすべて問題ありませんString
。しかし、配列の場合Pair<String, String>
はダメです。実際のところ、Pair<String, String>
これは非具象化可能なタイプです。コンパイル中に、パラメーターの型 (<String, String>) に関するすべての情報が消去されます。 Java では、非具体化可能型から配列を作成することは許可されていません。これは、配列ペア<String, String> を手動で作成しようとすると確認できます。
public static void main(String[] args) {
// ошибка компиляции! Generic array creation
Pair<String, String>[] array = new Pair<String, String>[10];
}
理由は明らかです。タイプ セーフティです。覚えているとおり、配列を作成するときは、この配列にどのオブジェクト (またはプリミティブ) を格納するかを指定する必要があります。
int array[] = new int[10];
前回のレッスンの 1 つで、活字消去メカニズムを詳しく調べました。したがって、この場合、型を消去した結果、オブジェクトにPair
ペアが格納されていたという情報が失われます<String, String>
。配列の作成は安全ではありません。および ジェネリックスを含むメソッドを使用するときはvarargs
、型の消去とそれがどのように機能するかを必ず覚えておいてください。自分の書いたコードに絶対の自信があり、問題が発生しないことがわかっている場合は、varargs
アノテーションを使用してコードに関連付けられた警告を無効にすることができます。@SafeVarargs
@SafeVarargs
public static <E> void addAll(List<E> list, E... array) {
for (E element : array) {
list.add(element);
}
}
このアノテーションをメソッドに追加すると、前に発生した警告は表示されなくなります。ジェネリックを一緒に使用する場合に考えられるもう 1 つの問題は、varargs
ヒープ汚染です。 汚染は次の状況で発生する可能性があります。
import java.util.ArrayList;
import java.util.List;
public class Main {
static List<String> makeHeapPollution() {
List numbers = new ArrayList<Number>();
numbers.add(1);
List<String> strings = numbers;
strings.add("");
return strings;
}
public static void main(String[] args) {
List<String> stringsWithHeapPollution = makeHeapPollution();
System.out.println(stringsWithHeapPollution.get(0));
}
}
コンソール出力:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
簡単に言うと、ヒープ汚染とは、タイプ 1 のオブジェクトがヒープ上にあるはずなのに、タイプ セーフティ エラーにより、А
タイプ 1 のオブジェクトがヒープ上に存在してしまう状況です。B
この例では、これが起こります。まず、Raw 変数を作成しnumbers
、それに汎用コレクションを割り当てましたArrayList<Number>
。その後、そこに数字を追加しました1
。
List<String> strings = numbers;
この行では、コンパイラは「未チェックの割り当て...」という警告を発行して、エラーの可能性について警告しようとしましたが、無視しました。結果として、 type のジェネリックList<String>
コレクションを指す type のジェネリック変数が得られますArrayList<Number>
。この状況は明らかにトラブルにつながる可能性があります。これが起こるのです。新しい変数を使用して、文字列をコレクションに追加します。ヒープが汚染されました。型付きコレクションに最初に数値を追加し、次に文字列を追加しました。コンパイラは警告を出しましたが、私たちはその警告を無視し、ClassCastException
プログラムの実行中にのみ結果を受け取りました。それと何の関係があるのですかvarargs
? ジェネリックと一緒に使用するvarargs
と、ヒープ汚染が簡単に発生する可能性があります。 簡単な例を次に示します。
import java.util.Arrays;
import java.util.List;
public class Main {
static void makeHeapPollution(List<String>... stringsLists) {
Object[] array = stringsLists;
List<Integer> numbersList = Arrays.asList(66,22,44,12);
array[0] = numbersList;
String str = stringsLists[0].get(0);
}
public static void main(String[] args) {
List<String> cars1 = Arrays.asList("Ford", "Fiat", "Kia");
List<String> cars2 = Arrays.asList("Ferrari", "Bugatti", "Zaporozhets");
makeHeapPollution(cars1, cars2);
}
}
何が起きてる?型の消去により、パラメーター シート (便宜上、「リスト」ではなく「シート」と呼びます) は次のようになります。
List<String>...stringsLists
- 未知の型のシートの配列に変わりますList[]
(コンパイルの結果、可変引数は通常の配列に変わることを忘れないでください)。このため、Object[] array
メソッドの最初の行で変数への代入を簡単に行うことができます。型はシートから消去されています。これで、 型の変数ができましたObject[]
。ここに何でも追加できます。Java のすべてのオブジェクトは から継承していますObject
。現時点では文字列シートの配列のみがあります。しかし、型の使用varargs
と消去のおかげで、それらに数字のシートを簡単に追加することができ、それが私たちの仕事です。その結果、異なる種類のオブジェクトが混在してヒープが汚染されます。ClassCastException
配列から文字列を読み取ろうとした場合も、結果は同じ例外になります。コンソール出力:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
これらは、一見単純なメカニズムを使用することで生じる可能性のある予期せぬ結果ですvarargs
:) そして、これで今日の講義は終わります。忘れずにいくつかの問題を解決し、時間とエネルギーが残っている場合は追加の文献を調べてください。「Effective Java」自体は読み込まれません。:) またね!
GO TO FULL VERSION