Конечное поле с пометкой @NotNull не инициализировано

У меня есть этот код:

public static class MyWebDriver extends RemoteWebDriver {
    @NotNull
    private final String nodeId;

    public MyRemoteWebDriver(@NotNull String nodeId) {
        super();
        this.nodeId = nodeId;
    }

    @Override
    public void quit() {
        System.out.println("deleting node: " + nodeId);
    }
}

и он гарантировал, что nodeId, передаваемый в конструктор, не null. И поскольку поле nodeId final, я ожидаю, что оно будет инициализировано в моем методе quit().

Но в конструкторе super() есть блок try-catch, который в случае исключения вызывает метод quit() и генерирует исключение. И в этом случае nodeId, который я получаю в моем методе quit(), не инициализирован (имеет значение null).

Есть ли способы избежать этого, кроме

@Override
public void quit() {
    if (nodeId != null) {
        System.out.println("deleting node: " + nodeId);
    }
}

этот? Это выглядит довольно глупо, потому что nodeId помечен как @NotNull.

Ответ 1

Но в конструкторе super() есть блок try-catch, который в случае исключения вызывает quit()

Это две проблемы в одном:

  • Конструкторы
  • не должны выполнять какую-либо работу, кроме сохранения заданных параметров в переменных (final).

    [править]

    Означает ли это, что классы не должны проверять свои входы? Я знаю многих людей, которые не согласятся с тем, что все их объекты могут оказаться недействительными, а не вообще отсутствовать. - chris

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

    Конструкторы
  • никогда не должны ссылаться на методы, отличные от private и/или final (внутри класса), прямо или косвенно (т.е. вы не должны вызывать метод final, который, в свою очередь, вызывает не final).


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

Что бы вы делали в конструкторе суперклассов, скорее всего, должно быть сделано в отдельном классе, и только результаты этого процесса должны быть переданы в ваш класс (и его суперкласс).

Это, очевидно, означает, что метод quit() также относится к другому классу.

Ответ 2

Это, конечно, легко объясняется тем, как работает Java. Объект типа MyWebDriver не инициализируется, включая его поля, то есть в вашем случае nodeId, пока его поля, унаследованные от суперкласса, не будут инициализированы, то есть в вашем случае до тех пор, пока super() не вернется.

Итак, если выбрано исключение в super, ясно, что nodeId будет null.

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

Ответ 3

Вы можете переместить логику инициализации из супер-конструктора в защищенный метод init() и вызвать его после инициализации nodeId.

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

Ответ 4

Вы должны изменить bean за RemoteWebDriver, добавив к quit это булевское сравнение. Или даже лучше, вы не должны вызывать какие-либо методы в вашем конструкторе, а только создавать сами настройки экземпляра. После этого оценивается значение новой переменной экземпляра boolean isItFullyConstructed, а если false, вызовите исходную логику try/catch, которая вызывает проблемы. Это выполнимо?