Статьи

Java 12: сопоставление с выражениями коммутатора

В этой статье мы рассмотрим новую функцию Java 12 «Выражения переключателя» и то, как ее можно использовать в сочетании с
Операция Stream::map и некоторые другие операции Stream. Узнайте, как вы можете улучшить свой код с помощью потоков и выражений переключателей.

Переключатель выражений

Java 12 поставляется с поддержкой «предварительного просмотра» «Switch Expressions». Выражение Switch позволяет операторам switch возвращать значения напрямую, как показано ниже:

1
2
3
4
5
6
7
public String newSwitch(int day) {
    return switch (day) {
        case 2, 3, 4, 5, 6 -> "weekday";
        case 7, 1 -> "weekend";
        default -> "invalid";
    } + " category";
}

Вызов этого метода с 1 вернет «категорию выходного дня».

Это здорово и делает наш код короче и лаконичнее. Нам не нужно беспокоиться о возможных проблемах, блоках, изменяемых временных переменных или пропущенных случаях / дефолтах, которые могут иметь место для переключателя good ole. Просто посмотрите на этот соответствующий старый пример коммутатора, и вы поймете, что я имею в виду:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
public String oldSwitch(int day) {
    final String attr;
    switch (day) {
        case 2,3,4,5,6: {
            attr = "weekday";
            break;
        }
        case  7, 1: {
            attr = "weekend";
            break;
        }
        default: {
            attr = "invalid";
        }
    }
    return attr + " category";
}

Переключатель выражений является функцией предварительного просмотра

Чтобы заставить Switch Expression работать под Java 12, мы должны передать
“--enable-preview” как аргумент командной строки, когда мы компилируем и запускаем наше приложение. Это оказалось немного сложнее, но, надеюсь, станет легче с выпуском новых версий IDE и / или / если Java включит эту функцию в качестве полностью поддерживаемой функции. Пользователи IntelliJ должны использовать версию 2019.1 или новее.

Переключение выражений в Stream :: map

Выражения-переключатели очень просты в использовании в операторах Stream::map , особенно по сравнению со старым синтаксисом-переключателем. В приведенных ниже примерах я использовал ORM Speedment Stream и примерную базу данных Sakila . База данных Sakila — это все о фильмах, актерах и так далее.

Вот поток, который декодирует идентификатор языка фильма ( short ) в полное имя языка ( String ), используя map() в сочетании с выражением Switch:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
public static void main(String... argv) {
 
    try (Speedment app = new SakilaApplicationBuilder()
        .withPassword("enter-your-db-password-here")
        .build()) {
 
        FilmManager films = app.getOrThrow(FilmManager.class);
 
        List<String> languages = films.stream()
            .map(f -> "the " + switch (f.getLanguageId()) {
                case 1 -> "English";
                case 2 -> "French";
                case 3 -> "German";
                default -> "Unknown";
            } + " language")
            .collect(toList());
 
        System.out.println(languages);
    }
}

Это создаст поток из всех 1000 фильмов в базе данных, а затем сопоставит каждый фильм с именем соответствующего языка и соберет все эти имена в список. Выполнение этого примера приведет к следующему выводу (сокращенно для краткости):

[английский язык, английский язык,…]

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

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<String> languages = films.stream()
            .map(f -> {
                final String language;
                switch (f.getLanguageId()) {
                    case 1: {
                        language = "English";
                        break;
                    }
                    case 2: {
                        language = "French";
                        break;
                    }
                    case 3: {
                        language = "German";
                        break;
                    }
                    default: {
                       language = "Unknown";
                    }
                }
                return "the " + language + " language";
            })
            .collect(toList());
        ...

Или, может быть, что-то вроде этого:

01
02
03
04
05
06
07
08
09
10
11
12
...
        List<String> languages = films.stream()
            .map(f -> {
                switch (f.getLanguageId()) {
                    case 1: return "the English language";
                    case 2: return "the French language";
                    case 3: return "the German language";
                    default: return "the Unknown language";
                }
            })
            .collect(toList());
         ...

Последний пример короче, но дублирует логику.

Переключение выражений в Stream :: mapToInt

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

01
02
03
04
05
06
07
08
09
10
11
12
IntSummaryStatistics statistics = films.stream()
    .mapToInt(f -> switch (f.getRating().orElse("Unrated")) {
        case "G", "PG" ->  0;
        case "PG-13"   ->  1;
        case "R"       ->  2;
        case "NC-17"   ->  5;
        case "Unrated" -> 10;
        default -> 0;
    })
    .summaryStatistics();
 
 System.out.println(statistics);

Это даст следующий результат:

1
IntSummaryStatistics{count=1000, sum=1663, min=0, average=1.663000, max=5}

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

01
02
03
04
05
06
07
08
09
10
11
12
IntSummaryStatistics statistics = films.stream()
    .mapToInt(f -> {
        switch (f.getRating().orElse("Unrated")) {
            case "G": case "PG": return 0;
            case "PG-13":   return 1;
            case "R":       return 2;
            case "NC-17":   return 5;
            case "Unrated": return 10;
            default: return 0;
        }
    })
   .summaryStatistics();

Переключение выражений в Stream :: collect

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
Map<Integer, Long> ageMap = films.stream()
     .collect(
         groupingBy( f -> switch (f.getRating().orElse("Unrated")) {
                 case "G", "PG" -> 0;
                 case "PG-13"   -> 13;
                 case "R"       -> 17;
                 case "NC-17"   -> 18;
                 case "Unrated" -> 21;
                 default -> 0;
             },
             TreeMap::new,
             Collectors.counting()
          )
      );
 
System.out.println(ageMap);

Это даст следующий результат:

1
{0=372, 13=223, 17=195, 18=210}

Предоставляя (необязательно) поставщика TreeMap::new Map TreeMap::new , мы получаем возраст в отсортированном порядке. Почему PG-13 можно увидеть с 13 лет, а NC-17 — с 17, а с 18 лет — загадочно, но выходит за рамки этой статьи.

Резюме

Я с нетерпением жду, чтобы функция Switch Expressions была официально включена в Java. Выражения-переключатели иногда могут заменять лямбда-выражения и ссылки на методы для многих типов потоковых операций.

См. Оригинальную статью здесь: Java 12: Отображение с помощью выражений коммутатора.

Мнения, высказанные участниками Java Code Geeks, являются их собственными.