跳至主要內容

异常处理 & I/O(对比 JavaScript)

Mr.Dingjava异常处理I/O前端转Java大约 4 分钟约 1086 字

异常处理 & I/O(对比 JavaScript)

一、异常处理

1. try-catch-finally

try {
    // 可能出问题的代码
    int result = 10 / 0;
    System.out.println("这行不会执行");
} catch (ArithmeticException e) {
    // 捕获特定异常
    System.out.println("除数不能为0: " + e.getMessage());
} catch (Exception e) {
    // 捕获通用异常(范围从窄到宽)
    System.out.println("其他异常: " + e.getMessage());
} finally {
    // 始终执行(类似 JS 的 finally)
    System.out.println("清理资源");
}
// JavaScript(基本一样)
try {
    const result = 10 / 0;
} catch (e) {
    console.log(e.message);
} finally {
    console.log("清理资源");
}

语法基本一致,但 Java 的异常机制比 JS 复杂得多。


2. 受检异常 vs 非受检异常

这是 Java 最独特(也最让新手困惑)的设计。

类型说明必须处理?常见例子
受检异常 (Checked Exception)编译器强制处理✅ 必须 try-catch 或 throwsIOException, SQLException
非受检异常 (RuntimeException)运行时可能发生❌ 不强制NullPointerException, ArrayIndexOutOfBoundsException
ErrorJVM 严重错误❌ 无法处理OutOfMemoryError, StackOverflowError
// 受检异常:编译器强制你处理(不处理编译报错)
import java.io.*;

public class ReadFile {
    public void readFile(String path) {
        // ❌ 编译错误!FileReader 构造器抛 FileNotFoundException(受检异常)
        FileReader reader = new FileReader(path);
    }
}

// ✅ 方案1:try-catch 处理
public void readFileSafe(String path) {
    try {
        FileReader reader = new FileReader(path);
        System.out.println("文件打开成功");
    } catch (FileNotFoundException e) {
        System.out.println("文件不存在: " + path);
    }
}

// ✅ 方案2:throws 抛给调用者
public void readFileThrows(String path) throws FileNotFoundException {
    FileReader reader = new FileReader(path);
}
// JavaScript 没有受检/非受检之分
// 任何异常都可以 throw 或 try-catch
function readFile(path) {
    // 不会强制处理
    const reader = new FileReader(path);
}

前端理解:受检异常 ≈ TS 在函数签名中声明了异常类型,编译器强制你处理。JS 没有这个机制,所有异常都是「运行时才发现」。


3. 自定义异常

// 自定义异常(继承 Exception 或 RuntimeException)
public class BusinessException extends RuntimeException {
    private int errorCode;

    public BusinessException(int errorCode, String message) {
        super(message);
        this.errorCode = errorCode;
    }

    public int getErrorCode() {
        return errorCode;
    }
}

// 使用
public void validateAge(int age) {
    if (age < 0) {
        throw new BusinessException(400, "年龄不能为负数");
    }
    if (age > 150) {
        throw new BusinessException(400, "年龄不合法");
    }
}

try {
    validateAge(-1);
} catch (BusinessException e) {
    System.out.println("错误码: " + e.getErrorCode());  // 400
    System.out.println("信息: " + e.getMessage());       // 年龄不能为负数
}

4. try-with-resources(Java 7+)

自动关闭实现了 AutoCloseable 的资源(文件、数据库连接等)。

// ❌ 传统方式:需要手动关闭
BufferedReader reader = null;
try {
    reader = new BufferedReader(new FileReader("test.txt"));
    String line = reader.readLine();
} catch (IOException e) {
    e.printStackTrace();
} finally {
    if (reader != null) {
        reader.close();  // 又可能抛异常
    }
}

// ✅ try-with-resources:自动关闭
try (BufferedReader reader = new BufferedReader(new FileReader("test.txt"))) {
    String line = reader.readLine();
    System.out.println(line);
} catch (IOException e) {
    System.out.println("读取失败: " + e.getMessage());
}
// reader 自动 close(),不需要 finally

前端理解:类似于 Python 的 with 语句,或者 MDN 的 using 声明(Stage 3)。


二、I/O 操作

1. 读取文件

import java.nio.file.*;
import java.io.*;
import java.util.List;

// ✅ 方式1:Files.readAllLines(最简单,推荐)
List<String> lines = Files.readAllLines(Paths.get("test.txt"));
lines.forEach(System.out::println);

// ✅ 方式2:Files.lines (Stream 风格)
try (Stream<String> stream = Files.lines(Paths.get("test.txt"))) {
    stream.filter(line -> line.contains("ERROR"))
          .forEach(System.out::println);
} catch (IOException e) {
    e.printStackTrace();
}

// ✅ 方式3:读取全部内容为字符串
String content = Files.readString(Paths.get("test.txt"));
// Node.js
const fs = require('fs');
const content = fs.readFileSync('test.txt', 'utf-8');
const lines = content.split('\n');

2. 写入文件

// 写入
Files.writeString(Paths.get("output.txt"), "Hello, Java!");

// 写入多行
List<String> data = List.of("Line 1", "Line 2", "Line 3");
Files.write(Paths.get("output.txt"), data);

// 追加写入
Files.writeString(Paths.get("output.txt"), "Append content",
    StandardOpenOption.APPEND);
// Node.js
fs.writeFileSync('output.txt', 'Hello, Node.js!');
fs.appendFileSync('output.txt', 'Append content');

3. 缓冲读取(大文件)

// 大文件逐行读取(不一次性加载到内存)
try (BufferedReader reader = Files.newBufferedReader(Paths.get("bigfile.txt"))) {
    String line;
    while ((line = reader.readLine()) != null) {
        // 处理每一行
        System.out.println(line);
    }
} catch (IOException e) {
    e.printStackTrace();
}
// Node.js
const readline = require('readline');
// 使用 stream 逐行处理

三、文件与路径操作

import java.nio.file.*;

// 检查文件
Path path = Paths.get("test.txt");
boolean exists = Files.exists(path);
boolean isFile = Files.isRegularFile(path);
boolean isDir = Files.isDirectory(path);
long fileSize = Files.size(path);          // 文件大小(字节)

// 创建目录
Files.createDirectories(Paths.get("a/b/c"));  // 创建多级目录

// 复制/移动/删除
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
Files.move(source, target);
Files.delete(path);
Files.deleteIfExists(path);

// 列出目录
try (Stream<Path> files = Files.list(Paths.get("."))) {
    files.filter(Files::isRegularFile)
         .forEach(System.out::println);
}

// 遍历目录树
Files.walk(Paths.get("src"))
     .filter(Files::isRegularFile)
     .filter(p -> p.toString().endsWith(".java"))
     .forEach(System.out::println);

快速速查

场景JavaNode.js
读文件Files.readString(path)fs.readFileSync(path, 'utf-8')
写文件Files.writeString(path, content)fs.writeFileSync(path, content)
逐行读Files.readAllLines(path)content.split('\n')
追加写Files.writeString(path, s, APPEND)fs.appendFileSync(path, s)
检查存在Files.exists(path)fs.existsSync(path)
删除Files.delete(path)fs.unlinkSync(path)
创建目录Files.createDirectories(path)fs.mkdirSync(path, {recursive: true})
复制Files.copy(src, dst)fs.cpSync(src, dst)
遍历目录Files.walk(path)fs.readdirSync(path, {recursive: true})
上次编辑于:
贡献者: dingyongya