Почему вылетает NullPointerException при использовании do while?

256
20 февраля 2017, 13:11

Задача сохранить в файл все ссылки в указанном URL. Во время чтения через BufferedReader вылетает NullPointerException при использовании цикла do while в строке if (buffer.indexOf ("<a ")> 0) } ,а при использовании for все работает, хотя по идее оба цикла выполняют одно и то же. Подскажите почему так. Ранее без проблем использовал как один, так и другой цикл.

Вылетает:

  public static void getLinks(String link, String file) {
    StringBuilder sb = new StringBuilder();
    try {
        URL url = new URL(link);
        HttpURLConnection connect = (HttpURLConnection) url.openConnection();
        BufferedReader input = new BufferedReader(new InputStreamReader(connect.getInputStream()));
        String buffer = "";
        String text = "";
        do {
            buffer = input.readLine();
            if (buffer.indexOf("<a ") > 0) { // NullPointerException
                if (buffer.indexOf("</a") > 0) {
                    sb.append(buffer.substring(buffer.indexOf("<a "), buffer.indexOf("</a") + 4))
                            .append(System.lineSeparator());
                    text = "";
                } else {
                    text = buffer.substring(buffer.indexOf("<a "));
                }
            } else if (text.length() > 0) {
                if (buffer.indexOf("</a") > 0) {
                    sb.append(text).append(text + buffer.substring(1, buffer.indexOf("</a") + 4))
                            .append(System.lineSeparator());
                    text = "";
                } else {
                    text += buffer;
                }
            }
        } while (buffer != null);
    } catch (IOException e) {
        System.out.println(e);
    }
    try (ObjectOutputStream links = new ObjectOutputStream(new FileOutputStream(file))) {
        links.writeObject(sb.toString());
        System.out.println("File " + file + " was saved!");
    } catch (IOException e) {
        System.out.println("Error save file!");
    }
}

Работает:

public static void getLinks(String link, String file) {
    StringBuilder sb = new StringBuilder();
    try {
        URL url = new URL(link);
        HttpURLConnection connect = (HttpURLConnection) url.openConnection();
        BufferedReader input = new BufferedReader(new InputStreamReader(connect.getInputStream()));
        String buffer = "";
        String text = "";
        for (; (buffer = input.readLine()) != null;) { // работает
            if (buffer.indexOf("<a ") > 0) {
                if (buffer.indexOf("</a") > 0) {
                    sb.append(buffer.substring(buffer.indexOf("<a "), buffer.indexOf("</a") + 4))
                            .append(System.lineSeparator());
                    text = "";
                } else {
                    text = buffer.substring(buffer.indexOf("<a "));
                }
            } else if (text.length() > 0) {
                if (buffer.indexOf("</a") > 0) {
                    sb.append(text).append(text + buffer.substring(1, buffer.indexOf("</a") + 4))
                            .append(System.lineSeparator());
                    text = "";
                } else {
                    text += buffer;
                }
            }
        }
    } catch (IOException e) {
        System.out.println(e);
    }
    try (ObjectOutputStream links = new ObjectOutputStream(new FileOutputStream(file))) {
        links.writeObject(sb.toString());
        System.out.println("File " + file + " was saved!");
    } catch (IOException e) {
        System.out.println("Error save file!");
    }
}
Answer 1

Ваш do-while работает не так же, как for: в for перед каждой итерацией проверяется, что считанный buffer не равен null. В do-while же эта проверка происходит после того, как buffer уже был использован, и, по сути, поэтому проверка лишена смысла, так как если buffer равен null, то об этом станет известно уже в строке if (buffer.indexOf("<a ") > 0) про проброшенному NullPointerException, и до while дело не дойдёт.

Имеет смысл do-while заменить на while:

while ((buffer = input.readLine()) != null)

При использовании Java 8 можно пройтись по всем строкам с использованием for-each.

Например, так:

Stream<String> stream = input.lines();
for (String buffer : (Iterable<String>)stream::iterator) { ... }

Или хотя бы так:

for (String buffer : input.lines().collect(Collectors.toList())) { ... }
Answer 2

Построчное чтение файла предполагает чтение строки и проверку на каждой итерации:

while (true) {
  buffer = input.readLine();
  if (buffer == null) break;
  // ...
}

Или:

do {
  buffer = input.readLine();
  // buffer может быть null здесь, поэтому обязательна проверка:
  if (buffer == null) break;
  // ...
} while (buffer != null); // Из-за проверки выше, проверка здесь не имеет смысла. Выражение всегда true

Как верно заметил @Regent, этот код также можно записать как:

while ((buffer = input.readLine()) != null) {}

Хотя на самом деле, ваш цикл for является полным ему аналогом - for(;cond;); эквивалентен while(cond);

Однако многие styleguides прямо запрещают использование оператора присваивания в условиях.

Если чтение первой строки считать инициализацией цикла, то можно записать этот код как:

for(buffer = input.readLine(); buffer != null; buffer = input.readLine()){
  // ...
}

Однако здесь, очевидно, налицо дублирование кода.

В Java, когда нам нужно перебрать элементы некоторой коллекции(например, строк в файле) мы описываем итератор.
Давайте попробуем:

public class BufferedReaderIterator implements Iterable<String> {
    private BufferedReader input;
    public BufferedReaderIterator(BufferedReader input) {
      this.input = input;
    }
    @Override
    public Iterator<String> iterator() {
        return new Iterator<String>() {
            @Override
            public boolean hasNext() {
                try {
                    input.mark(1);
                    if (input.read() < 0) return false;
                    input.reset();
                    return true;
                } 
                catch (IOException e) {return false; }
            }
            @Override
            public String next() {
                try { return input.readLine(); } 
                catch (IOException e) {return null; }
            }
            @Override
            public void remove() {throw new UnsupportedOperationException(); }
        };
    }

И вот как теперь будет выглядеть первоначальный цикл:

for(String buffer: new BufferedReaderIterator(input)){
  // ...
}

Мы избавились и от дублирования, и от присваивания.

В качестве альтернативы можно воспользоваться готовым решением, например: org.apache.commons.io.LineIterator.
Правда с ним придётся обрабатывать исключения.

READ ALSO
NumberFormatException при считывании чисел из файла

NumberFormatException при считывании чисел из файла

Есть текстовый файл, который содержит целые числа, каждое с новой строки:

287
в клиент-сервере на RMI исключение java.lang.ClassNotFoundException:

в клиент-сервере на RMI исключение java.lang.ClassNotFoundException:

Клиент - сервер на RMI При старте под intelij idea, на стороне выбрасывается исключение

354