Статьи

Вывод локального типа в Java 10, или если он крякает как утка

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

  • Текущая версия с LTS — Java 8;
  • Предполагается, что Java 9 будет иметь поддержку только до марта 2018 года, поэтому она уже закончена;
  • Java 10 поддерживается до сентября 2018 года;
  • Предполагается, что следующей версией LTS будет Java 11, которая должна быть выпущена в сентябре 2018 года и будет поддерживаться по крайней мере до сентября 2023 года.

Подробнее об этом здесь в Oracle Java SE.

Наша компания до сих пор не приняла Java 9, но теперь кажется, что ее можно полностью пропустить и перейти к следующему LTS. Есть много других проблем, кроме самой Java, например, версии Spring Boot Java и т. Д., Поэтому мы, вероятно, будем действовать осторожно. Тем не менее, ввиду неизбежных изменений, я решил посмотреть, что будет в Java 10. И похоже, что главное в этой версии — это вывод локальных типов .

Мы все знаем этот синтаксис Java:

1
2
3
List<User> list = new ArrayList<User>();
// or since Java 7
List<User> list = new ArrayList<>();

По сути, вывод локального типа — это возможность заменить его следующим:

1
2
// left side type is inferred from the right side and will be ArrayList
var userList = new ArrayList();

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

Локальный вывод типов уже давно существует во многих языках программирования, таких как Scala, C #, Go и, конечно, Kotlin. В этом отношении Java отставала, и теперь решила это исправить. Тем не менее, было некоторое противоречие в отношении конкретной реализации этого. Например, была возможность:

  • иметь val для локальных констант и var для переменных, как в Kotlin или Scala;
  • наличие const или только final для локальных констант и var для переменных, поскольку const и final уже зарезервированы в Java;
  • наличие окончательного var для констант и var для переменных;
  • используя let , def или : = ;
  • и больше об этом здесь.

Наконец, было решено сохранить синтаксис ближе к тому, что уже есть, и разрешить var для локальных переменных и final var для локальных констант, поэтому приведенный выше пример будет работать в Java 10. Однако замена не всегда проста. Например:

1
2
3
4
5
6
7
8
// example 1 - list is a List<User> type
List<User> list = new ArrayList<>();
 
// example 2 - userList is an ArrayList<Object> type, so you lose type information
var userList = new ArrayList<>();
 
// example 3 - userListFixed is an ArrayList<User> type, so you keep type information
var userListFixed = new ArrayList<User>();

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

Одним из примеров, где вывод локального типа полезен, являются преобразования данных. Например, вы хотите изменить объекты одного типа на другой, имеющие разные атрибуты, и использовать для этого анонимный класс. В Java 8 вы могли делать это только внутри области потока, поэтому, например, новые свойства будут доступны вам в потоке, но не снаружи.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
List<User> users = Arrays.asList(
               new User("Elisabeth", "Bennett", 20),
               new User("Jane", "Bennett", 22),
               new User("Mary", "Bennett", 18),
               new User("Kitty", "Bennett", 17),
               new User("Lydia", "Bennett", 15)
       );
       users.stream()
               .map(u ->
                       new Object() {
                           String fullName = u.firstName + " " + u.lastName;
                           boolean canDrink = u.age >= 18;
                       })
               .forEach(u -> {
                   if (u.canDrink) {
                       System.out.println("+ " + u.fullName + " is of age and can drink");
                   } else {
                       System.out.println("- " + u.fullName + " is not of age and cannot drink");
                   }
               });
 
       /*  Output will be
        * + Elisabeth Bennett is of age and can drink
        * + Jane Bennett is of age and can drink
        * + Mary Bennett is of age and can drink
        * - Kitty Bennett is not of age and cannot drink
        * - Lydia Bennett is not of age and cannot drink
        */

В Java 10 вы можете использовать преобразованный список нового объекта вне конвейера потока.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
List<User> users = Arrays.asList(
         new User("Elisabeth", "Bennett", 20),
         new User("Jane", "Bennett", 22),
         new User("Mary", "Bennett", 18),
         new User("Kitty", "Bennett", 17),
         new User("Lydia", "Bennett", 15)
 );
 final var objects = users.stream()
         .map(u ->
                 new Object() {
                     String fullName = u.firstName + " " + u.lastName;
                     boolean canDrink = u.age >= 18;
                 })
         .collect(Collectors.toUnmodifiableList());
 
 // do something with the users...
 
 System.out.println();
 for (var o : objects) {
     if (o.canDrink) {
         System.out.println("+ " + o.fullName + " is of age and can drink");
     } else {
         System.out.println("- " + o.fullName + " is not of age and cannot drink");
     }
 }

Итак, после закрытия потока свойства сопоставленного объекта все еще доступны. Однако, поскольку это локальный тип, вы не сможете использовать их вне локальной области текущего метода. Ключевое слово var не будет работать в объявлении параметров метода, поэтому вы не можете передать переменную в другой метод. Таким образом, наличие var не означает, что Java волшебным образом становится динамически типизированной. Он по-прежнему типизирован статически, только с добавленным кусочком синтаксического сахара, и только в тех местах, где компилятор может определить тип.

Для меня это показывает, что Java стремится двигаться вперед, но ей очень мешают пытаться сохранить исторические корни, делая обратную совместимость, а не инновации своим главным приоритетом.

Опубликовано на Java Code Geeks с разрешения Марины Чернявской, партнера нашей программы JCG . См. Оригинальную статью здесь: Локальный вывод типа в Java 10, или Если он крякает, как утка