4

私が見ている振る舞いから始めるのがおそらく最善だと思います:

public class genericTest {
    public static void main(String[] args) {
        String str = "5318008";

        printClass(str);              // class java.lang.String
        callPrintClass(str);          // class java.lang.String

        printClassVarargs(str);       // class java.lang.String
        callPrintClassVarargs(str);   // class java.lang.Object
    }

    public static <T> void printClass(T str) {
        System.out.println(str.getClass());
    }

    public static <T> void printClassVarargs(T ... str) {
        System.out.println(str.getClass().getComponentType());
    }

    public static <T> void callPrintClass(T str) {
        printClass(str);
    }

    @SuppressWarnings("unchecked")
    public static <T> void callPrintClassVarargs(T str) {
        printClassVarargs(str);
    }
}

とを見るprintClass()callPrintClass()、すべてが正常に機能しているようです。callPrintClass()一般的な引数を取り、それを渡します。printClass()誰がパラメータを送信しているかを気にせずに、この変数を正しいタイプで認識し、想定どおりに実行して出力しjava.lang.Stringます。

しかし、varargsを使おうとすると、これは機能しなくなります。varargsのないメソッドが引数の型を認識するのと同じように、printClassVarargs()引数が型であることを認識することを期待します。また、直接String[]呼び出した場合(出力するのは完全に満足です)、これは発生しませんが、引数の型を忘れて、を取得していると推定する、によって呼び出された場合にのみ発生することにも注意してください。また、ここでコンパイラの警告を抑制しなければならないことも認識しています。これは通常、ジェネリックをキャストしようとしているときに発生しますが、そこで何が起こっているのか正確にはわかりません。printClassVarargs()StringcallPrintClassVarargs()Object

だから私の質問は本当に2つです。この動作の背後にある理由は何ですか?これは型消去の結果ですか、それともJavaが配列を処理する方法ですか?そして第二に、これを回避する方法はありますか?

もちろん、これは単純な例にすぎません。私はこの方法でクラス名を出力しようとはしていませんが、配列を連結するためのオーバーロードされたメソッドを記述しているときに最初に問題を発見しました。

4

2 に答える 2

2

問題は、ジェネリック型の配列を作成できないことに帰着すると思います。callPrintClassVarargs新しい配列インスタンスを明示的に作成して渡すようにを変更すると、printClassVarargs根本的な問題が明示的になります。

// doesn't work, gives compiler error (cannot create array of generic type)
public static <T> void callPrintClassVarargs(T str) {
        printClassVarargs(new T[]{str});
}

//This works
public static <T> void callPrintClassVarargs(T str) {
        printClassVarargs(new Object[]{str});
}

Javaでジェネリック配列型を作成できない理由は何ですか?-この質問は、ジェネリック型の配列を作成できない理由を扱っています。おそらく同じことがこの問題についても説明しています。

于 2012-07-16T09:50:38.043 に答える
2

Varargsは、コンパイラーによって指定されたタイプの配列に変換される構文糖衣です。つまり、amethod(Type arg...)はになりmethod(Type[] arg)ます。

Javaでは、再利用不可能な型(型情報が消去によって失われる型)の配列を作成することはできません。したがって、などの一般的なvarargsprintClassVarargs(T ... str)はに変換されprintClassVarargs(Object[] str)、事実上、型情報が消去されます。これはあなたがあなたのテストで観察しているものです。

- - 編集 - -

printClassVarargs(str)との違いに関する質問(cfrコメント)に答えるcallPrintClassVarargs(str)ために、必要な手がかりを得るためにテストクラスのバイトコードを調べることができます。

public Test();
  Code:
   0:   aload_0
   1:   invokespecial   #8; //Method java/lang/Object."<init>":()V
   4:   return

public static void main(java.lang.String[]);
  Code:
   0:   ldc #16; //String 5318008
   2:   astore_1
   3:   aload_1
   4:   invokestatic    #18; //Method printClass:(Ljava/lang/Object;)V
   7:   aload_1
   8:   invokestatic    #22; //Method callPrintClass:(Ljava/lang/Object;)V
   11:  iconst_1
   12:  anewarray   #25; //class java/lang/String
   15:  dup
   16:  iconst_0
   17:  aload_1
   18:  aastore
   19:  invokestatic    #27; //Method printClassVarargs:([Ljava/lang/Object;)V
   22:  aload_1
   23:  invokestatic    #31; //Method callPrintClassVarargs:(Ljava/lang/Object;)V
   26:  return

public static void printClass(java.lang.Object);
  Code:
   0:   getstatic   #40; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:   aload_0
   4:   invokevirtual   #46; //Method java/lang/Object.getClass:()Ljava/lang/Class;
   7:   invokevirtual   #50; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V
   10:  return

public static void printClassVarargs(java.lang.Object[]);
  Code:
   0:   getstatic   #40; //Field java/lang/System.out:Ljava/io/PrintStream;
   3:   aload_0
   4:   invokevirtual   #46; //Method java/lang/Object.getClass:()Ljava/lang/Class;
   7:   invokevirtual   #59; //Method java/lang/Class.getComponentType:()Ljava/lang/Class;
   10:  invokevirtual   #50; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V
   13:  return

public static void callPrintClass(java.lang.Object);
  Code:
   0:   aload_0
   1:   invokestatic    #18; //Method printClass:(Ljava/lang/Object;)V
   4:   return

public static void callPrintClassVarargs(java.lang.Object);
  Code:
   0:   iconst_1
   1:   anewarray   #3; //class java/lang/Object
   4:   dup
   5:   iconst_0
   6:   aload_0
   7:   aastore
   8:   invokestatic    #27; //Method printClassVarargs:([Ljava/lang/Object;)V
   11:  return

}

main#12で、文字列objの新しいString []が作成され、printClassVarargs()およびのパラメータとして使用されることを確認します。callPrintClassVarargs()

main#19printClassVarargsで、作成されたString[]をパラメーターとして呼び出されます。これにより、printClassVarargs実行時にそのオブジェクトのタイプがわかります。このタイプは保持されました。

main#23 callPrintClassVarargsで呼び出され、作成されたString[]もパラメーターとして使用されます。次にcallPrintClassVarargs#1、新しいアレイが作成されます。今回は、ジェネリック型宣言から利用できる型情報がないため、新しいObject[]が作成されます。String []はこの配列に格納され、にcallPrintClassVarargs#8渡されprintClassVarargsます。これは、componentTypeがobjectであるObject[]で機能する必要があります。

ご覧のとおり、の汎用パラメーターに渡されると、パラメーターのタイプが消去されますcallPrintClassVarargs(T str)

QED

于 2012-07-16T10:07:41.590 に答える