Подтвердить что ты не робот

Почему генерируется тип возвращаемого типа, который был удален, когда есть непроверенное преобразование параметра метода в Java 8?

Рассмотрим следующий пример кода:

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List list = new ArrayList<Integer>();
        String response = getProducer(list).get();
    }
    static Producer<String> getProducer(List<Integer> list) {
        return new Producer<String>();
    }
}
class Producer<T> {
    T get() {
        return null;
    }
}

При компиляции в Java 7 он просто выводит ожидаемое предупреждение для getProducer(list):

Предупреждение: (7, 39) java: unchecked conversion требуется: java.util.List<java.lang.Integer>найдено: java.util.List

Однако при компиляции в Java 8 он вызывает следующую ошибку для назначения response = getProducer(list).get():

Ошибка: (7, 48) java: несовместимые типы: java.lang.Object не может быть преобразован в java.lang.String

Похоже, что тип, возвращаемый из getProducer(list), не является Producer<String>, но стирается Producer (что также подтверждается с помощью функции "extract variable" в среде IDE). Это очень озадачивает, потому что метод getProducer всегда возвращает Producer<String>.

Как ни странно, это можно было бы устранить, избегая непроверенного преобразования при вызове метода getProducer, либо:

  • Измените тип параметра getProducer с List<Integer> на List
  • Измените тип переменной List с List на List<Integer>

Обновление

  • Java используется Oracle JDK 1.8.0_40
  • Я также попытался использовать исходные и целевые параметры с 1.5 по 1.7 с компилятором Java 8, и результат был таким же.

Вопросы

  • Как общий тип переданного аргумента влияет на общий тип метода возвращаемое значение, в то время как общий тип возвращаемого значения фиксируется в сигнатуре метода?
  • Почему существует такое отстало-несовместимое изменение в поведении между Java 7 и Java 8?
4b9b3361

Ответ 1

Это похоже на известную проблему совместимости, описанную здесь здесь и здесь.

Из второй ссылки:

Следующий код, скомпилированный с предупреждениями в JDK 7, не будет скомпилировать в JDK 8:

import java.util.List;
class SampleClass {

     static class Baz<T> {
         public static List<Baz<Object>> sampleMethod(Baz<Object> param) {
             return null;
         }
     }

     private static void bar(Baz arg) {
         Baz element = Baz.sampleMethod(arg).get(0);
     }
}

Компиляция этого кода в JDK 8 вызывает следующую ошибку:

SampleClass.java:12: ошибка: несовместимые типы: объект не может быть преобразован в Баз

Baz element = Baz.sampleMethod(arg).get(0);

Примечание. SampleClass.java использует непроверенные или небезопасные операции. Заметка: Перекомпиляция с -Xlint: непроверенная для деталей. 1 ошибка

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

List list = new ArrayList<Integer>();

с

List<Integer> list = new ArrayList<Integer>();

который не приведет к стиранию типа из возвращаемого типа метода getProducer(List<Integer> list)

Цитата из второй ссылки снова:

В этом примере исходный тип передается в sampleMethod(Baz<Object>), который применяется подтипированием (см. JLS, Java SE 7 Edition, раздел 15.12.2.2). Непроверенный необходимо, чтобы метод был применим, поэтому его возвращение тип удаляется (см. JLS, Java SE 7 Edition, раздел 15.12.2.6). В в этом случае тип возврата sampleMethod(Baz<Object>) равен java.util.List вместо java.util.List<Baz<Object>> и, следовательно, возвращаемый тип get(int) равен Object, который не совместим с назначением с Baz.

Ответ 2

Давайте рассмотрим спецификацию языка Java:

Java 8

15.12.2.6. Тип вызова метода

...

  • Если выбранный метод не является общим, то:

    • Если для применимости метода необработанное преобразование было необходимым, типы параметров типа вызова - это типы параметров типа метода, а тип возвращаемого типа и типы сброса задаются стираниями типа возвращаемого типа и типов типа метода.

    ...

Java 7

15.12.2.6. Метод Результат и типы бросков

Тип результата выбранного метода определяется следующим образом:

  • Если выбранный метод объявлен с типом возвращаемого значения void, тогда результат будет недействительным.

  • В противном случае, если для применения метода необработанное преобразование было необходимо, тогда тип результата - это стирание (§4.6) метода, объявленного возвращаемым типом.

Важно понимать, что означает "общий метод":

Ссылка

8.4.4. Общие методы

Метод является общим, если он объявляет одну или несколько переменных типа (§4.4).

Другими словами, метод

static Producer<String> getProducer(List<Integer> list)

имеет общие параметры и возвращаемые типы, но не является общим, поскольку он не объявляет переменные типа.

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

Поэтому его старший компилятор нарушает спецификацию с использованием типа возврата Producer<String> вместо стирания Producer.

Ответ 3

Реальный вопрос: почему вы можете использовать сырой тип? Для обратной совместимости. Если это для обратной совместимости, предполагается, что предполагается использование только сырых типов.

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

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

Почему существует такое отсталое несовместимое изменение в поведении между Java7 и Java8?

Так как Java 1.4 больше не поддерживается и не был на какое-то время, аргумент обратной совместимости не выдерживает столь сильно и имеет смысл давать исходные типы места на текущем языке.

Ответ 4

Думаю, рассуждения идут так, фокусируясь на совместимости с "миграцией" -

Если вызов предоставляет аргумент raw-типа параметру метода общего типа, весьма вероятно, что код вызова является пред-генерическим кодом, а объявление метода, которое также использовалось для предварительного генерирования, с тех пор "развился", чтобы использовать общие типы.

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

Более сложное решение может быть слишком сложным для JLS, например - как мы делаем вывод типа, если есть аргументы типа raw.


Сегодня это предположение больше не может быть выполнено - скорее всего, вызов является пост-генерическим кодом, который по разным причинам все еще использует необработанный тип. Лучше "скорректировать" исходный тип на раннем этапе

List list0 = ...; // got it from somewhere, possibly from an old API

@SuppressWarnings("unchecked")
List<Integer> list = list0;