Почему этот общий код компилируется в java 8?

Я наткнулся на кусок кода, который заставляет меня задаться вопросом, почему он успешно компилируется:

public class Main { public static void main(String[] args) { String s = newList(); // why does this line compile? System.out.println(s); } private static <T extends List> T newList() { return (T) new ArrayList(); } } 

Интересно, что если я модифицирую подпись метода newList с помощью <T extends ArrayList> он больше не работает.

Обновление после комментариев и ответов: если я переведу общий тип из метода в class, код больше не компилируется:

 public class SomeClass<T extends List> { public void main(String[] args) { String s = newList(); // this doesn't compile anymore System.out.println(s); } private T newList() { return (T) new ArrayList(); } } 

Если вы объявляете параметр типа при методе, вы позволяете вызывающему пользователю выбрать для него фактический тип, если этот фактический тип будет соответствовать ограничениям. Этот тип не обязательно должен быть конкретным конкретным типом, это может быть абстрактный тип, переменная типа или тип пересечения, в других, более разговорных словах, гипотетический тип. Итак, как сказал Mureinik , может существовать тип, расширяющий String и реализующий List . Мы не можем вручную указать тип пересечения для вызова, но мы можем использовать переменную типа, чтобы продемонстрировать логику:

 public class Main { public static > void main(String[] args) { String s = Main.newList(); System.out.println(s); } private static > T newList() { return (T) new ArrayList(); } } 

Конечно, newList() не может выполнить ожидание возврата такого типа, но это проблема определения (или реализации) этого метода. Вы должны получить предупреждение «unchecked» при ArrayList в T Единственно возможная правильная реализация будет возвращать значение null здесь, что делает этот метод совершенно бесполезным.

Точка, повторяющая первоначальный оператор, заключается в том, что вызывающий объект общего метода выбирает фактические типы для параметров типа. Напротив, когда вы объявляете общий class, например, с

 public class SomeClass> { public void main(String[] args) { String s = newList(); // this doesn't compile anymore System.out.println(s); } private T newList() { return (T) new ArrayList(); } } 

параметр типа является частью контракта classа, поэтому любой, кто создает экземпляр, будет выбирать фактические типы для этого экземпляра. Основной метод экземпляра является частью этого classа и должен подчиняться этому контракту. Вы не можете выбрать T , которого хотите; фактический тип для T был установлен, и в Java вы обычно не можете даже узнать, что такое T

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

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

 public class SomeClass> { public > void main(String[] args) { String s = new SomeClass().newList(); System.out.println(s); } private T newList() { return (T) new ArrayList(); } } 

Здесь создатель нового экземпляра выбирает фактические типы для этого экземпляра. Как сказано, этот фактический тип не должен быть конкретным.

Я предполагаю, что это потому, что List – это интерфейс. Если мы проигнорируем тот факт, что String является final на секунду, теоретически вы можете иметь class, который extends String (что означает, что вы можете назначить его s ), но implements List (что означает, что он может быть возвращен из newList() ). После того, как вы измените тип возврата из интерфейса ( T extends List ) в конкретный class ( T extends ArrayList ), компилятор может определить, что они не могут быть назначены друг другу и создает ошибку.

Это, конечно же, ломается, поскольку String , по сути, является final , и мы могли ожидать, что компилятор учтет это. ИМХО, это ошибка, хотя я должен признать, что я не специалист по компилятору, и может быть веская причина проигнорировать final модификатор на данный момент.

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

Таким образом, newList() является общим методом, он имеет один параметр типа. Если вы укажете этот параметр, компилятор проверит это для вас:

Не удалось скомпилировать:

 String s = Main.newList(); // this doesn't compile anymore System.out.println(s); 

Пропускает этап компиляции:

 List l = Main.>newList(); // this compiles and works well System.out.println(l); 

Указание параметра типа

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

Параметр типа при создании экземпляра

Наиболее распространенный случай – указать шаблоны для экземпляра объекта. Т.е. для списков:

 List list = new ArrayList<>(); 

Здесь мы видим, что List указывает тип элементов списка. С другой стороны, новый ArrayList<>() не работает. Вместо этого он использует алмазный оператор . Т.е. компилятор java вводит тип, основанный на объявлении.

Неявный параметр типа при вызове метода

Когда вы вызываете статический метод, вы должны указать тип по-другому. Иногда вы можете указать его как параметр:

 public static  T max(T n1, T n2) { if (n1.doubleValue() < n2.doubleValue()) { return n2; } return n1; } 

Вы можете использовать его так:

 int max = max(3, 4); // implicit param type: Integer 

Или вот так:

 double max2 = max(3.0, 4.0); // implicit param type: Double 

Явные параметры типа при вызове метода:

Скажем, например, таким образом вы можете создать пустой список типов:

 List noIntegers = Collections.emptyList(); 

Параметр type передается методу emptyList() . Единственное ограничение состоит в том, что вы также должны указать class. Т.е. вы не можете этого сделать:

 import static java.util.Collections.emptyList; ... List noIntegers = emptyList(); // this won't compile 

Маркер типа времени выполнения

Если ни один из этих трюков не поможет вам, вы можете указать токен типа времени выполнения . Т.е. вы предоставляете class в качестве параметра. Общим примером является EnumMap :

 private static enum Letters {A, B, C}; // dummy enum ... public static void main(String[] args) { Map map = new EnumMap<>(Letters.class); } 
  • неверное преобразование из 'const char *' в 'char'
  • Возвращать определенный тип внутри Haskell
  • Android java.exe закончил с ненулевым значением выхода 1
  • Определение манифеста размещенной сборки не соответствует ссылке на сборку
  • «Нестатический метод не может ссылаться на статический контекст»
  • Ошибка компилятора: «Инициализатор не является константой времени компиляции»
  • Почему вектор :: ссылка не возвращает ссылку на bool?
  • Неявная проблема преобразования в тройном состоянии
  • Методы расширения должны быть определены в неэквивалентном статическом classе
  • 'Delegate' System.Action 'не принимает 0 аргументов.' Является ли это ошибкой компилятора C # (lambdas + два проекта)?
  • Конструктор Java не компилируется должным образом
  • Давайте будем гением компьютера.