Статьи

Предполагаемые исключения в Java

Всегда приятно позаимствовать и украсть концепции и идеи из других языков. Опция Scala — одна из идей, которая мне действительно нравится, поэтому я написал реализацию на Java. Он оборачивает объект, который может быть или не быть нулевым, и предоставляет некоторые методы для работы более функциональным способом. Например, метод isDefined добавляет объектно-ориентированный способ проверки, является ли значение нулевым. Затем он используется в других местах, таких как метод getOrElse, который в основном говорит: «Дайте мне то, что вы упаковываете, или запасной вариант, если он нулевой».

1
2
3
4
public T getOrElse(T fallback)
{
    return isDefined() ? get() : fallback;
}

На практике это заменит традиционную Java, такую ​​как

1
2
3
4
5
6
7
8
9
public void foo()
{
    String s = dao.getValue();
    if (s == null)
    {
        s = 'bar';
    }
    System.out.println(s);
}

с более лаконичным и оо

1
2
3
4
5
public void foo()
{
    Option<String> s = dao.getValue();
    System.out.println(s.getOrElse('bar'));
}

Тем не менее, что, если я хочу сделать что-то другое, кроме получения запасного значения — скажем, выбросить исключение? Если говорить более конкретно, что если я захочу выдать конкретный тип исключения — то есть как специфического в использовании, так и не жестко закодированного в Option? Это требует хитрости и всплеска вывода типа.

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

1
2
3
4
public interface ExceptionFactory <E extends Exception>
{
    E create(String message);
}

Обратите внимание, что <E расширяет исключение> — это ключ к тому, как это работает. Используя фабрику, теперь мы можем добавить новый метод в Option:

01
02
03
04
05
06
07
08
09
10
11
12
public <E extends Exception> T getOrThrow(ExceptionFactory<E> exceptionFactory,
                                          String message) throws E
{
    if (isDefined())
    {
        return get();
    }
    else
    {
        throw exceptionFactory.create(message);
    }
}

Опять же, обратите внимание на броски E — это вывод из фабрики исключений.

И это, хотите верьте, хотите нет, это 90% того, что нужно. Единственное раздражение — необходимость иметь фабрики исключений. Если вы можете пережить это, у вас все готово. Давайте определим пару пользовательских исключений, чтобы увидеть это в действии.

01
02
03
04
05
06
07
08
09
10
11
12
public <E extends Exception> T getOrThrow(ExceptionFactory<E> exceptionFactory,
                                          String message) throws E
{
    if (isDefined())
    {
        return get();
    }
    else
    {
        throw exceptionFactory.create(message);
    }
}

И подозрительно похожее ExceptionB

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
public class ExceptionB extends Exception
{
    public ExceptionB(String message)
    {
        super(message);
    }
 
    public static ExceptionFactory<ExceptionB> factory()
    {
        return new ExceptionFactory<ExceptionB>()
        {
            @Override
            public ExceptionB create(String message)
            {
                return new ExceptionB(message);
            }
        };
    }
}

И, наконец, бросить все это вместе:

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
29
30
31
32
33
34
public class GenericExceptionTest
{
    @Test(expected = ExceptionA.class)
    public void exceptionA_throw() throws ExceptionA
    {
        Option.option(null).getOrThrow(ExceptionA.factory(),
                                       "Some message pertinent to the situation");
    }
 
    @Test
    public void exceptionA_noThrow() throws ExceptionA
    {
        String s = Option.option("foo").getOrThrow(ExceptionA.factory(),
                                                   "Some message pertinent to the situation");
        Assert.assertEquals("foo",
                            s);
    }
 
    @Test(expected = ExceptionB.class)
    public void exceptionB_throw() throws ExceptionB
    {
        Option.option(null).getOrThrow(ExceptionB.factory(),
                                       "Some message pertinent to the situation");
    }
 
    @Test
    public void exceptionB_noThrow() throws ExceptionB
    {
        String s = Option.option("foo").getOrThrow(ExceptionB.factory(),
                                                   "Some message pertinent to the situation");
        Assert.assertEquals("foo",
                            s);
    }
}

Важно отметить, как выделено жирным шрифтом выше, исключение, объявленное в сигнатуре метода, является специфическим — оно не является общим предком (Exception или Throwable). Это означает, что теперь вы можете использовать параметры в своем слое DAO, слое обслуживания, где бы то ни было, и создавать конкретные исключения, где и как вам нужно.

Скачать исходный код : Вы можете получить исходный код и тесты здесь — genex

Примечание
Еще одна интересная вещь, появившаяся при написании этого, — наблюдение, что можно сделать это:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
public void foo()
{
    throw null;
}
 
public void bar()
{
    try
    {
        foo();
    }
    catch (NullPointerException e)
    {
        ...
    }
}

Само собой разумеется, что это не очень хорошая идея.

Ссылка: Предполагаемые исключения в Java от нашего партнера JCG Стива Чалонера в блоге Objectify .