Это действительно Java?


это действительно 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));
    }

}
  • затмение 3.5 говорит да
  • программное обеспечение Eclipse 3.6 говорит нет
  • Intellij 9 говорит да
  • Sun javac 1.6.0_20 говорит да
  • GCJ 4.4.3 говорит да
  • компилятор GWT говорит да
  • толпа на мой предыдущий вопрос Stackoverflow говорит нет

мое понимание теории java говорит нет!

было бы интересно узнать, какие JLS говорит об этом.

10 59

10 ответов:

это зависит от того, как вы хотите назвать эти способы. Если вы хотите вызвать эти методы из другого исходного кода Java, то он считается недействительным по причинам, проиллюстрированным в Эдвина!--10-->. Это ограничение языка Java.

однако не все классы должны быть сгенерированы из исходного кода Java (рассмотрим все языки, которые используют JVM в качестве среды выполнения: JRuby, Jython и т. д...). на уровне байт-кода виртуальная машина можно устранить неоднозначность двух методов, поскольку инструкции байт-кода указывают тип возвращаемого они ожидали. Например, вот класс, написанный в Жасмин это может вызвать любой из этих методов:

.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
strings
numbers

обновление

Симон опубликовано пример программы что вызывает эти методы:

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
Compiled from "RealyCompilesAndRunsFine.java"
class RealyCompilesAndRunsFine extends java.lang.Object{
RealyCompilesAndRunsFine();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."":()V
   4:   return

public static java.lang.String f(java.util.List);
  Code:
   0:   aload_0
   1:   iconst_0
   2:   invokeinterface #2,  2; //InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
   7:   checkcast       #3; //class java/lang/String
   10:  areturn

public static java.lang.Integer f(java.util.List);
  Code:
   0:   aload_0
   1:   iconst_0
   2:   invokeinterface #2,  2; //InterfaceMethod java/util/List.get:(I)Ljava/lang/Object;
   7:   checkcast       #4; //class java/lang/Integer
   10:  areturn

public static void main(java.lang.String[]);
  Code:
   0:   iconst_1
   1:   anewarray       #3; //class java/lang/String
   4:   dup
   5:   iconst_0
   6:   ldc     #5; //String asdf
   8:   aastore
   9:   invokestatic    #6; //Method java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List;
   12:  invokestatic    #7; //Method f:(Ljava/util/List;)Ljava/lang/String;
   15:  astore_1
   16:  iconst_1
   17:  anewarray       #4; //class java/lang/Integer
   20:  dup
   21:  iconst_0
   22:  bipush  123
   24:  invokestatic    #8; //Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   27:  aastore
   28:  invokestatic    #6; //Method java/util/Arrays.asList:([Ljava/lang/Object;)Ljava/util/List;
   31:  invokestatic    #9; //Method f:(Ljava/util/List;)Ljava/lang/Integer;
   34:  astore_2
   35:  getstatic       #10; //Field java/lang/System.out:Ljava/io/PrintStream;
   38:  aload_1
   39:  invokevirtual   #11; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
   42:  getstatic       #10; //Field java/lang/System.out:Ljava/io/PrintStream;
   45:  aload_2
   46:  invokevirtual   #12; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V
   49:  return

оказывается, компилятор Sun генерирует байт-код, необходимый для устранения неоднозначности методов (см. инструкции 12 и 31 в последнем методе).

обновление #2

The Спецификация Языка Java предполагает, что это может быть действительно исходный код Java. На странице 449 (§15.12 выражения вызова метода) мы видим это:

возможно, что ни один метод не является наиболее конкретным, потому что есть два или больше методов, которые максимально специфичны. В этом случае:

  • если все максимально конкретные методы имеют переопределение эквивалентных (§8.4.2) сигнатур, затем:
    • если точно один из максимально конкретных методов не объявлен абстрактным, это самый специфический метод.
    • в противном случае, если все максимально специфические методы, объявленные абстрактными, и сигнатуры всех максимально специфичных методов имеют одинаковые стирание (§4.6), тогда наиболее конкретный метод выбирается произвольно среди подмножество максимально специфичных методов, которые имеют наиболее специфичные тип возврата. Однако наиболее специфическим методом считается бросок проверенное исключение, если и только если это исключение или его стирание объявлено в бросок пункты каждого из максимально конкретных методов.
  • в противном случае, мы говорим, что вызов метода неоднозначен, и в compiletime возникает ошибка.

если я не ошибаюсь, это поведение должно применяться только к методам, объявленным как абстрактные...

обновление #3

благодаря комментарию Илмтитана:

@Adam Paynter: ваш полужирный текст делает не иметь значение, потому что это только случай когда два метода переопределение-эквивалент, который показал Дэн но это было не так. Таким образом, определяющий фактор должен быть, если JLS учитывает универсальные типы, когда определение наиболее конкретного метода. – Илмтитан

--- редактировать в ответ на комментарии ниже ---

ок, так что это действительно Java, но это не должно быть. Ключ заключается в том, что он на самом деле не полагается на возвращаемый тип, а на стираемый параметр Generics.

Это не будет работать на нестатическом методе и явно запрещено на нестатическом методе. Попытка сделать это в классе потерпит неудачу из-за дополнительных проблем, во-первых, что типичный класс не финал как класс класс есть.

это непоследовательность в другом довольно последовательном языке. Ти выйду на ветку и скажу, что это должны незаконным, даже если это технически возможно. Это на самом деле ничего не добавляет к удобочитаемости языка, и это мало добавляет к способности решать значимые проблемы. Единственная значимая проблема, которую он, похоже, решает, - достаточно ли вы знакомы с языком, чтобы знать, когда он core принципы кажется, нарушаются внутренние несоответствия языка в разрешении стирания типов, обобщений и результирующих сигнатур методов.

определенно код, которого следует избегать, поскольку тривиально решать одну и ту же проблему любым количеством более значимых способов, и единственное преимущество заключается в том, чтобы увидеть, знает ли рецензент/расширитель пыльный грязный угол спецификации языка.

--- Оригинальный пост ниже ---

в то время как компиляторы могут допустившие это, ответа до сих пор нет.

стирание превратит как List, так и List в неприкрашенный список. Это означает, что оба ваших метода "f" будут иметь одинаковую сигнатуру, но разные типы возврата. Возвращаемый тип нельзя использовать для различения методов, потому что это не удастся, когда вы вернетесь в общий супертип; например:

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

вы пробовали записывать возвращенные значения в переменные? Возможно, компилятор оптимизировал вещи таким образом, что это не наступив на правильный код ошибки.

один вопрос, на который не был дан ответ: почему он вызывает только ошибку компиляции в Eclipse 3.6?

вот почему: это особенность.

в javac 7 рассматриваются два метода дубликаты (или ошибка столкновения имен) независимо от их типов возврата.

Это поведение теперь более последовательно с javac 1.5, который сообщил имя столкновение ошибок в методах и игнорируется их возвращаемые типы. Только в 1.6 был изменение сделано, что включены возвращаемые типы при обнаружении дубликатов методов.

мы решили внести это изменение на всех уровнях соответствия (1.5, 1.6, 1.7) в версии 3.6, поэтому пользователи не будут удивлены изменением, если они скомпилируйте их код с помощью javac 7.

Ну, если я правильно понимаю пункт три в первом списке из раздела 8.4.2 спецификации, он говорит, что ваши методы f () имеют ту же подпись:

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

это спецификация, которая действительно отвечает на этот вопрос, а не наблюдаемое поведение компилятора X или IDE X. Все, что мы можем сказать, глядя на инструменты, - это то, как автор инструмента интерпретировал спецификацию.

Если применяем пулю три, получаем:

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

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

и подписи совпадают, так что есть коллизия и код не должен компилироваться.

это действительно ВАЗ на спецификация.

подпись метода m1 - Это подподписью подпись метод m2 если

  • m2 имеет ту же подпись, что и m1 или

  • подпись m1 Это то же самое, что и стирание подписи m2.

так что это не подсигналы друг друга, потому что стирание List<String> не List<Integer> и наоборот.

две сигнатуры метода m1 и m2 are переопределить-эквивалент эквивалентность или m1 - Это подподписью m2 или m2 это подподписью m1.

таким образом, эти два не переопределяют эквивалент (обратите внимание на iff). И правило для перегрузки:

если два метода класса (будь оба объявлены в тот же класс, или оба наследуются классом или одним объявленный и один унаследованный) есть то же имя, но подписи, которые не являются override-эквивалент, затем метод имя, как говорят, перегружается.

поэтому эти два метода перегружены и все должно работать.

также работает (с 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);
    }

}

из того, что я могу сказать.файл класса может содержать оба метода, так как дескриптор метода содержит параметры, а также тип возвращаемого значения. Если возвращаемый тип будет одинаковым, то дескрипторы будут одинаковыми, а методы будут неразличимы после стирания типа (следовательно, он также не работает с void). http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html#7035

теперь, вызов метода с invoke_virtual требует дескриптор метода, поэтому вы можете фактически сказать, какой из методов вы хотите вызвать, поэтому кажется, что все эти компиляторы, которые все еще имеют общую информацию, просто помещают дескриптор для метода, который соответствует общему типу параметра, поэтому тогда он жестко закодирован в байт-коде, какой метод вызывать (в отличие от их дескрипторов или более точно по типу возврата в этих дескрипторах), даже если параметр теперь является списком, без общего информация.

хотя я нахожу эту практику немного сомнительной, я должен сказать, что нахожу ее классной, что вы можете это сделать, и думаю, что дженерики должны были быть разработаны, чтобы иметь возможность работать так, как это в первую очередь (да, я знаю, что это создаст проблемы с обратной совместимостью).

вывод типа Java (что происходит, когда вы вызываете статические, общие методы, такие как Array.asList) является сложным и не вполне определенным в JLS. Этот документ от 2008 года имеет очень интересное описание некоторых проблем и как это может быть исправлено:

вывод типа Java нарушен: как мы можем это исправить?

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

похоже, компилятор выбирает наиболее конкретный метод на основе дженериков.

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