В чем смысл Guava checkNotNull


Я довольно новичок в Guava (давайте будем честными, я не "довольно новый", я полный новичок на эту тему), и поэтому я решил пройти через некоторую документацию и был очень удивлен, читая это:

com.google.common.base.Preconditions.checkNotNull(...)

Я не понимаю смысла этого метода. Это означает, что вместо того, чтобы делать :

myObject.getAnything();

(что может привести к NullPointerException Если myObject равен null)

Я должен используйте

checkNotNull(myObject).getAnything();

, который будет бросить NullPointerException Если myObject имеет значение null и возвращает myObject если это не null.

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

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

Я даже не думаю, что последнее является более читаемым.

значит, я что-то упустил. Что это?

3 52

3 ответа:

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

public class Foo {
    private final String s;

    public Foo(String s) {
        this.s = s;
    }

    public int getStringLength() {
        return s.length();
    }
}

допустим, вы не хотите разрешать нулевые значения для s. (или же getStringLength будет бросать NPE). С классом как есть, к тому времени, когда вы поймаете это null, уже слишком поздно -- очень трудно выяснить, кто его туда положил. Преступник вполне может быть в совершенно другом классе, и это Foo экземпляр мог быть построен уже давно. Теперь вам нужно прочесать свою базу кода, чтобы узнайте, кто мог бы поставить null есть значение.

вместо этого представьте себе этот конструктор:

public Foo(String s) {
    this.s = checkNotNull(s);
}

теперь, если кто-то ставит null там, вы узнаете сразу -- и у вас будет трассировка стека, указывающая вам точно на вызов, который пошел не так.


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

public class StringLengthAverager {
    private int stringsSeen;
    private int totalLengthSeen;

    public void accept(String s) {
        stringsSeen++;
        totalLengthSeen += s.length();
    }

    public double getAverageLength() {
        return ((double)totalLengthSeen) / stringsSeen;
    }
}

вызов accept(null) приведет к тому, что NPE будет брошен - но не раньше stringsSeen был увеличен. Это может быть не то, что вы хотите; как пользователь класса, я могу ожидать, что если он не принимает null, то его состояние должно быть неизменным, если вы передаете null (другими словами: вызов должен завершиться неудачей, но он не должен аннулировать объект). Очевидно, что в этом примере вы также можете исправить это, получив s.length() перед приращение stringsSeen, но вы можете увидеть, как для более длинного и более сложного метода может быть полезно сначала проверить, что все ваши аргументы действительны, и только затем изменить состояние:

    public void accept(String s) {
        checkNotNull(s); // that is, s != null is a precondition of the method

        stringsSeen++;
        totalLengthSeen += s.length();
    }

myObject.getAnything(); (что может вызвать исключение NullPointerException, если myObject равен null)

нет... это будет бросьте NPE всякий раз, когда myObject == null. В Java нет возможности вызвать метод с помощью null приемник (теоретическим исключением являются статические методы, но они могут и должны быть всегда вызывается без объекта).


Я должен использовать checkNotNull(myObject).getAnything();

нет, вы не должны. Это было бы скорее избыточный (обновление).

вы должны использовать checkNotNull для того чтобы не быстро. Без него вы можете пройти нелегальный null другой метод, который передает его дальше, и так далее и так далее, где он окончательно терпит неудачу. Тогда вам может понадобиться некоторая удача, чтобы узнать, что на самом деле самый первый метод должен был отказаться null.


в ответе yshavit упоминается важный момент: передача незаконного значения плоха, но хранение его и передав его позже еще хуже.

обновление

на самом деле

 checkNotNull(myObject).getAnything()

также имеет смысл, поскольку вы четко выражаете свое намерение не принимать никаких нулей. Без него, кто-то может подумать, что вы забыли чек и преобразовать его в нечто вроде

 myObject != null ? myObject.getAnything() : somethingElse

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

 myObject!!.getAnything()                    // checkNotNull
 myObject?.getAnything()                     // safe call else null
 myObject?.getAnything() ?: somethingElse    // safe call else somethingElse

для nullable myObject, в то время как стандартный синтаксис точки будет разрешен только тогда, когда myObject как известно, быть не-null.

Я прочитал всю эту тему несколько минут назад. Тем не менее, я был смущен, почему мы должны использовать checkNotNull. Затем просмотрите предварительный класс doc of Guava, и я получил то, что ожидал. Чрезмерное использование checkNotNull определенно ухудшит производительность.

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

согласно DOC:ссылке

С помощью checkNotNull:

public static double sqrt(double value) {
     Preconditions.checkArgument(value >= 0.0, "negative value: %s", value);
     // calculate the square root
}

предупреждение о производительности

цель этого класса-улучшить читаемость кода, но в некоторых обстоятельства это может привести к значительным затратам на производительность. Помните, что значения параметров для сообщения строительство должно быть все вычисляется с нетерпением, и автобоксинг и создание массива varargs может произойти также, даже когда проверка предварительного условия затем завершается успешно (как и должно почти всегда делаю в производстве). В некоторых обстоятельствах эти впустую Циклы процессора и распределения могут добавить до реальной проблемы. Проверки предварительных условий с учетом производительности всегда можно преобразовать в обычная форма:

if (value < 0.0) {
     throw new IllegalArgumentException("negative value: " + value);
}