やあ!今日のレッスンでは、引き続きジェネリックについて学習していきます。偶然にも、これは大きなトピックですが、避けることはできません。これは言語の非常に重要な部分です :) ジェネリックスに関する Oracle ドキュメントを研究したり、オンライン チュートリアルを読んだりすると、非具体化可能型と具象化可能なタイプ。具象化可能な型は、実行時に情報が完全に利用できる型です。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
これは、可変引数をジェネリックと組み合わせて使用する場合にいくつかの重要な機能があることを示しています。次のコードを見てみましょう。
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>(), // This is okay
"Leonardo da Vinci",
"Vasco de Gama"
);
// but here we get a warning
addAll(new ArrayList<Pair<String, String>>(),
new Pair<String, String>("Leonardo", "da Vinci"),
new Pair<String, String>("Vasco", "de Gama")
);
}
}
このaddAll()
メソッドは入力としてList<E>
と任意の数のE
オブジェクトを受け取り、これらすべてのオブジェクトをリストに追加します。メソッド内でメソッドを 2 回main()
呼び出しますaddAll()
。最初のケースでは、2 つの通常の文字列を に追加しますList
。ここではすべてが順調です。2 番目のケースでは、2 つのPair<String, String>
オブジェクトを に追加しますList
。しかし、ここで予期せず警告が表示されます。
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 では、非具象化可能型の配列の作成は許可されていません。これは、Pair<String, String> 配列を手動で作成しようとすると確認できます。
public static void main(String[] args) {
// Compilation error Generic array creation
Pair<String, String>[] array = new Pair<String, String>[10];
}
理由は明らかです。タイプ セーフティです。思い出していただけると思いますが、配列を作成するときは、配列に格納するオブジェクト (またはプリミティブ) を必ず指定する必要があります。
int array[] = new int[10];
以前のレッスンの 1 つで、型の消去について詳しく調べました。Pair
この場合、型消去により、オブジェクトがペアを格納しているという情報が失われます<String, String>
。配列の作成は安全ではありません。 可変引数とジェネリックを含むメソッドを使用するときは、型の消去とその仕組みについて必ず覚えておいてください。自分が作成したコードに絶対の確信があり、問題が発生しないことがわかっている場合は、アノテーションを使用して可変引数関連の警告をオフにすることができます。 @SafeVarargs
@SafeVarargs
public static <E> void addAll(List<E> list, E... array) {
for (E element : array) {
list.add(element);
}
}
このアノテーションをメソッドに追加すると、前に発生した警告は表示されなくなります。ジェネリックスで可変長引数を使用するときに発生する可能性のあるもう 1 つの問題は、ヒープ汚染です。 ヒープ汚染は次の状況で発生する可能性があります。
import java.util.ArrayList;
import java.util.List;
public class Main {
static List<String> polluteHeap() {
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 = polluteHeap();
System.out.println(stringsWithHeapPollution.get(0));
}
}
コンソール出力:
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
簡単に言うと、ヒープ汚染とは、型のオブジェクトがA
ヒープ内に存在する必要があるにもかかわらず、B
型の安全性に関連するエラーにより、型のオブジェクトがヒープ内に存在してしまうことです。この例では、まさにこれが起こっています。まず、生の変数を作成し、それにnumbers
汎用コレクション ( ) を割り当てました。ArrayList<Number>
次に、番号を1
コレクションに追加しました。
List<String> strings = numbers;
この行では、コンパイラは「未チェックの割り当て...」という警告を発行してエラーの可能性を警告しようとしましたが、無視されました。List<String>
最終的には、 typeのジェネリック コレクションを指す typeのジェネリック変数になりますArrayList<Number>
。この状況がトラブルにつながる可能性があることは明らかです。そしてその通りです。新しい変数を使用して、文字列をコレクションに追加します。これでヒープ汚染が発生しました。パラメータ化されたコレクションに数値を追加し、次に文字列を追加しました。コンパイラは警告を出しましたが、私たちはその警告を無視しました。その結果、ClassCastException
プログラムの実行中にのみ が得られます。では、これは可変長引数とどのような関係があるのでしょうか? ジェネリックスで可変引数を使用すると、ヒープ汚染が発生しやすくなります。 簡単な例を次に示します。
import java.util.Arrays;
import java.util.List;
public class Main {
static void polluteHeap(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");
polluteHeap(cars1, cars2);
}
}
何が起きてる?型消去のため、可変長引数は
List<String>...stringsLists
はリストの配列、つまりList[]
未知の型のオブジェクトの配列になります (コンパイル中に varargs が通常の配列に変わることを忘れないでください)。Object[] array
このため、メソッドの最初の行の変数に簡単に割り当てることができます。リスト内のオブジェクトの型は消去されています。Java のすべてのオブジェクトは!Object[]
を継承するため、変数を追加できます。Object
最初は、文字列のリストの配列しかありません。しかし、型の消去と可変引数の使用のおかげで、数値のリストを簡単に追加できます。その結果、異なる種類のオブジェクトが混在してヒープが汚染されます。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