web-dev-qa-db-ja.com

Java 7のひし形演算子(<>)のポイントは何ですか?

Java 7のひし形演算子は、次のようなコードを許可します。

List<String> list = new LinkedList<>();

しかし、Java 5/6では、私は簡単に書くことができます:

List<String> list = new LinkedList();

タイプ消去の私の理解はこれらが全く同じであるということです。 (ジェネリックはとにかくランタイム時に削除されます)。

ダイヤモンドを気にするのはなぜですか。どんな新しい機能/型安全性がそれを可能にしますか?それが新しい機能を生み出さないのなら、なぜ彼らはそれを機能として言及するのでしょうか?この概念に対する私の理解には欠陥がありますか?

433
tofarr

の問題

List<String> list = new LinkedList();

左側ではgeneric type List<String>を使用していますが、右側ではraw type LinkedListを使用しています。 JavaのRAW型は実質的には事前総称コードとの互換性のためにのみ存在し、絶対に必要な場合を除き、新しいコードでは使用しないでください。

さて、Javaが最初から総称を持っていて、LinkedListのように、元々総称を持つ前に作成された型を持っていなかった場合、おそらくジェネリック型のコンストラクタが自動的にその型パラメータを可能であれば代入の左側。しかし、それはしておらず、後方互換性のために生の型とジェネリック型を別々に扱わなければなりません。そのため、型パラメータを繰り返すことなく汎用オブジェクトの新しいインスタンスを宣言するわずかに異なる、しかし同様に便利な方法を作成する必要があります。

元のList<String> list = new LinkedList()の例では、コンパイラはその代入に対して警告を生成します。このことを考慮:

List<String> strings = ... // some list that contains some strings

// Totally legal since you used the raw type and lost all type checking!
List<Integer> integers = new LinkedList(strings);

ジェネリックは、間違ったことをすることに対するコンパイル時の保護を提供するために存在します。上記の例では、生の型を使用することはあなたがこの保護を得られないことを意味し、実行時にエラーが発生します。だから生の型を使うべきではないのです。

// Not legal since the right side is actually generic!
List<Integer> integers = new LinkedList<>(strings);

ただし、ひし形演算子を使用すると、代入の右側を、左側と同じ型パラメーターを持つ真の総称インスタンスとして定義できます。これらのパラメーターを再度入力する必要はありません。生の型を使うのと同じようにほぼでジェネリックの安全性を保つことができます。

私が理解するべき重要なことは生の型(<>のない)はジェネリック型と同じように扱うことができないということです。生の型を宣言しても、総称の利点や型チェックは得られません。また、総称はJava言語の汎用部分です ...これらはCollectionsの引数のないコンストラクターには適用されません。

479
ColinD

あなたの理解は少し欠陥があります。あなたは自分自身を繰り返す必要はないので、ダイヤモンド演算子はいい機能です。型を宣言するときに型を定義することは意味がありますが、右側で定義し直すことは意味がありません。 DRYの原則.

型定義のあいまいさを説明しましょう。型は実行時に削除されますが、型定義を持つListから何かを取得すると、リストを宣言するときに定義した型として返されます。取得したオブジェクトを元の型にキャストする場合を除いて、オブジェクトの機能は非常にトリッキーでClassCastExceptionが発生することがあります。

List<String> list = new LinkedList()を使用するとrawtype警告が表示されます。

34

この行は[未チェック]の警告を引き起こします。

List<String> list = new LinkedList();

それで、質問は変わります:なぜ新しいチェックボックスが作成された場合にのみ[未チェック]警告が自動的に抑制されないのですか?

<>機能を追加するよりもはるかに困難な作業になると思います。

UPD:生の型を「ほんの少しのために」使用するのが合法的であれば、混乱が生じるだろうとも思います。

16
Roman

理論的には、菱形演算子を使用すると、型引数を繰り返し保存することで、よりコンパクトで読みやすいコードを作成できます。実際には、混乱を招くような2つの文字だけで、何も得られません。どうして?

  1. 普通のプログラマは新しいコードで生の型を使います。そのため、コンパイラは単に型引数を記述しないことで、それらを推論したいと考えることができます。
  2. ひし形演算子は型情報を提供しません、それはただ「それは大丈夫だろう」とコンパイラに言います。それを省略することによってあなたは害を及ぼすことができません。菱形演算子が正当な場所であれば、コンパイラによって「推測」される可能性があります。

私見、ソースをJava 7としてマークするための明確で単純な方法を持つことはそのような奇妙なことを発明することよりも役に立つでしょう。そのようにマークされたコードでは、何も失うことなく生の型を禁止することができます。

ところで、私はそれがコンパイルスイッチを使用して行われるべきだとは思わない。プログラムファイルのJavaバージョンはファイルの属性であり、まったくオプションではありません。些細なことと同じくらい簡単なこと

package 7 com.example;

明確にすることができます(あなたは1つ以上の派手なキーワードを含むより洗練されたものを好むかもしれません)。異なるJavaバージョン用に書かれたソースを問題なくコンパイルすることさえ可能です。互換性を失うことなく、新しいキーワード( "module"など)を導入したり、いくつかの古い機能(単一のファイルに複数の非公開の非ネストクラスなど)を削除することができます。

13
maaartinus

List<String> list = new LinkedList();を書くと、コンパイラは「未チェック」の警告を出します。無視してもかまいませんが、これらの警告を無視していた場合は、実際のタイプの安全性の問題について通知する警告を見逃すこともあります。

それで、余分な警告を生成しないコードを書くのがより良いです、そして、ダイアモンドオペレータは不必要な繰り返しなしであなたが便利な方法でそれをすることを可能にします。

8
axtavt

他の回答で述べたことはすべて有効ですが、ユースケースは完全に有効なわけではありません。 Guava そして特にコレクション関連のものをチェックアウトすれば、静的メソッドでも同じことが行われています。例えば。 Lists.newArrayList() これで書くことができます

List<String> names = Lists.newArrayList();

または静的インポートあり

import static com.google.common.collect.Lists.*;
...
List<String> names = newArrayList();
List<String> names = newArrayList("one", "two", "three");

Guavaにはこのような他の非常に強力な機能があり、私は実際には<>の用途をあまり考えられません。

ダイアモンド演算子の振る舞いをデフォルトにしたい場合、つまり式が式の左側から推論される場合、または左側の型が右側から推論される場合は、それがもっと便利でした。後者はScalaで起こることです。

4
allprog

ひし形演算子のポイントは、単にジェネリック型を宣言するときにコードの型付けを減らすことです。ランタイムには何の影響もありません。

Java 5と6で指定した場合の唯一の違いは、

List<String> list = new ArrayList();

それはあなたがlist@SuppressWarnings("unchecked")を指定しなければならないということです。私の理解では、ダイヤモンドオペレーターは開発をより簡単にしようとしています。実行時にジェネリックスを実行しても何もしません。

3
Buhake Sindi