Этот допустимый Java?

Этот допустимый 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));
    }

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

Мое понимание теории Java говорит "нет"!

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

59
задан Community 23 May 2017 в 11:47
поделиться

10 ответов

Это зависит от того, как вы хотите вызвать эти методы. Если вы хотите вызвать эти методы из другого исходного кода 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

29
ответ дан 24 November 2019 в 18:32
поделиться

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

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

5
ответ дан 24 November 2019 в 18:32
поделиться

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

}
1
ответ дан 24 November 2019 в 18:32
поделиться

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

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

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

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

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

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

Хотя компиляторы могли разрешить это, ответ все равно отрицательный.

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

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

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

13
ответ дан 24 November 2019 в 18:32
поделиться

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

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

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

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

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

11
ответ дан 24 November 2019 в 18:32
поделиться

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

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

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

  • подпись m1 является такой же, как и стертая подпись m2.

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

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

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

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

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

5
ответ дан 24 November 2019 в 18:32
поделиться

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

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

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

1
ответ дан 24 November 2019 в 18:32
поделиться

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

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
0
ответ дан 24 November 2019 в 18:32
поделиться

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

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

1
ответ дан 24 November 2019 в 18:32
поделиться

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

0
ответ дан 24 November 2019 в 18:32
поделиться
Другие вопросы по тегам:

Похожие вопросы: