一、Java异常处理
Java异常处理是Java语言中用于处理程序运行时错误的机制,它允许程序在遇到错误时优雅地处理而不是直接崩溃。
1. 异常分类
Java异常主要分为三大类:
- 检查异常(Checked Exception):编译时必须处理的异常,如
IOException、SQLException - 运行时异常(Runtime Exception):程序运行过程中可能出现的异常,如
NullPointerException、ArrayIndexOutOfBoundsException - 错误(Error):系统级别的严重问题,如
OutOfMemoryError、StackOverflowError
2. 异常处理语法
try-catch-finally结构
try {
// 可能抛出异常的代码
int result = 10 / 0;
} catch (ArithmeticException e) {
// 处理特定异常
System.out.println("除零异常: " + e.getMessage());
} catch (Exception e) {
// 处理其他异常
System.out.println("其他异常: " + e.getMessage());
} finally {
// 无论是否有异常都会执行
System.out.println("清理资源");
}
try-with-resources语句
这段代码演示了 Java 7 引入的 try-with-resources 语法特性,它是一种自动资源管理机制。
// 自动关闭资源
try (FileInputStream fis = new FileInputStream("test.txt");
BufferedReader reader = new BufferedReader(new InputStreamReader(fis))) {
String line = reader.readLine();
System.out.println(line);
} catch (IOException e) {
System.out.println("文件操作异常: " + e.getMessage());
}
// 资源会自动关闭,无需手动调用close()
- 在 try 后面的括号内声明并初始化资源
- FileInputStream fis = new FileInputStream("test.txt"):创建文件输入流
- BufferedReader reader = new BufferedReader(new InputStreamReader(fis)):创建带缓冲的读取器
- 当 try 代码块执行完毕后(无论是正常结束还是发生异常)
- JVM 会自动调用所有在 try 括号中声明的资源的 close() 方法
- 无需手动编写 fis.close() 或 reader.close()
- 只有实现了 AutoCloseable 或 Closeable 接口的类才能在 try-with-resources 中使用 FileInputStream 和 BufferedReader 都实现了 Closeable 接口
3. 抛出异常
throw关键字
public void validateAge(int age) throws IllegalArgumentException {
if (age < 0) {
throw new IllegalArgumentException("年龄不能为负数");
}
if (age > 150) {
throw new IllegalArgumentException("年龄不能超过150");
}
}
throws声明
// 声明可能抛出的检查异常
public void readFile(String filename) throws IOException {
FileReader file = new FileReader(filename);
// 文件操作代码...
}
4. 自定义异常
// 自定义检查异常
public class InsufficientFundsException extends Exception {
private double amount;
public InsufficientFundsException(double amount) {
super("余额不足,缺少金额: " + amount);
this.amount = amount;
}
public double getAmount() {
return amount;
}
}
// 自定义运行时异常
public class InvalidAccountException extends RuntimeException {
public InvalidAccountException(String message) {
super(message);
}
}
5. 异常处理最佳实践
多重异常捕获
try {
// 可能抛出多种异常的代码
processData();
} catch (IOException | SQLException e) {
// 同时处理多种相似异常
logger.error("数据处理失败", e);
throw new ServiceException("服务暂时不可用");
}
异常链
public void processFile(String filename) throws FileProcessException {
try {
// 文件处理逻辑
readFile(filename);
} catch (IOException e) {
// 保留原始异常信息
throw new FileProcessException("文件处理失败: " + filename, e);
}
}
资源管理示例
public class ResourceManager {
public String readFileContent(String filePath) {
try (BufferedReader reader = Files.newBufferedReader(Paths.get(filePath))) {
return reader.lines()
.collect(Collectors.joining("\n"));
} catch (NoSuchFileException e) {
System.err.println("文件不存在: " + filePath);
return "";
} catch (IOException e) {
System.err.println("读取文件失败: " + e.getMessage());
return "";
}
}
}
6. 异常处理原则
- 具体异常优先:先捕获具体的异常类型,再捕获通用异常
- 避免空catch块:至少记录异常信息
- 合理使用finally:用于释放资源和清理工作
- 异常信息要明确:提供有用的错误信息帮助调试
- 不要忽略检查异常:必须显式处理或声明抛出
二、Java IO流
Java IO流是Java中用于处理输入输出操作的核心机制,它提供了一种统一的方式来读取和写入数据。IO流将数据的传输抽象为流的形式,使得程序可以像处理水流一样处理数据。
1. 流的分类
按数据流向分类
- 输入流:
InputStream、Reader - 输出流:
OutputStream、Writer
按处理数据单位分类
-
字节流:处理二进制数据
- 基类:
InputStream、OutputStream - 常用实现类:
FileInputStream、FileOutputStream、ByteArrayInputStream、ByteArrayOutputStream
- 基类:
-
字符流:处理字符数据
- 基类:
Reader、Writer - 常用实现类:
FileReader、FileWriter、StringReader、StringWriter
- 基类:
2. 常用IO流类详解
字节流示例
// FileInputStream示例
try (FileInputStream fis = new FileInputStream("input.txt")) {
int data;
// 逐字节读取输入流中的数据
while ((data = fis.read()) != -1) {
System.out.print((char) data);
}
} catch (IOException e) {
e.printStackTrace();
}
// FileOutputStream示例
try (FileOutputStream fos = new FileOutputStream("output.txt")) {
String content = "Hello World";
fos.write(content.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
字符流示例
// FileReader示例
try (FileReader fr = new FileReader("input.txt")) {
int ch;
// 逐字符读取输入流中的数据
while ((ch = fr.read()) != -1) {
System.out.print((char) ch);
}
} catch (IOException e) {
e.printStackTrace();
}
// FileWriter示例
try (FileWriter fw = new FileWriter("output.txt")) {
fw.write("Hello World");
} catch (IOException e) {
e.printStackTrace();
}
缓冲流示例
缓冲流(Buffered Stream)是Java IO中的一种处理流,它通过在内存中创建缓冲区来提高IO操作的效率。
// BufferedInputStream示例
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream("input.txt"))) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = bis.read(buffer)) != -1) {
System.out.write(buffer, 0, bytesRead);
}
} catch (IOException e) {
e.printStackTrace();
}
// BufferedWriter示例
try (BufferedWriter bw = new BufferedWriter(new FileWriter("output.txt"))) {
bw.write("Hello World");
bw.newLine();
bw.write("This is a new line");
} catch (IOException e) {
e.printStackTrace();
}
三、文件操作
1. File类操作
文件和目录基本操作
// 创建File对象
File file = new File("example.txt");
File dir = new File("exampleDir");
// 文件操作
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
// 目录操作
if (!dir.exists()) {
dir.mkdir(); // 创建单级目录
// dir.mkdirs(); // 创建多级目录
}
// 文件信息获取
System.out.println("文件名: " + file.getName());
System.out.println("文件路径: " + file.getAbsolutePath());
System.out.println("文件大小: " + file.length() + " bytes");
System.out.println("是否为文件: " + file.isFile());
System.out.println("是否为目录: " + file.isDirectory());
文件列表操作
// 列出目录下的文件
File directory = new File(".");
File[] files = directory.listFiles();
if (files != null) {
for (File f : files) {
System.out.println(f.getName() + (f.isDirectory() ? " [目录]" : " [文件]"));
}
}
// 使用文件过滤器
File[] txtFiles = directory.listFiles((dir, name) -> name.endsWith(".txt"));
2. NIO.2文件操作(Path和Files)
NIO.2(New I/O 2)是Java 7引入的文件I/O API,提供了更现代化和功能丰富的文件操作方式。使用 Path 接口替代传统的 File 类,通过Files工具类提供更灵活的路径处理能力,Files 方法内部封装了流的创建和管理, 可以异步进行文件读写。一般开发都是用这个。
// 传统方式:手动管理流
try (BufferedReader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
String line = reader.readLine();
}
// Files方式:简化操作
List<String> lines = Files.readAllLines(path);
Path和Files类操作
// Path操作
Path path = Paths.get("example.txt");
Path absolutePath = path.toAbsolutePath();
System.out.println("绝对路径: " + absolutePath);
// Files类操作
try {
// 读取文件所有行
List<String> lines = Files.readAllLines(path);
lines.forEach(System.out::println);
// 写入文件
List<String> content = Arrays.asList("第一行", "第二行", "第三行");
Files.write(Paths.get("output.txt"), content, StandardCharsets.UTF_8);
// 复制文件
Files.copy(Paths.get("source.txt"), Paths.get("destination.txt"),
StandardCopyOption.REPLACE_EXISTING);
// 删除文件
Files.deleteIfExists(Paths.get("temp.txt"));
} catch (IOException e) {
e.printStackTrace();
}
Path类速查
2. 创建 Path 对象
Paths.get(String first, String... more): 通过字符串创建路径Paths.get(URI uri): 通过 URI 创建路径FileSystem.getPath(String first, String... more): 通过文件系统创建
路径操作
Path resolve(Path other): 解析相对路径Path resolve(String other): 解析字符串路径Path relativize(Path other): 计算相对路径Path normalize(): 规范化路径Path getParent(): 获取父路径Path getFileName(): 获取文件名Path getName(int index): 获取指定索引的名称
路径查询
boolean isAbsolute(): 判断是否为绝对路径Path toAbsolutePath(): 转换为绝对路径int getNameCount(): 获取路径元素数量Iterator<Path> iterator(): 获取路径迭代器
文件系统操作
FileSystem getFileSystem(): 获取关联的文件系统URI toUri(): 转换为 URIFile toFile(): 转换为传统 File 对象(如果适用)
与其他组件协作
- 与
Files类配合进行文件操作 - 可作为
InputStream/OutputStream等流的源或目标 - 支持与传统
File类相互转换
Files类速查
1. 文件创建和删除
createFile(Path path, FileAttribute<?>... attrs): 创建新文件createDirectory(Path dir, FileAttribute<?>... attrs): 创建目录createDirectories(Path dir, FileAttribute<?>... attrs): 创建目录及其所有父目录delete(Path path): 删除文件或目录deleteIfExists(Path path): 如果文件存在则删除
2. 文件复制和移动
copy(Path source, Path target, CopyOption... options): 复制文件move(Path source, Path target, CopyOption... options): 移动或重命名文件
3. 文件读写
readString(Path path): 读取文件内容为字符串writeString(Path path, CharSequence csq, OpenOption... options): 将字符串写入文件readAllLines(Path path): 读取文件所有行readAllBytes(Path path): 读取文件所有字节write(Path path, byte[] bytes, OpenOption... options): 写入字节数组到文件
4. 文件属性操作
exists(Path path, LinkOption... options): 检查文件是否存在isDirectory(Path path, LinkOption... options): 检查是否为目录isRegularFile(Path path, LinkOption... options): 检查是否为普通文件size(Path path): 获取文件大小getLastModifiedTime(Path path): 获取最后修改时间setLastModifiedTime(Path path, FileTime time): 设置最后修改时间
5. 目录遍历
list(Path dir): 列出目录内容walk(Path start, FileVisitOption... options): 遍历文件树find(Path start, int maxDepth, BiPredicate<Path, BasicFileAttributes> matcher, FileVisitOption... options): 查找匹配的文件
6. 文件权限和属性
getPosixFilePermissions(Path path, LinkOption... options): 获取POSIX文件权限setPosixFilePermissions(Path path, Set<PosixFilePermission> perms): 设置POSIX文件权限getAttribute(Path path, String attribute, LinkOption... options): 获取文件属性setAttribute(Path path, String attribute, Object value, LinkOption... options): 设置文件属性
四、 序列化
序列化(Serialization)是将对象转换为字节流的过程,以便于存储或传输。反序列化(Deserialization)则是将字节流还原为对象的过程。
1. 基本序列化
实现Serializable接口
import java.io.*;
// 可序列化的类
public class Person implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
private transient String password; // 不会被序列化
public Person(String name, int age, String password) {
this.name = name;
this.age = age;
this.password = password;
}
// getter和setter方法...
@Override
public String toString() {
return "Person{name='" + name + "', age=" + age + ", password='" + password + "'}";
}
}
序列化和反序列化示例
// 序列化对象到文件
public static void serializeObject(Object obj, String filename) {
try (ObjectOutputStream oos = new ObjectOutputStream(
new FileOutputStream(filename))) {
oos.writeObject(obj);
System.out.println("对象已序列化到文件: " + filename);
} catch (IOException e) {
e.printStackTrace();
}
}
// 从文件反序列化对象
public static Object deserializeObject(String filename) {
try (ObjectInputStream ois = new ObjectInputStream(
new FileInputStream(filename))) {
Object obj = ois.readObject();
System.out.println("对象已从文件反序列化: " + filename);
return obj;
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
return null;
}
}
// 使用示例
Person person = new Person("张三", 25, "secret123");
serializeObject(person, "person.ser");
Person deserializedPerson = (Person) deserializeObject("person.ser");
System.out.println(deserializedPerson);
2. 自定义序列化
实现自定义序列化方法
public class CustomSerializableClass implements Serializable {
private static final long serialVersionUID = 1L;
private String name;
private int age;
private String sensitiveData;
public CustomSerializableClass(String name, int age, String sensitiveData) {
this.name = name;
this.age = age;
this.sensitiveData = sensitiveData;
}
// 自定义序列化方法
private void writeObject(ObjectOutputStream oos) throws IOException {
// 执行默认序列化
oos.defaultWriteObject();
// 自定义序列化逻辑 - 加密敏感数据
String encryptedData = encrypt(sensitiveData);
oos.writeObject(encryptedData);
}
// 自定义反序列化方法
private void readObject(ObjectInputStream ois)
throws IOException, ClassNotFoundException {
// 执行默认反序列化
ois.defaultReadObject();
// 自定义反序列化逻辑 - 解密敏感数据
String encryptedData = (String) ois.readObject();
this.sensitiveData = decrypt(encryptedData);
}
private String encrypt(String data) {
// 简单加密实现
return new StringBuilder(data).reverse().toString();
}
private String decrypt(String data) {
// 简单解密实现
return new StringBuilder(data).reverse().toString();
}
}
3. Externalizable接口
实现Externalizable接口
public class ExternalizableExample implements Externalizable {
private String name;
private int age;
// 必须有无参构造函数
public ExternalizableExample() {}
public ExternalizableExample(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public void writeExternal(ObjectOutput out) throws IOException {
out.writeObject(name);
out.writeInt(age);
}
@Override
public void readExternal(ObjectInput in)
throws IOException, ClassNotFoundException {
name = (String) in.readObject();
age = in.readInt();
}
@Override
public String toString() {
return "ExternalizableExample{name='" + name + "', age=" + age + "}";
}
}
最佳实践
1. IO流使用建议
- 使用try-with-resources确保资源正确关闭
- 对于大量数据操作,使用缓冲流提高性能
- 根据数据类型选择合适的流(字节流vs字符流)
2. 文件操作建议
- 使用NIO.2 API进行现代文件操作
- 注意处理文件路径分隔符兼容性
- 检查文件操作权限和异常处理
3. 序列化建议
- 显式声明
serialVersionUID - 对敏感数据使用
transient关键字 - 考虑使用JSON等格式替代Java原生序列化
- 注意序列化版本兼容性问题