8

言語:Java
コンパイラバージョン:1.6

以下のコードでは、次のことを実行しようとしています。

  1. 作成するList<String>
  2. 追加するString
  3. List<String>生に割り当てるList
  4. 作成するList<Integer>
  5. List生を割り当てるList<Integer>
  6. 追加しますInteger
  7. get()@インデックス1および2を使用して値を取得し、それらを出力します。

すべてのステートメントは(警告付きで)コンパイルされており、正常に実行されます。

しかし、ループをList<Integer>使用してループしようとするとfor、が取得されClassCastExceptionます。list.get()なぜメソッドを使用できるのに、反復できないのか疑問に思っています。

出力:( コメントなしのforループで実行した場合)abcd 200

Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot  be cast to java.lang.Integer
        at genericsamples.CheckRawTypeAdd.main(CheckRawTypeAdd.java:26)

これが私のコードです

import java.util.*;
import java.io.*;
class CheckRawTypeAdd
{
    public static void main(String[] sr)
    {   
        List<String> list_str = new ArrayList<String>();
        list_str.add("abcd");
        List<Integer> list_int = new ArrayList<Integer>();  
        List list_raw; 
        list_raw=list_str;
        list_int=list_raw;
        list_int.add(200);
        Object o1 = list_int.get(0);
        Object o2 = list_int.get(1);        
        System.out.println(o1);
        System.out.println(o2);
        //for(Object o : list_int)
        //{
        //  System.out.println("o value is"+o);
        //}
    }
}
4

2 に答える 2

5

これはコンパイラのバグだと思いjavacます。チェックされたキャストが挿入されています。これを使用javap -c CheckRawTypeAddしてクラスを逆アセンブルすることができます(キャストは101です。コンパイルする前に不要なコード行の一部を取り出したため、コードポイントは異なります)。

  77: invokeinterface #10,  1           // InterfaceMethod java/util/List.iterator:()Ljava/util/Iterator;
  82: astore        6
  84: aload         6
  86: invokeinterface #11,  1           // InterfaceMethod java/util/Iterator.hasNext:()Z
  91: ifeq          109
  94: aload         6
  96: invokeinterface #12,  1           // InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object;
 101: checkcast     #13                 // class java/lang/Integer

ただし、Java言語仕様(14.14.2)Objectは、このキャストはではなく、にすべきであることを示していIntegerます。それは文法を介して用語を定義することから始まります:

EnhancedForStatement:
    for ( FormalParameter : Expression ) Statement

FormalParameter:
    VariableModifiersopt Type VariableDeclaratorId

VariableDeclaratorId:
    Identifier
    VariableDeclaratorId []

したがって、私たちの場合TypeはですObject。次に、これが何に変換されるかを説明します。

for (I #i = Expression.iterator(); #i.hasNext(); ) {
    VariableModifiersopt TargetType Identifier =
        (TargetType) #i.next();
    Statement
}

したがって、ここで関連するのはの解像度ですTargetType。これはJLSでも定義されています。

Type(FormalParameterプロダクション内)が参照型の場合、TargetTypeはTypeです。

Object確かに参照型であるTargetTypeように、それでObject、チェックされたキャストはObject、ではなく、になりますInteger

これがバグであることは、ecj(Eclipseのコンパイラ)が使用されている場合はこの問題が発生しないことを指摘しているこのスレッドの他の人によってさらに証明されています。ただし、ジェネリックスを悪用して実行する必要があるため、これはOracleコンパイラチームにとって優先度の低いバグになることを理解しています。これはバグではなく機能だと言っても過言ではありません。

ファローアップ

これがバグであることを最終的に確認するために、この正確な問題に関する既存のバグレポートを次に示します。

また、2つのことに注意する必要があります。 まず、上記で示したJLSリファレンスは最新のJLSにあり、そのセクションは実際にはJava 7用に変更されています(このバグに対応して!)

これが、Java6以前の拡張forステートメントに変換されるべきものです。

for (I #i = Expression.iterator(); #i.hasNext(); ) {
        VariableModifiersopt Type Identifier = #i.next();
   Statement
}

ご覧のとおり、ここではチェックキャストは指定されていません。つまり、バグは間違ったキャストjavacを行っているということではなく、キャストを行っているということでした。

次に、Java 7ではjavac、JLS SE 7仕様(上記で引用したもの)に従ってコードを正しくコンパイルします。したがって、次のコードが機能します。

List<String> list_str = new ArrayList<String>();
((List) list_str).add(new StringBuilder(" stringbuilder"));
for (CharSequence o : list_str) {
   System.out.println("o value is" + o);
}

正しくキャストするとCharSequence、ではなくString。最初は、JDK7ではなくJDK6を使用してコンパイルしていました。

于 2012-09-09T15:38:07.743 に答える
1

コード

    for(Object o : list_int)
    {
      System.out.println("o value is"+o);
    }

次のようなものと同等です:

for (Iterator<Integer> it = list.iterator(); it.hasNext();) {
    Integer o = it.next();
    System.out.println("o value is"+o);
}

ご覧のとおりIterator、一般的であるため、値をそのパラメータータイプにキャストします(Integerこの場合)。

したがって、Integer o = it.next();舞台裏の行は次のようになります。

Integer o = (Integer)it.next();

これで、がどのように生成されるかは明らかだと思いますClassCastException。実際、文字列値をリストに挿入することができたので、it.next()文字列を返すとキャストは失敗します。

したがって、「どのようにして文字列をintリストに挿入できたのか」という疑問が残ります。答えは、ジェネリックはコンパイラの魔法であるということです。彼らの他の名前は消去です。Javaバイトコードには、リストタイプに関する情報は含まれていません。必要に応じてコンクリートタイプへの鋳造が含まれています。これが、パラメータ化されたリストを生のリストに割り当ててから、それにスティングを追加することに成功した理由です。

あなたが正しく述べたように、あなたは警告を見ました。結論は、「コンパイルの警告を無視しないでください」です。

于 2012-09-09T15:17:12.253 に答える