web-dev-qa-db-ja.com

diamond演算子の結果と生のコンストラクタを区別できますか?

私が書くコードがあります

GenericClass<Foo> foos = new GenericClass<>();

同僚が書いていた

GenericClass<Foo> foos = new GenericClass();

この場合、ダイヤモンド演算子は何も追加しないと主張します。

コンストラクタ実際にジェネリック型に関連する引数を使用するは、生の場合の実行時エラーではなく、<>でコンパイル時エラーを引き起こす可能性があることを知っています。そして、コンパイル時のエラーははるかに優れています。 ( この質問 で概説されているように)

コンパイラー(およびIDE)がジェネリックへのraw型の割り当てに関する警告を生成する可能性があることも私は十分に認識しています。

問題は、引数がない場合、またはジェネリック型に関連する引数がない場合です。その場合、構築されたオブジェクトGenericClass<Foo> foosは、使用されたコンストラクターに応じて異なる可能性がありますか、それともJavaの型消去はそれらが同一であることを保証しますか?

26

2つのArrayListsのインスタンス化の場合、1つは末尾にダイヤモンド演算子があり、もう1つはない...

List<Integer> fooList = new ArrayList<>();
List<Integer> barList = new ArrayList();

...生成されたバイトコードは同じです。

LOCALVARIABLE fooList Ljava/util/List; L1 L4 1
// signature Ljava/util/List<Ljava/lang/Integer;>;
// declaration: Java.util.List<Java.lang.Integer>
LOCALVARIABLE barList Ljava/util/List; L2 L4 2
// signature Ljava/util/List<Ljava/lang/Integer;>;
// declaration: Java.util.List<Java.lang.Integer>

したがって、バイトコードごとに2つの間に違いはありません。

ただし、2番目の方法を使用すると、コンパイラはチェックされていない警告を生成します。したがって、2番目のアプローチには価値がありません。あなたがしているすべては、プロジェクトのノイズに追加するコンパイラーで偽陽性のチェックされていない警告を生成することです。


私はこれを行うことが積極的に有害であるシナリオを示すことができました。これの正式な名前は heap汚染 です。 これは発生させたいことではありません コードベースで、この種の呼び出しが表示された場合はいつでも削除する必要があります。

ArrayListの一部の機能を拡張するこのクラスを検討してください。

class Echo<T extends Number> extends ArrayList<T> {
    public Echo() {

    }

    public Echo(Class<T> clazz)  {
        try {
            this.add(clazz.newInstance());
        } catch (InstantiationException | IllegalAccessException e) {
            System.out.println("YOU WON'T SEE ME THROWN");
            System.exit(-127);
        }
    }
}

無害なようです。タイプバウンドが何であれ、インスタンスを追加できます。

ただし、 raw types ...をいじくり回していると、残念な副作用が発生する可能性があります。

final Echo<? super Number> oops = new Echo(ArrayList.class);
oops.add(2);
oops.add(3);

System.out.println(oops);

これは、あらゆる種類の例外をスローする代わりに[[], 2, 3]を出力します。このリスト内のすべてのIntegersに対して操作を実行したい場合、その楽しいArrayList.class呼び出しのおかげで、ClassCastExceptionに遭遇します。

もちろん、ひし形演算子が追加されれば、これらすべてを回避できます。これにより、このようなシナリオが手元にないことが保証されます。

さて、ミックスに生の型を導入したので、JavaはJLS 4.12.2に従って型チェックを実行できません:

たとえば、コード:

List l = new ArrayList<Number>();
List<String> ls = l;  // Unchecked warning

変数lが実行するかどうか、コンパイル時(コンパイル時の型チェックルールの制限内)または実行時に確認することができないため、コンパイル時のチェックされていない警告が発生します。実際、List<String>を参照してください。

上記の状況は非常に似ています。使用した最初の例を見ると、問題に変数を追加するだけではありません。ヒープ汚染はすべて同じように発生します。

List rawFooList = new ArrayList();
List<Integer> fooList = rawFooList;

そのため、バイトコードは同じですが(消去が原因である可能性があります)、このような宣言から異なる動作または異常な動作が発生する可能性があるという事実は変わりません。

生の型を使用しないでください 、mmkay?

11
Makoto

JLSは、この点について実際にはかなり明確です。 http://docs.Oracle.com/javase/specs/jls/se8/html/jls-8.html#jls-8.1.2

まず、「ジェネリッククラス宣言は、型引数による型パラメーターセクションの可能なパラメーター化ごとに1つずつ、パラメーター化された型のセット(4.5)を定義します。これらのパラメーター化された型はすべて、実行時に同じクラスを共有します。」

次に、それは私たちにコードブロックを与えます

Vector<String>  x = new Vector<String>();
Vector<Integer> y = new Vector<Integer>();
boolean b = x.getClass() == y.getClass();

そして、「は変数bに値trueを保持する結果になるでしょう。

インスタンスの等価性のテスト(==)は、xyの両方がまったく同じClassオブジェクトを共有することを示します。

今度はdiamond演算子を使用して、または使用せずにそれを実行します。

Vector<Integer> z = new Vector<>();
Vector<Integer> w = new Vector();
boolean c = z.getClass() == w.getClass();
boolean d = y.getClass() == z.getClass();

繰り返しますが、ctrueであり、dも同様です。

ですから、私が理解しているように、ダイヤモンドを使用する場合と使用しない場合で実行時またはバイトコードに違いがあるかどうかを尋ねている場合、答えは簡単です。違いはありません。

この場合にdiamond演算子を使用する方が良いかどうかは、スタイルと意見の問題です。

追伸メッセンジャーを撃たないでください。この場合は常にdiamond演算子を使用します。しかし、それは私がコンパイラーが私のために行うことが一般的にw/r/tジェネリックが好きで、悪い習慣に陥りたくないからです。

P.P.S.これは一時的な現象である可能性があることを忘れないでください。 http://docs.Oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.8 は、「 Javaプログラミング言語へのジェネリックの導入は強くお勧めしません。Javaプログラミング言語の将来のバージョンでは、生の型の使用が許可されなくなる可能性があります。 "

3

ジェネリック引数が制限されている場合、デフォルトのコンストラクターで問題が発生する可能性があります。たとえば、合計を追跡する数値のリストのずさんで不完全な実装は次のとおりです。

public class NumberList<T extends Number> extends AbstractList<T> {
    List<T> list = new ArrayList<>();
    double sum = 0;

    @Override
    public void add(int index, T element) {
        list.add(index, element);
        sum += element.doubleValue();
    }

    @Override
    public T remove(int index) {
        T removed = list.remove(index);
        sum -= removed.doubleValue();
        return removed;
    }

    @Override
    public T get(int index) {
        return list.get(index);
    }

    @Override
    public int size() {
        return list.size();
    }

    public double getSum() {
        return sum;
    }
}

デフォルトコンストラクターの汎用引数を省略すると、実行時にClassCastExceptionが発生する可能性があります。

List<String> list = new NumberList(); // compiles with warning and runs normally
list.add("test"); // throws CCE

ただし、ひし形演算子を追加すると、コンパイル時エラーが発生します。

List<String> list = new NumberList<>(); // error: incompatible types
list.add("test");
1
Tagir Valeev

あなたの特定の例では:はい、それらは同一です。

一般的に:注意してください、そうでない可能性があります!

その理由は、rawタイプが使用されると、異なるoverloadedコンストラクタ/メソッドが呼び出される可能性があるためです。 だけではないは、型の安全性を向上させ、ランタイムClassCastExceptionを回避します。

オーバーロードされたコンストラクタ

public class Main {

    public static void main(String[] args) {
        Integer anInteger = Integer.valueOf(1);
        GenericClass<Integer> foosRaw = new GenericClass(anInteger);
        GenericClass<Integer> foosDiamond = new GenericClass<>(anInteger);
    }

    private static class GenericClass<T> {

        public GenericClass(Number number) {
            System.out.println("Number");
        }

        public GenericClass(T t) {
            System.out.println("Parameter");
        }
    }
}

Diamondを使用したバージョンは、異なるコンストラクターを呼び出します。上記のプログラムの出力は次のとおりです。

Number
Parameter

オーバーロードされたメソッド

public class Main {

    public static void main(String[] args) {
        method(new GenericClass());
        method(new GenericClass<>());
    }

    private static void method(GenericClass<Integer> genericClass) {
        System.out.println("First method");
    }

    private static void method(Object object) {
        System.out.println("Second method");
    }

    private static class GenericClass<T> { }
}

ひし形のバージョンは別のメソッドを呼び出します。出力:

First method
Second method
1

これは完全な答えではありませんが、さらにいくつかの詳細を提供します。

あなたはのような呼び出しを区別することはできませんが

_GenericClass<T> x1 = new GenericClass<>();
GenericClass<T> x2 = new GenericClass<T>();
GenericClass<T> x3 = new GenericClass();
_

を区別できるツールがあります

_GenericClass<T> x4 = new GenericClass<T>() { };
GenericClass<T> x5 = new GenericClass() { };
_

注:new GenericClass<>() { }がないようですが、現在有効なJavaではありません。

重要なのは、ジェネリックパラメーターに関する型情報が匿名クラスに格納されることです。特に、次の方法でジェネリックパラメーターにアクセスできます。

_Type superclass = x.getClass().getGenericSuperclass();
Type tType = (superclass instanceof ParameterizedType) ?
             ((ParameterizedType) superclass).getActualTypeArguments()[0] : 
             null;
_
  • _x1_、_x2_、および_x3_の場合、tTypeTypeVariableImplのインスタンスになります(3つのすべてのケースで同じインスタンスであり、 getClass()は、3つのケースすべてで同じオブジェクトを返します。

  • _x4_の場合、tTypeは_T.class_になります

  • _x5_の場合getGenericSuperclass()ParameterizedTypeのインスタンスを返さず、代わりにClass(infact _GenericClass.class_)を返します

次に、これを使用して、オブジェクトが(x1、x2またはx3)またはx4またはx5を介して構築されたかどうかを判断できます。

0