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

}

В моем понимании теории Java говорится: нет!

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

Ответ 1

Это зависит от того, как вы хотите называть эти методы. Если вы хотите вызвать эти методы из другого исходного кода 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 предполагает, что это действительно может быть правильным исходным кодом Java. На стр. 449 (§15.12 Выражения вызова метода) мы видим следующее:

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

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

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

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

Благодаря комментарию ILMTitan:

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

Ответ 2

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

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

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

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

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

--- Исходное сообщение следует ---

Пока компиляторы могли его разрешить, ответа по-прежнему нет.

Erasure превратит как List <String> и List <Integer> в неукрашенный Список. Это означает, что оба метода "f" будут иметь одну и ту же подпись, но разные типы возврата. Тип возврата не может использоваться для дифференциации методов, потому что это произойдет, когда вы вернетесь в общий супертип; как:

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

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

Ответ 3

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

Вот почему: это функция.

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

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

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

Ответ 4

Он действителен в спецификации .

Подпись метода m1 является подсигнала подписи метод m2, если

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

  • подпись m1 такая же, как стирание сигнатуры m2.

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

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

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

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

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

Ответ 5

Хорошо, если я правильно понял точку с тремя точками в первом списке из раздела 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;
    }
...

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

Ответ 6

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

}

Ответ 7

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

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

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

Ответ 8

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

Вывод типа Java разбит: как его исправить?

Ответ 9

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

Ответ 10

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

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