Статьи

NetBeans в классе: время попрощаться с NULL в качестве возвращаемого значения

колледже Доусон в Монреале, Канада. Он также является программным консультантом и по совместительству преподавателем в Школе расширенного обучения при Институте вычислительной техники Университета Конкордия . Он ведет блог на omniprogrammer.com и пишет в Твиттере @omniprof .

Я любил ноль . Когда я преподавал C, а затем C ++, я объяснил своим студентам, как указатели имеют три состояния. Первым состоянием было правильное обращение к объекту в памяти. Вторым был неправильный адрес или адрес мусора, который возникает, когда создается локальный указатель или указатель выходит из области видимости. К сожалению, не было никакого способа отличить действительный адрес от неверного адреса. Тогда было нулевое состояние. Это указатель в ожидании. Еще не указывает на действительный объект и легко распознается как таковой. Я потребовал, чтобы мои ученики инициализировали ссылки локальных указателей на нуль или присвоили им нуль после их удаления. Каждый раз, когда функция возвращала указатель, следующая строка кода после вызова функции должна была прочитать «if (ptr == null) ».

Когда я перешел к обучению Java, указатель исчез. Любой студент, который видит свое первое сообщение об ошибке нулевого указателя, может не согласиться с этим, но я оставлю это другому обсуждению. На месте указателя находится ссылка. Как и указатель C / C ++, он тоже имеет три состояния. Основным усовершенствованием Java является то, что компилятор распознает, что вы пытаетесь использовать недопустимое состояние адреса в левой части выражения, и объявит ошибку.

Это оставляет нам действительный адрес или ноль. Следовательно, как и в случае с C / C ++, программист должен проверить, является ли ссылка нулевой или нет. Это никогда не должно игнорироваться. Для этого достаточно одного метода Java, который пытается получить доступ к значению, например, ArrayList, чья ссылка равна нулю, чтобы закрыть программу.

Чтобы увидеть проблему, давайте посмотрим на этот метод:

public ArrayList findAll() throws SQLException {
        ArrayList rows = null;
        String selectQuery = "SELECT ID, COMMONNAME, LATIN, PH, KH, TEMP, FISHSIZE, SPECIESORIGIN, TANKSIZE, STOCKING, DIET FROM FISH";
        try (Connection connection = DriverManager.getConnection(url, user,
                password);
                PreparedStatement pStatement = connection
                .prepareStatement(selectQuery);
                ResultSet resultSet = pStatement.executeQuery()) {
            if (resultSet.next()) {
                // Only create the ArrayList if there is something to put in it
                rows = new ArrayList<>();
                do {
                    FishData fishData = new FishData();
                    fishData.setCommonName(resultSet.getString("COMMONNAME"));
                    fishData.setDiet(resultSet.getString("DIET"));
                    fishData.setKh(resultSet.getString("KH"));
                    fishData.setLatin(resultSet.getString("LATIN"));
                    fishData.setPh(resultSet.getString("PH"));
                    fishData.setFishSize(resultSet.getString("FISHSIZE"));
                    fishData.setSpeciesOrigin(resultSet.getString("SPECIESORIGIN"));
                    fishData.setStocking(resultSet.getString("STOCKING"));
                    fishData.setTankSize(resultSet.getString("TANKSIZE"));
                    fishData.setTemp(resultSet.getString("TEMP"));
                    fishData.setId(resultSet.getLong("ID"));
                    rows.add(fishData);
                } while (resultSet.next());
            }
        }
        return rows;
    }

При кодировании этого метода ArrayList создается только в том случае, если из запроса к базе данных были возвращены какие-либо записи, в противном случае возвращается нулевое значение . Вот метод, который преобразует ArrayList записей в String, вызывая findAll () :

    public String retrieveFish() {
        StringBuilder sb = new StringBuilder();
        FishDAO fishDAO = new FishDAOImpl();
        ArrayList data;
        try {
            data = fishDAO.findAll();
            // Must check for null
            if (data != null) {
                data.stream().forEach((fd) -> {
                    sb.append(fd.toString()).append("\n");
                });
            }
        } catch (SQLException e) {
            log.error("Error retrieving records: ", e.getCause());
        }
        return sb.toString();
    }

В этом коде нет ничего плохого. Что может пойти не так, это программист, особенно студент-программист. Ошибка забывает if (data! = Null). Вот классическая ошибка студента:

        try {
            // Oops, forgot to check for null
            data = fishDAO.findAll();
            data.stream().forEach((fd) -> {
                sb.append(fd.toString()).append("\n");
            });
        } catch (SQLException e) {
            log.error("Error retrieving records: ", e.getCause());
        }

Если бы был какой-то способ заставить программиста осознать возможность нулевой ссылки? Существует и появился в Java 8. Это необязательный тип. Его основная цель состоит в том, чтобы исключить возврат null из методов. Изменяя тип возвращаемого значения на Необязательный, мы можем изменить первую и последнюю строки метода findAll () :

public Optional> findAll() throws SQLException {
	. . . 
	// The original code does not change
	. . .
        return Optional.ofNullable(rows);
}

Если возвращаемая ссылка помещается в Optional.ofNullable (), то она может быть или не быть нулевой. Вызывающая сторона этого метода должна иметь дело с возможностью нулевого значения .

Вот как это делается:

    public Optional retrieveFish() {
        StringBuilder sb = new StringBuilder();
        FishDAO fishDAO = new FishDAOImpl();
        //ArrayList data;
        try {
            // ifPresent is required so a check for null can never be forgotten 
            fishDAO.findAll().ifPresent(data -> data.stream().forEach((fd) -> {
                sb.append(fd.toString()).append("\n");
            })
            );
        } catch (SQLException e) {
            log.error("Error retrieving records: ", e.getCause());
        }
        return Optional.of(sb.toString());
    }

Обратите внимание, что для доступа к ArrayList в Optional используется метод ifPresent () . Если это так, то лямбда вызывается для копирования ArrayList в объект StringBuilder .

В этом методе используется еще одна особенность Optional . Тип возвращаемого значения: Optional <String>, и последний оператор читает return Optional.of (sb.toString ()) . Метод члена of () в Optional может принимать только ненулевое значение, поэтому, если объект StringBuilder так или иначе равен нулю, будет исключение.

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