1. 概述

在这个简短的教程中,我们将探讨**如何处理使用Scanner类读取文件时遇到的NoSuchElementException: No line found**。

首先,我们会理解这个异常的根本原因,然后学习如何在实践中重现它,最后学习如何修复它。

2. 理解异常

Scanner如其名所示,是Java提供的一类方法,用于扫描和解析基本类型和字符串。

在这些方法中,我们找到了nextLine(),它返回当前行,但不包括行尾的分隔符。

在深入之前,让我们来看看“NoSuchElementException: No line found”是什么意思。

NoSuchElementException表示我们尝试访问的元素不存在。因此,堆栈跟踪中的“没有找到行”表明Scanner未能获取请求的行。

最常见的原因是当我们尝试调用nextLine()方法时,没有可读的行

3. 重现异常

现在我们知道Scanner为何会因NoSuchElementException失败,接下来我们看看如何重现它。

为了演示这个异常,我们将创建一个方法,使用Scanner.nextLine()来读取文件:

static String readFileV1(String pathname) throws IOException {
    Path pathFile = Paths.get(pathname);
    if (Files.notExists(pathFile)) {
        return "";
    }

    try (Scanner scanner = new Scanner(pathFile)) {
        return scanner.nextLine();
    }
}

如图所示,我们使用Path类表示要读取的文件。

现在,让我们将一个空文件作为参数传递给我们的方法,看看会发生什么:

@Test
void givenEmptyFile_whenUsingReadFileV1_thenThrowException() throws IOException {
    Exception exception = assertThrows(NoSuchElementException.class, () -> {
        ScannerNoSuchElementException.readFileV1("src/test/resources/emptyFile.txt");
    });

    assertEquals("No line found", exception.getMessage());
}

因此,试图读取一个空文件会导致NoSuchElementException: No line found

这里的主要原因是nextLine()期望存在一行,否则就会抛出异常

4. 使用防御性编程的解决方案

避免异常最简单的方法是在调用nextLine()之前检查是否有下一行。

为此,我们可以使用hasNextLine()方法,如果输入还有下一行,它将返回true

那么,让我们创建一个新的方法readFileV2(),作为readFileV1()的增强版本:

static String readFileV2(String pathname) throws IOException {
    Path pathFile = Paths.get(pathname);
    if (Files.notExists(pathFile)) {
        return "";
    }

    try (Scanner scanner = new Scanner(pathFile)) {
        return scanner.hasNextLine() ? scanner.nextLine() : "";
    }
}

如上所示,如果有下一行,我们就返回下一行,否则返回一个空字符串。

现在,让我们用一个测试用例验证一切是否按预期工作:

@Test
void givenEmptyFile_whenUsingReadFileV2_thenSuccess() throws IOException {
    String emptyLine = ScannerNoSuchElementException.readFileV2("src/test/resources/emptyFile.txt");

    assertEquals("", emptyLine);
}

解决方案正常工作,如测试用例所示。hasNextLine()防止了nextLine()抛出异常。

另一种解决方案是在使用Scanner读取文件之前检查文件是否为空

static String readFileV3(String pathname) throws IOException {
    Path pathFile = Paths.get(pathname);
    if (Files.notExists(pathFile) || Files.size(pathFile) == 0) {
        return "";
    }

    try (Scanner scanner = new Scanner(pathFile)) {
        return scanner.nextLine();
    }
}

这样可以确保至少有一行被nextLine()消费。因此,我们省略了hasNextLine()

现在,让我们测试这个新方法:

@Test
void givenEmptyFile_whenUsingReadFileV3_thenSuccess() throws IOException {
    String emptyLine = ScannerNoSuchElementException.readFileV3("src/test/resources/emptyFile.txt");

    assertEquals("", emptyLine);
}

5. 使用异常处理的解决方案

另一种选择是**传统地使用try-catch块处理NoSuchElementException**:

static String readFileV4(String pathname) throws IOException {
    Path pathFile = Paths.get(pathname);
    if (Files.notExists(pathFile)) {
        return "";
    }

    try (Scanner scanner = new Scanner(pathFile)) {
        return scanner.nextLine();
    } catch (NoSuchElementException exception) {
        return "";
    }
}

如上所示,我们捕获了异常,并返回了一个空字符串。

最后,让我们用一个测试用例确认这一点:

@Test
void givenEmptyFile_whenUsingReadFileV4_thenSuccess() throws IOException {
    String emptyLine = ScannerNoSuchElementException.readFileV4("src/test/resources/emptyFile.txt");

    assertEquals("", emptyLine);
}

6. 总结

在这篇文章中,我们了解了当使用Scanner读取文件时,NoSuchElementException: No line found的原因。

接着,我们通过实际例子理解了如何产生这个异常以及如何修复它。

一如既往,所有示例的完整源代码可以在GitHub上找到。