Статьи

noException в потоковой операции

Эта статья о некоторой простой практике кодирования. Ничего особенного. Это также обсуждается на StackOverflow .

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

Скажем, вы хотите конвертировать строки в IP-адреса.

1
2
3
4
5
6
7
private static final String[] allowed = {"127.0.0.1", "::1"};
 
...
 
Arrays.stream(allowed)
      .map(InetAddress::getByName)
      .collect(Collectors.toSet());

Проблема в том, что getByName(String host) UnknownHostException . Это не RuntimeException поэтому его нужно проверить, но метод map() нуждается в Function в качестве аргумента, и Function не выдает никаких исключений. Нам нужна версия getByName , которая не генерирует исключения (или нам нужно использовать другой язык, более хромой с исключениями).

1
2
3
4
5
6
7
8
Arrays.stream(allowed)
       .map(s -> {
                   try {
                     return InetAddress.getByName(s);
                     } catch (UnknownHostException e) {
                     throw new RuntimeException(e);
                     }
                 }).collect(Collectors.toSet());

Это просто уродливее и грязнее, чем было в оригинальном цикле. Может ли это попытаться / поймать любую вещь, помещенную в служебный класс, и вызвать какой-то слабый статический метод, который оборачивает реальный вызов? Вроде да. Статически импортируйте следующий метод:

01
02
03
04
05
06
07
08
09
10
11
public interface ExceptionalSupplier<T> {
        T apply() throws Exception;
    }
...
    public static <T> T lame(ExceptionalSupplier<T> z) {
        try {
            return z.apply();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

После импорта вы можете написать

1
2
3
Arrays.stream(allowed)
      .map(s -> lame(() -> InetAddress.getByName(s)))
      .collect(Collectors.toSet());

подвох в том, что вы не можете просто lame( ... ) на вызов. Вы должны превратить его в исключительного поставщика. Функциональный интерфейс, который похож на Supplier но допускает исключения.

Все еще не идеал. (Ну, это Java, так что вы ожидали?) Хорошо. Это Java, но все еще можно сделать лучше. Что если вместо преобразования выражения через поставщика в выражение, которое не вызывает исключение, мы можем преобразовать «функцию», которая выбрасывает исключение, в функцию, которая не вызывает исключение. Нам нужен метод, который принимает исключительную функцию и возвращает нормальную функцию. Таким образом мы можем сохранить шум () -> в нашем коде. Удобочитаемость рулез.

01
02
03
04
05
06
07
08
09
10
11
12
13
public interface ExceptionalFunction<T, R> {
        R apply(T r) throws Exception;
    }
...
    public static <T, R> Function<T, R> lame(ExceptionalFunction<T, R> f) {
        return (T r) -> {
            try {
                return f.apply(r);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        };
    }

С этой утилитой «окончательное» выражение будет

1
2
3
4
Collection<InetAddress> allowedAddresses =
        Arrays.stream(allowed)
              .map(lame(InetAddress::getByName))
              .collect(Collectors.toSet());

Фактический служебный класс в GIST определяет WrapperException расширяющий RuntimeException так что вы можете перехватить исключение где-нибудь в методе, например

1
2
3
4
5
6
public myMethod() throws IOException {
try{
    ... do whatever here we do ...
   } catch (RuntTimeExceptionWrapper.WrapperException we) {
       throw (IOException) we.getCause();
   }

Таким образом, метод сгенерирует исключение, но если где-то есть еще одно RuntimeException которое будет выбрасывать uncauched.

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