59

これは有効なJavaですか?

import java.util.Arrays;
import java.util.List;

class TestWillThatCompile {

    public static String f(List<String> list) {
        System.out.println("strings");
        return null;
    }

    public static Integer f(List<Integer> list) {
        System.out.println("numbers");
        return null;
    }

    public static void main(String[] args) {
        f(Arrays.asList("asdf"));
        f(Arrays.asList(123));
    }

}
  • Eclipse3.5は「はい」と言っています
  • Eclipse3.6はノーと言っています
  • Intellij9は「はい」と言っています
  • Sunjavac1.6.0_20は「はい」と言っています
  • GCJ4.4.3は「はい」と言っています
  • GWTコンパイラは「はい」と言います
  • 私の前のStackoverflowの質問で群衆はノーと言います

私のJava理論の理解はノーと言っています!

JLSがそれについて何を言っているかを知ることは興味深いでしょう。

4

10 に答える 10

29

これらのメソッドをどのように呼び出すかによって異なります。これらのメソッドを他の Java ソース コードから呼び出したい場合は、 Edwin の回答に示されている理由から無効と見なされます。これは Java 言語の制限です。

ただし、すべてのクラスを Java ソース コードから生成する必要があるわけではありません (JVM をランタイムとして使用するすべての言語を考慮してください: JRuby、Jython など...)。バイトコード レベルでは、JVM は 2 つのメソッドのあいまいさを解消できます。これは、バイトコード命令が期待する戻り値の型を指定しているためです。たとえば、これらのメソッドのいずれかを呼び出すことができるJasminで記述されたクラスを次に示します。

.class public CallAmbiguousMethod
.super java/lang/Object

.method public static main([Ljava/lang/String;)V
  .limit stack 3
  .limit locals 1

  ; Call the method that returns String
  aconst_null
  invokestatic   TestWillThatCompile/f(Ljava/util/List;)Ljava/lang/String;

  ; Call the method that returns Integer
  aconst_null
  invokestatic   TestWillThatCompile/f(Ljava/util/List;)Ljava/lang/Integer;

  return

.end method

次のコマンドを使用して、クラス ファイルにコンパイルします。

java -jar jasmin.jar CallAmbiguousMethod.j

そして、次を使用して呼び出します。

java CallAmbiguousMethod

見よ、出力は次のとおりです。

> Java CallAmbiguousMethod
文字列
数字

アップデート

Simon は、これらのメソッドを呼び出すサンプル プログラムを投稿しました。

import java.util.Arrays;
import java.util.List;

class RealyCompilesAndRunsFine {

    public static String f(List<String> list) {
        return list.get(0);
    }

    public static Integer f(List<Integer> list) {
        return list.get(0);
    }

    public static void main(String[] args) {
        final String string = f(Arrays.asList("asdf"));
        final Integer integer = f(Arrays.asList(123));
        System.out.println(string);
        System.out.println(integer);
    }

}

生成された Java バイトコードは次のとおりです。

>javap -c RealyCompilesAndRunsFine
「RealyCompilesAndRunsFine.java」からコンパイル
class RealyCompilesAndRunsFine extends java.lang.Object{
RealyCompilesAndRunsFine();
  コード:
   0: aload_0
   1: 特別な #1 を呼び出します。//メソッド java/lang/Object."":()V
   4: 戻る

public static java.lang.String f(java.util.List);
  コード:
   0: aload_0
   1: アイコンスト_0
   2: 呼び出しインターフェイス #2、2; //InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
   7: チェックキャスト #3; //クラス java/lang/String
   10: 戻る

public static java.lang.Integer f(java.util.List);
  コード:
   0: aload_0
   1: アイコンスト_0
   2: 呼び出しインターフェイス #2、2; //InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
   7: チェックキャスト #4; //クラス java/lang/Integer
   10: 戻る

public static void main(java.lang.String[]);
  コード:
   0: アイコンスト_1
   1: 新たな配列 #3; //クラス java/lang/String
   4: 複製
   5: アイコンスト_0
   6: ldc #5; //文字列 asdf
   8: アストア
   9: インボークスタティック #6; //メソッド java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List;
   12: インボークスタティック #7; //メソッド f:(Ljava/util/List;)Ljava/lang/String;
   15: アストア_1
   16: アイコンスト_1
   17: 新たな配列 #4; //クラス java/lang/Integer
   20: 重複
   21: アイコンスト_0
   22:バイプッシュ123
   24: インボークスタティック #8; //メソッド java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   27: アーストア
   28: インボークスタティック #6; //メソッド java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List;
   31: インボークスタティック #9; //メソッド f:(Ljava/util/List;)Ljava/lang/Integer;
   34: アストア_2
   35: getstatic #10; //フィールド java/lang/System.out:Ljava/io/PrintStream;
   38: アロード_1
   39: インボークバーチャル #11; //メソッド java/io/PrintStream.println:(Ljava/lang/String;)V
   42: getstatic #10; //フィールド java/lang/System.out:Ljava/io/PrintStream;
   45: アロード_2
   46: インボークバーチャル #12; //メソッド java/io/PrintStream.println:(Ljava/lang/Object;)V
   49: 戻る

メソッドのあいまいさを解消するために必要なバイトコードを Sun コンパイラが生成していることがわかります (最後のメソッドの手順 12 と 31 を参照)。

アップデート #2

Java 言語仕様は、これが実際には有効な Java ソース コードである可能性があることを示唆しています。449 ページ (§15.12 メソッド呼び出し式) に、次のように記載されています。

最も具体的なメソッドが 2 つ以上あるため、最も具体的なメソッドがない可能性があります。この場合:

  • すべての最大限に具体的なメソッドにオーバーライドと同等の (§8.4.2) 署名がある場合、次のようになります。
    • 最大限に具体的なメソッドの 1 つだけが抽象として宣言されていない場合、それは最も具体的なメソッドです。
    • それ以外の場合、すべての最大に固有のメソッドが抽象として宣言され、すべての最大に固有のメソッドの署名が同じ消去 (§4.6) を持つ場合、最も固有のメソッドは、最大に固有のメソッドのサブセットの中から任意に選択されます。最も具体的な戻り値の型。ただし、最も具体的なメソッドは、その例外またはその消去が最大限に具体的な各メソッドの throws 句で宣言されている場合にのみ、チェック済み例外をスローすると見なされます。
  • それ以外の場合、メソッド呼び出しがあいまいであると言い、コンパイル時エラーが発生します。

私が間違っていない限り、この動作は抽象として宣言されたメソッドにのみ適用されます...

アップデート #3

ILMTitan のコメントに感謝します。

@Adam Paynter: 太字のテキストは重要ではありません。これは、2 つのメソッドがオーバーライドと同等である場合のみであり、Dan が示したのはそうではなかったからです。したがって、決定要因は、JLS が最も具体的なメソッドを決定するときにジェネリック型を考慮に入れるかどうかでなければなりません。– ILMチタン

于 2010-06-24T13:11:56.387 に答える
13

--- 以下のコメントに応じて編集 ---

わかりました、それで有効な Java ですが、そうであってはなりません。重要なのは、実際には戻り値の型に依存しているのではなく、消去された Generics パラメータに依存していることです。

これは非静的メソッドでは機能せず、非静的メソッドでは明示的に禁止されています。クラスでそうしようとすると、余分な問題が原因で失敗します.1つ目は、典型的なクラスがクラスクラスのように最終的ではないということです。

それ以外の場合はかなり一貫した言語での矛盾です。TI は、たとえ技術的に許可されていたとしても、それは違法であるべきだと言い張るでしょう。言語の可読性には何も追加されず、意味のある問題を解決する能力にはほとんど追加されません。それが解決すると思われる唯一の意味のある問題は、型消去、ジェネリック、および結果として生じるメソッド署名を解決する際の言語の内部矛盾によって、コアの信条がいつ破られるように見えるかを知るのに十分な言語に精通しているかどうかです。

同じ問題をより有意義な方法で解決するのは簡単なことであり、唯一の利点は、レビュアー/エクステンダーが言語仕様のほこりの多い汚れたコーナーを知っているかどうかを確認することであるため、コードは絶対に避ける必要があります。

--- 元の投稿が続きます ---

コンパイラはそれを許可したかもしれませんが、答えはまだノーです。

Erasure は、List<String> と List<Integer> の両方を飾り気のない List に変えます。つまり、両方の「f」メソッドのシグネチャは同じですが、戻り値の型は異なります。戻り値の型を使用してメソッドを区別することはできません。これは、共通のスーパー型に戻るときに失敗するためです。お気に入り:

Object o = f(Arrays.asList("asdf"));

戻り値を変数にキャプチャしようとしましたか? おそらく、コンパイラが正しいエラー コードを踏まないように最適化したのでしょう。

于 2010-06-24T12:58:03.050 に答える
11

答えられていない質問の 1 つは、Eclipse 3.6 でのみコンパイル エラーが発生するのはなぜですか?

理由は次のとおりです。これは機能です

javac 7 では、戻り値の型に関係なく、2 つのメソッドが重複 (または名前衝突エラー) と見なされます。

この動作は、メソッドで名前衝突エラーを報告し、戻り値の型を無視する javac 1.5 とより一貫性が保たれるようになりました。1.6 でのみ、重複したメソッドを検出する際の戻り値の型を含む変更が行われました。

3.6 リリースのすべての準拠レベル (1.5、1.6、1.7) でこの変更を行うことを決定したため、ユーザーが javac 7 を使用してコードをコンパイルした場合に、この変更に驚くことはありません。

于 2010-06-25T11:01:24.947 に答える
5

仕様上有効です。

メソッドの署名は、次のいずれかの場合m1、メソッドの署名のサブ署名です。m2

  • m2m1、またはと同じ署名を持っています

  • の署名は の署名m1の消去と同じです m2

したがって、これらはお互いのサブ署名でList<String>はありませんList<Integer>

2 つのメソッド シグネチャm1とは、 のサブシグネチャであるか、 のサブシグネチャでm2ある場合、オーバーライド同等です。m1m2m2m1

したがって、これら 2 つはオーバーライドと同等ではありません ( iffに注意してください)。オーバーロードのルールは次のとおりです。

クラスの 2 つのメソッド (両方が同じクラスで宣言されているか、両方がクラスによって継承されているか、または 1 つが宣言され 1 つが継承されているかにかかわらず) が同じ名前を持ちますが、シグネチャがオーバーライド等価でない場合、メソッド名は次のようになります。過負荷。

したがって、これら 2 つのメソッドはオーバーロードされており、すべてが機能するはずです。

于 2010-06-24T13:27:33.733 に答える
5

仕様のセクション 8.4.2 の最初のリストの箇条書き 3 を正しく理解していれば、f() メソッドは同じシグネチャを持っていることがわかります。

http://java.sun.com/docs/books/jls/third_edition/html/classes.html#38649

この質問に本当に答えるのは仕様であり、コンパイラ X や IDE X の観察された動作ではありません。ツールを見て言えることは、ツールの作成者が仕様をどのように解釈したかということだけです。

箇条書き 3 を適用すると、次のようになります。

...
    public static String f(List<String> list) {
        System.out.println("文字列");
        null を返します。
    }

    public static Integer f(List<String> list) {
        System.out.println("数字");
        null を返します。
    }
...

署名が一致するため、衝突が発生し、コードはコンパイルされません。

于 2010-06-24T13:31:38.793 に答える
1

Java の型推論 (Array.asList のような静的でジェネリックなメソッドを呼び出すとどうなるか) は複雑で、JLS では明確に規定されていません。2008年のこの論文には、いくつかの問題とその修正方法に関する非常に興味深い説明があります。

Java 型推論が壊れています: どうすれば修正できますか?

于 2010-06-24T14:16:48.513 に答える
1

また、動作しています(今回はsun java 1.6.0_16で)

import java.util.Arrays;
import java.util.List;

class RealyCompilesAndRunsFine {

    public static String f(List<String> list) {
        return list.get(0);
    }

    public static Integer f(List<Integer> list) {
        return list.get(0);
    }

    public static void main(String[] args) {
        final String string = f(Arrays.asList("asdf"));
        final Integer integer = f(Arrays.asList(123));
        System.out.println(string);
        System.out.println(integer);
    }

}
于 2010-06-24T13:10:51.500 に答える
1

メソッド記述子がパラメーターと戻り値の型を保持するため、.class ファイルは両方のメソッドを保持できることがわかります。戻り値の型が同じ場合、記述子も同じになり、型の消去後にメソッドを区別できなくなります (したがって、void でも機能しません)。http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html#7035

現在、invoke_virtual を使用してメソッドを呼び出すにはメソッド記述子が必要であるため、実際にどのメソッドを呼び出したいかを指定できます。そのため、まだ一般的な情報を保持しているすべてのコンパイラは、単にメソッドの記述子を配置するだけのようです。これはパラメーターのジェネリック型と一致するため、パラメーターがジェネリックなしのリストであっても、どのメソッドを呼び出すか (記述子によって、またはより正確にはそれらの記述子の戻り値の型によって区別される) がバイトコードにハードコーディングされます。情報。

この慣行には少し疑問があると思いますが、これができるのはちょっとクールだと思いますし、ジェネリックは最初からこのように機能するように設計されているべきだと思います (はい、そうなれば下位互換性に問題が生じます)。

于 2010-06-24T13:29:31.143 に答える
0

Eclipse はこれからバイト コードを生成できます。

public class Bla {
private static BigDecimal abc(List<BigDecimal> l) {
    return l.iterator().next().multiply(new BigDecimal(123));
}

private static String abc(List<String> l) {
    return l.iterator().next().length() + "";
}

public static void main(String[] args) {
    System.out.println(abc(Arrays.asList("asdf")));
    System.out.println(abc(Arrays.<BigDecimal>asList(new BigDecimal(123))));
}
}

出力:

4

15129

于 2010-06-24T13:27:04.343 に答える
0

コンパイラは、ジェネリックに基づいて最も具体的な方法を選択するようです。

import java.util.Arrays;
import java.util.List;

class TestWillThatCompile {

public static Object f(List<?> list) {
    System.out.println("strings");
    return null;
}

public static Integer f(List<Integer> list) {
    System.out.println("numbers");
    return null;
}

public static void main(String[] args) {
    f(Arrays.asList("asdf"));
    f(Arrays.asList(123));
}

}

出力:

strings
numbers
于 2010-06-25T13:53:13.130 に答える