Этот допустимый Java?
import java.util.Arrays;
import java.util.List;
class TestWillThatCompile {
public static String f(List list) {
System.out.println("strings");
return null;
}
public static Integer f(List list) {
System.out.println("numbers");
return null;
}
public static void main(String[] args) {
f(Arrays.asList("asdf"));
f(Arrays.asList(123));
}
}
Мое понимание теории Java говорит "нет"!
Было бы интересно знать то, что JLS говорит об этом.
Это зависит от того, как вы хотите вызвать эти методы. Если вы хотите вызвать эти методы из другого исходного кода Java, то это будет считаться недопустимым по причинам, показанным в ответе Эдвина. Это ограничение языка Java.
Однако не все классы должны генерироваться из исходного кода Java (рассмотрим все языки, использующие JVM в качестве среды выполнения: JRuby, Jython и т.д...). На уровне байткода JVM может различать эти два метода, поскольку инструкции байткода указывают возвращаемый тип, который они ожидают. Например, вот класс, написанный на 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 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
Спецификация языка Java Language Specification предполагает, что это, на самом деле, может быть действительный исходный код Java. На странице 449 (§15.12 Method Invocation Expressions) мы видим следующее:
Возможно, что ни один метод не является наиболее специфичным, потому что есть два или более методов, которые являются максимально конкретными. В этом случае:
- Если все максимально специфичные методы имеют сигнатуры, эквивалентные переопределению (§8.4.2), то:
- Если ровно один из максимально конкретных методов не объявлен абстрактным, то он является наиболее специфичным методом.
- Иначе, если все максимально конкретные методы объявлены абстрактными, и сигнатуры всех максимально конкретных методов имеют одинаковое стирание (§4.6), то наиболее конкретный метод выбирается произвольно среди подмножества максимально конкретных методов, которые имеют наиболее конкретный возвращаемый тип. Однако считается, что наиболее специфичный метод выбрасывает проверяемое исключение тогда и только тогда, когда это исключение или его стирание объявлено в выражениях throws каждого из максимально конкретных методов.
- В противном случае мы говорим, что вызов метода неоднозначен, и возникает компилятивная возникает ошибка.
Если я не ошибаюсь, такое поведение должно применяться только к методам, объявленным как абстрактные, хотя...
Обновление #3
Благодаря комментарию ILMTitan:
@Adam Paynter: Ваш выделенный жирным текст не имеет значения, потому что это только случай когда два метода являются эквивалентны по переопределению, что, как показал Дэн. это не тот случай. Таким образом, определяющим фактором должно быть то, учитывает ли JLS принимает ли JLS во внимание общие типы при определении наиболее специфического метода. - ILMTitan
Что ж, если я правильно понимаю третий пункт в первом списке из раздела 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; } ...
и сигнатуры совпадают, значит, есть коллизия, и код не должен компилироваться.
Также работает (на этот раз с 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);
}
}
--- Отредактировано в ответ на комментарии ниже ---
Хорошо, значит, это допустимая Java, но не должна быть. Ключ в том, что на самом деле он не полагается на возвращаемый тип, а на стертый параметр Generics.
Это не сработает для нестатических методов и явно запрещено для нестатических методов. Попытка сделать это в классе не удалась бы из-за дополнительных проблем, во-первых, типичный класс не final , как класс Class .
Это несоответствие во всем остальном довольно последовательном языке. Я рискну и скажу, что это должно быть незаконным, даже если это технически разрешено. На самом деле это ничего не добавляет к удобочитаемости языка и мало что добавляет к способности решать значимые проблемы. Единственная значимая проблема, которую он, кажется, решает, заключается в том, достаточно ли вы знакомы с языком, чтобы знать, когда его основные принципы , по-видимому, нарушаются внутренними несогласованностями языка в устранении стирания типов, обобщений и результирующих сигнатур методов. .
Определенно кода следует избегать, поскольку решить одну и ту же проблему любым количеством более значимых способов тривиально, и единственное преимущество состоит в том, чтобы увидеть, знает ли рецензент / расширитель пыльный грязный уголок спецификации языка.
--- Исходное сообщение следует ---
Хотя компиляторы могли разрешить это, ответ все равно отрицательный.
Стирание превратит List
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.
Она действительна в соответствии со спецификацией.
Подпись метода
m1
является подписью подписи методаm2
, если либо
m2
имеет ту же подпись, что иm1
, либоподпись
m1
является такой же, как и стертая подписьm2
.
Таким образом, это не подсигнатуры друг друга, потому что стирание List
не является List
и наоборот.
Две сигнатуры методов
m1
иm2
являются эквивалентны, если либоm1
является подпризнакm2
илиm2
является подпризнаком подпризнакомm1
.
Таким образом, эти два выражения не являются эквивалентными при перегрузке (обратите внимание на iff). А правило перегрузки гласит:
Если два метода класса (независимо от того. оба объявлены в одном классе, или оба наследуются классом, или один объявленный и один унаследованный) имеют одинаковое имя, но сигнатуры не эквивалентными, то метод считается перегруженным.
Следовательно, эти два метода перегружены, и все должно работать.
Насколько я могу судить, файл .class может содержать оба метода, поскольку дескриптор метода содержит параметры, а также тип возвращаемого значения. Если бы тип возвращаемого значения был таким же, то дескрипторы были бы такими же, и методы были бы неразличимы после стирания типа (следовательно, он не работает и с void). http://java.sun.com/docs/books/jvms/second_edition/html/ClassFile.doc.html#7035
Теперь для вызова метода с invoke_virtual требуется дескриптор метода, поэтому вы можете скажите, какой из методов вы хотите вызвать, поэтому кажется, что все эти компиляторы, которые все еще имеют общую информацию, просто помещают дескриптор для метода, который соответствует универсальному типу параметра, поэтому он жестко закодирован в байт-коде, который метод для вызова (в зависимости от их дескрипторов или, точнее, типа возвращаемого значения в этих дескрипторах), даже если параметр теперь является списком, без общей информации.
Хотя я нахожу эту практику немного сомнительной, я должен сказать, что мне нравится то, что вы можете это делать, и я считаю, что дженерики должны были быть разработаны для того, чтобы они могли работать таким образом (да, я знаю, что это создало бы проблемы с обратной совместимостью).
Похоже, компилятор выбирает наиболее конкретный метод на основе универсальных шаблонов.
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
Вывод типа 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