Статьи

Известная особенность Java 8: обобщенный вывод целевого типа

Проходя через список возможностей Java 8 , обобщенный вывод типа цели показался мне особенно интересным, менее известным сокровищем. Похоже, что дизайнеры языка Java облегчат некоторые трудности, которые мы испытывали в прошлом с использованием дженериков (Java 5-7). Давайте посмотрим на их пример:

1
2
3
4
5
class List<E> {
  static <Z> List<Z> nil() {..}
  static <Z> List<Z> cons(Z head, List<Z> tail) {..}
  E head() {..}
}

Учитывая приведенный выше пример, функция JEP 101 утверждает, что было бы неплохо иметь возможность написать:

1
2
3
4
5
6
7
// This:
List.cons(42, List.nil());
String s = List.nil().head();
 
// ... instead of this:
List.cons(42, List.<Integer>nil());
String s = List.<String>nil().head();

Будучи опытным разработчиком API , я был рад видеть, что такое улучшение находится на дорожной карте, особенно последней. Что такого захватывающего в этих изменениях? Позвольте мне прокомментировать это более подробно:

01
02
03
04
05
06
07
08
09
10
// In addition to inferring generic types from
// assignments
List<String> l = List.nil();
 
// ... it would be nice for the compiler to be able
// to infer types from method argument types
List.cons(42, List.nil());
 
// ... or from "subsequent" method calls
String s = List.nil().head();

Таким образом, в последнем примере, где методы объединены в цепочку, вывод типа будет отложен до тех пор, пока все выражение присваивания не будет оценено. С левой стороны присваивания компилятор может сделать вывод, что <Z> привязывается к String при вызове head() . Затем эту информацию можно использовать снова, чтобы сделать вывод, что <Z> снова связывается со String при вызове nil() .

Это звучит как большая хитрость для меня, так как оценки AST вызова nil() должны быть отложены до тех пор, пока не будет оценена «зависимая» суб-AST. Это хорошая идея?

Да, это так здорово!

… ты можешь подумать. Поскольку свободный API, такой как jOOQ или Streams API, может быть разработан в гораздо более свободном стиле, задерживая вывод типов до конца цепочки вызовов.

Поэтому я скачал последний оценочный дистрибутив JDK 8, чтобы проверить это с помощью следующей программы:

1
2
3
4
5
6
7
public class InferenceTest {
    public static void main(String[] args) {
        List<String> ls = List.nil();
        List.cons(42, List.nil());
        String s = List.nil().head();
    }
}

Я скомпилировал это и получил:

1
2
3
4
5
6
C:\Users\Lukas\java8>javac InferenceTest.java
InferenceTest.java:5: error: incompatible types:
    Object cannot be converted to String
        String s = List.nil().head();
                                  ^
1 error

Таким образом, вывод типа на основе типа аргумента метода реализован (и, следовательно, компилируется), но не вывод типа для связанных вызовов методов. Я искал объяснение в Интернете и нашел вопрос о переполнении стека, связанный с этой интересной веткой в ​​списке рассылки lambda-dev .

Похоже, что система типов Java стала довольно сложной. Слишком сложно реализовать такие сумасшедшие вещи типа вывода. Но, тем не менее, небольшое улучшение, которое будет очень цениться при ежедневном написании кода Java 8.

И, возможно, в Java 9 мы получим val и var , как и все остальные!