質問:
- Java の raw 型とは何ですか? また、新しいコードでは raw 型を使用しないほうがよいとよく言われるのはなぜですか?
- 生の型を使用できない場合の代替手段は何ですか? また、それがどのように優れているのでしょうか?
ベストアンサー1
生タイプとは何ですか?
Java 言語仕様では、raw 型を次のように定義しています。
JLS 4.8 生の型
生の型は次のいずれかとして定義されます。
型引数リストを伴わないジェネリック型宣言の名前を取得して形成される参照型。
要素型が raw 型である配列型。
のスーパークラスまたはスーパーインターフェースから継承されていない
static
生の型の非メンバー型。R
R
以下に例を挙げて説明します。
public class MyType<E> {
class Inner { }
static class Nested { }
public static void main(String[] args) {
MyType mt; // warning: MyType is a raw type
MyType.Inner inn; // warning: MyType.Inner is a raw type
MyType.Nested nest; // no warning: not parameterized type
MyType<Object> mt1; // no warning: type parameter given
MyType<?> mt2; // no warning: type parameter given (wildcard OK!)
}
}
ここで、パラメータ化されたMyType<E>
型(JLS4.5 について)。このタイプは、一般的には単にMyType
略して と呼ばれますが、厳密には と呼ばれますMyType<E>
。
mt
上記の定義の最初の箇条書きによって生の型を持ち (コンパイル警告を生成します)、inn
3 番目の箇条書きによっても生の型を持ちます。
MyType.Nested
は、パラメータ化された型のメンバー型ですがMyType<E>
、 であるため、パラメータ化された型ではありませんstatic
。
mt1
、およびはmt2
どちらも実際の型パラメータで宣言されているため、生の型ではありません。
生のタイプの何が特別なのでしょうか?
本質的に、生の型はジェネリックが導入される前と同じように動作します。つまり、以下はコンパイル時に完全に合法です。
List names = new ArrayList(); // warning: raw type!
names.add("John");
names.add("Mary");
names.add(Boolean.FALSE); // not a compilation error!
上記のコードは問題なく実行されますが、次のようなコードもあるとします。
for (Object o : names) {
String name = (String) o;
System.out.println(name);
} // throws ClassCastException!
// java.lang.Boolean cannot be cast to java.lang.String
names
ここで、に ではないものが含まれているため、実行時に問題が発生しますinstanceof String
。
おそらく、names
のみを含めたい場合はString
、依然として生の型を使用して、をすべて手動で確認し、からのすべての項目に手動でキャストすることができます。ただし、生の型を使用せず、Java ジェネリックのパワーを活用してコンパイラにすべての作業を任せる方がさらに良いでしょう。 add
String
names
List<String> names = new ArrayList<String>();
names.add("John");
names.add("Mary");
names.add(Boolean.FALSE); // compilation error!
もちろん、を許可したい場合は、 として宣言することができ、上記のコードはコンパイルされます。names
Boolean
List<Object> names
参照
生の型を<Object>
型パラメータとして使用することとどう違うのでしょうか?
以下は、Effective Java 2nd Edition の項目 23「新しいコードでは生の型を使用しないでください」からの引用です。
List
生の型とパラメータ化された型の違いは何でしょうかList<Object>
。簡単に言えば、前者はジェネリック型のチェックを省略していますが、後者はコンパイラーに対して、任意の型のオブジェクトを保持できることを明示的に伝えています。 をList<String>
型のパラメータに渡すことはできますがList
、型のパラメータに渡すことはできませんList<Object>
。ジェネリックにはサブタイプ規則があり、 はList<String>
生の型 のサブタイプですList
が、パラメータ化された型 のサブタイプではありませんList<Object>
。結果として、のような生の型を使用すると型の安全性が失われますが、 のようなパラメータ化された型を使用すると型の安全性は失われませんList
List<Object>
。
List<Object>
この点を説明するために、 を受け取って を追加する次のメソッドを考えてみましょうnew Object()
。
void appendNewObject(List<Object> list) {
list.add(new Object());
}
Java のジェネリックは不変です。 AList<String>
は ではないList<Object>
ため、次の例ではコンパイラ警告が生成されます。
List<String> names = new ArrayList<String>();
appendNewObject(names); // compilation error!
appendNewObject
生の型をパラメータとして受け取るように宣言した場合List
、これはコンパイルされ、ジェネリックから得られる型の安全性が失われます。
参照
生の型を<?>
型パラメータとして使用する場合とどう違うのでしょうか?
List<Object>
、、List<String>
などはすべて なのでList<?>
、代わりに であると言いたくなるかもしれませんList
。しかし、大きな違いがあります。 はList<E>
のみを定義するためadd(E)
、 に任意のオブジェクトを追加することはできませんList<?>
。一方、生の型にList
は型安全性がないため、add
にはほぼ何でも追加できますList
。
前のスニペットの次のバリエーションを検討してください。
static void appendNewObject(List<?> list) {
list.add(new Object()); // compilation error!
}
//...
List<String> names = new ArrayList<String>();
appendNewObject(names); // this part is fine!
コンパイラは、 の型不変性に違反する可能性からユーザーを保護するという素晴らしい仕事をしましたList<?>
。パラメーターを生の型 として宣言した場合List list
、コードはコンパイルされ、 の型不変性に違反することになりますList<String> names
。
生の型はその型の消去である
JLS 4.8に戻る:
パラメータ化された型の消去、または要素型がパラメータ化された型である配列型の消去を型として使用することができます。このような型は、raw 型と呼ばれます。
[...]
生の型のスーパークラス (それぞれ、スーパーインターフェイス) は、ジェネリック型のいずれかのパラメーター化のスーパークラス (スーパーインターフェイス) の消去です。
static
スーパークラスまたはスーパーインターフェースから継承されていない生の型のコンストラクタ、インスタンス メソッド、または非フィールドの型C
は、 に対応するジェネリック宣言内のその型の消去に対応する生の型ですC
。
簡単に言えば、生の型を使用すると、コンストラクター、インスタンス メソッド、および非static
フィールドも消去されます。
次の例を見てみましょう。
class MyType<E> {
List<String> getNames() {
return Arrays.asList("John", "Mary");
}
public static void main(String[] args) {
MyType rawType = new MyType();
// unchecked warning!
// required: List<String> found: List
List<String> names = rawType.getNames();
// compilation error!
// incompatible types: Object cannot be converted to String
for (String str : rawType.getNames())
System.out.print(str);
}
}
raw を使用するとMyType
、getNames
も消去され、 raw が返されますList
。
JLS4.6 について次のように説明を続けます。
型消去は、コンストラクタまたはメソッドのシグネチャを、パラメータ化された型または型変数を持たないシグネチャにマップします。コンストラクタまたはメソッドのシグネチャの消去は、と同じ名前と、 で指定されたすべての仮パラメータ型の消去
s
で構成されるシグネチャです。s
s
メソッドまたはコンストラクターのシグネチャが消去されると、メソッドの戻り値の型とジェネリック メソッドまたはコンストラクターの型パラメーターも消去されます。
ジェネリック メソッドのシグネチャの消去には型パラメーターがありません。
次のバグレポートには、コンパイラ開発者の Maurizio Cimadamore 氏と JLS の作者の 1 人である Alex Buckley 氏による、なぜこのような動作が発生するのかという意見が含まれています。参考:(簡単に言えば、仕様がシンプルになります。)
安全でないのなら、なぜ生の型の使用が許可されるのでしょうか?
JLS 4.8 からの別の引用を以下に示します。
生の型の使用は、レガシー コードの互換性を保つためにのみ許可されています。Javaプログラミング言語にジェネリック性が導入された後に記述されたコードでは、生の型を使用しないことを強くお勧めします。Java プログラミング言語の将来のバージョンでは、生の型の使用が禁止される可能性があります。
Effective Java 2nd Edition には、次の内容も追加されています。
生の型を使用するべきではないのに、なぜ言語設計者はそれを許可したのでしょうか? 互換性を提供するためです。
ジェネリックが導入されたとき、Java プラットフォームは 20 年目に入ろうとしていましたが、ジェネリックを使用していない Java コードが大量に存在していました。このすべてのコードが合法であり、ジェネリックを使用する新しいコードと相互運用可能であることが重要であると考えられました。パラメータ化された型のインスタンスを通常の型で使用するように設計されたメソッドに渡すことは合法である必要があり、その逆も同様でした。移行互換性と呼ばれるこの要件により、生の型をサポートするという決定が下されました。
要約すると、生の型は新しいコードでは決して使用しないでください。常にパラメーター化された型を使用する必要があります。
例外はないのでしょうか?
残念ながら、Java ジェネリックは非具象化であるため、新しいコードで生の型を使用する必要がある例外が 2 つあります。
- クラスリテラル、例
List.class
:notList<String>.class
instanceof
オペランド、例o instanceof Set
:noto instanceof Set<String>