Почему javac не может вызывать общие аргументы типа для функций, используемых в качестве аргументов? - программирование
Подтвердить что ты не робот

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

В следующем примере почему компилятор может вывести общие аргументы для первого вызова Foo.create() в Foo.test(), но не может сделать это во втором? Я использую Java 6.

public class Nonsense {
    public static class Bar {
        private static void func(Foo<String> arg) { }
    }

    public static class Foo<T> {

        public static <T> Foo<T> create() {
            return new Foo<T>();
        }

        private static void test() {
            Foo<String> foo2 = Foo.create(); // compiles
            Bar.func(Foo.create());          // won't compile
            Bar.func(Foo.<String>create());  // fixes the prev line
        }
    }
}

(Ошибка компиляции - метод func (Nonsense.Foo) в типе Nonsense.Bar не применим для аргументов (Nonsense.Foo)).

Примечание. Я понимаю, что ошибка компилятора может быть исправлена ​​третьей строкой в ​​test(). Мне любопытно, существует ли определенное ограничение, которое мешает компилятору вывести тип. Мне кажется, что здесь достаточно контекста.

4b9b3361

Ответ 1

Как и в случае с Java 7, разрешение перегрузки метода должно выполняться до того, как любая информация о целевом типе от метода, который вы вызываете, может быть принята во внимание, чтобы попытаться вывести переменную типа T в объявление func. Это кажется глупым, поскольку мы все можем видеть, что в этом случае существует один и только один метод с именем func, однако он задан JLS ​​и является поведением javac из Java 7.

Компиляция выполняется следующим образом: во-первых, компилятор видит, что он компилирует вызов статического метода класса Bar с именем func. Чтобы выполнить разрешение перегрузки, он должен выяснить, с какими параметрами вызывается метод. Несмотря на то, что это тривиальный случай, он все равно должен это делать, и пока он не сделал этого, у него нет никакой информации об официальных параметрах метода, доступных для его помощи. Фактические параметры состоят из одного аргумента, вызов Foo.create(), который объявляется как возвращающий Foo<T>. Опять же, без критериев из целевого метода, можно только вывести, что возвращаемый тип - это стирание Foo<T>, которое Foo<Object>, и оно делает это.

Разрешение перегрузки метода не выполняется, так как ни одна из перегрузок func не совместима с фактическим параметром Foo<Object>, и при этом испускается ошибка.

Это, конечно, очень неудачно, так как мы все можем видеть, что если информация может просто перетекать в другом направлении, от цели метода обращается к сайту вызова, тип может быть легко выведен и не будет ошибка. И на самом деле компилятор в Java 8 может сделать именно это и делает. Как было сказано в другом ответе, этот более богатый тип ввода очень полезен для lambdas, которые добавляются в Java 8, и к расширениям Java API, которые используются для использования lambdas.

Вы можете загрузить предварительную версию Java 8 с JSR 335 lambdas из предыдущей ссылки. Он компилирует код в вопросе без каких-либо предупреждений или ошибок.

Ответ 2

Вывод типов из контекста слишком сложный. Главным препятствием является, вероятно, перегрузка метода. Например, f(g(x)), чтобы определить, к какому f() применить, нам нужно знать тип g(x); но тип g(x), возможно, должен быть выведен из типов параметров f(). На некоторых языках перегрузка метода просто запрещена, так что вывод типа может быть проще.

В Java 8 ваш пример компилируется. Команда Java больше заинтересована в расширении вывода типа из-за использования случаев использования лямбда-выражения. Это непростая задача.

Спецификация java-языка для java 7 содержит 40 страниц только для выражения вызова метода spec (раздел 15.12)