【Java零基础·第10章】异常与常用类
Java 异常与常用类详解
一、异常处理机制
1.1 什么是异常?
异常是指在Java程序运行过程中发生的不正常情况,这些情况会导致程序无法按照预期流程执行。例如除数为零、数组下标越界等情况都会引发异常。
1.2 异常的分类
Java异常主要分为两类:
-
编译时异常(Checked Exception):
- 编译器在编译阶段就能检测到的异常
- 必须显式处理(捕获或抛出),否则程序无法通过编译
- 通常表示程序外部可能发生的、可预见的错误情况
- 示例:
FileNotFoundException
:文件找不到异常IOException
:输入输出异常SQLException
:数据库操作异常ClassNotFoundException
:类未找到异常
-
运行时异常(Unchecked Exception/RuntimeException):
- 编译器无法检测,只有在程序运行时才可能发生的异常
- 不强制要求处理,但良好的编程习惯建议处理
- 通常表示程序内部的逻辑错误或资源使用不当
- 常见子类:
ArithmeticException
:算术异常(如除零)NullPointerException
:空指针异常ArrayIndexOutOfBoundsException
:数组越界异常ClassCastException
:类型转换异常IllegalArgumentException
:非法参数异常
// 编译时异常示例
import java.io.FileInputStream;
import java.io.FileNotFoundException;public class ExceptionDemo {public static void main(String[] args) {// 编译时异常处理方式1:try-catch捕获try {FileInputStream fis = new FileInputStream("nonexistent.txt");} catch (FileNotFoundException e) {System.err.println("文件不存在,请检查路径!");e.printStackTrace();}// 编译时异常处理方式2:throws声明抛出try {readFile();} catch (FileNotFoundException e) {System.out.println("在主方法中处理文件异常");}// 运行时异常示例int[] arr = {1, 2, 3};// 可能抛出ArrayIndexOutOfBoundsExceptiontry {System.out.println(arr[5]);} catch (ArrayIndexOutOfBoundsException e) {System.out.println("数组索引越界!有效范围:0-" + (arr.length-1));}// 算术异常示例int a = 10;int b = 0;// 可能抛出ArithmeticExceptiontry {System.out.println(a/b);} catch (ArithmeticException e) {System.out.println("除数不能为零!");}}// 声明抛出FileNotFoundException异常public static void readFile() throws FileNotFoundException {FileInputStream fis = new FileInputStream("data.txt");// 文件操作代码...}
}
实际应用场景建议:
- 对于编译时异常,应该优先考虑是否能预防(如文件操作前检查文件是否存在)
- 对于运行时异常,应该在开发阶段通过充分测试来避免
- 异常处理时应该:
- 提供有意义的错误信息
- 考虑异常恢复策略
- 必要时记录异常日志
- 避免空的catch块(会隐藏错误)
1.3 异常的体系结构
Java中所有异常的根类是Throwable
,它有两个直接子类:
- Error:表示严重错误,程序通常无法处理,如
OutOfMemoryError
(内存溢出)。 - Exception:表示程序可以处理的异常,分为:
- 运行时异常(
RuntimeException
及其子类) - 编译时异常(除运行时异常外的其他
Exception
子类)
- 运行时异常(
1.4 异常处理关键字
try-catch-finally
try-catch-finally
是 Java 中处理运行时异常的标准结构,它提供了完整的异常处理流程:
-
try
块:- 包含可能抛出异常的代码段
- 一旦发生异常,立即跳转到对应的catch块
- 可以包含多个可能抛出不同异常的语句
- 示例:文件操作、数据库连接、数学运算等高风险操作
-
catch
块:- 捕获并处理特定类型的异常
- 可以定义多个catch块处理不同类型的异常
- 捕获顺序应遵循从具体到一般的异常类型
- 常见异常类型:ArithmeticException、NullPointerException、ArrayIndexOutOfBoundsException等
- 可以通过e.printStackTrace()打印完整异常堆栈
-
finally
块:- 无论是否发生异常都会执行的代码块
- 主要用于资源释放(如关闭文件流、数据库连接)
- 当 try 或 catch 块中包含 return 语句时,finally 块依然会执行
- 但如果在 try/catch 块中调用 System.exit(),则 finally 块不会执行
public class TryCatchDemo {public static void main(String[] args) {int a = 10;int b = 0;try {// 高风险运算代码System.out.println("开始除法运算");System.out.println(a / b); // 可能抛出ArithmeticExceptionSystem.out.println("运算成功完成"); // 异常发生时这行不会执行} catch (ArithmeticException e) {// 处理算术异常System.err.println("捕获到算术异常: " + e.getMessage());System.out.println("除数不能为零");} finally {// 无论是否异常都会执行System.out.println("finally块执行 - 运算结束");// 这里通常会放置资源释放代码// 例如:file.close(), connection.close()}// 多catch块示例try {int[] arr = new int[5];arr[10] = 50; // 可能抛出ArrayIndexOutOfBoundsException} catch (ArrayIndexOutOfBoundsException e) {System.out.println("数组越界异常");} catch (Exception e) {System.out.println("通用异常处理");}}
}
实际应用场景:
- 文件操作时处理IOException
- 数据库操作时处理SQLException
- 网络通信时处理SocketException
- 类型转换时处理ClassCastException
- 处理用户输入时的NumberFormatException
throw与throws
在了解throw与throws之前,我们需要先明确 Java 异常处理的核心目的 ——当程序运行中出现意外情况(如空指针、数组越界)时,通过规范化的方式传递错误信息,避免程序直接崩溃,并提供修复或降级的机会。
异常处理的核心流程分为三步:
发现异常:程序执行时检测到不符合预期的情况(如除以 0、访问不存在的文件);
抛出异常:通过throw或throws将异常信息传递给上层调用者;
捕获并处理异常:通过try-catch-finally语句捕获异常,并编写针对性的处理逻辑。
而throw与throws,正是 “抛出异常” 环节的关键工具,但分工不同。
throw—— 主动 “抛出” 一个具体异常
throw的作用是在代码执行过程中,主动创建并抛出一个 “具体的异常对象”,它直接触发异常,属于 “执行时的动作”。
语法格式
throw后面必须跟一个异常类的实例对象(不能是异常类本身),语法如下:
throw new 异常类名(异常信息);
异常类可以是 Java 内置异常(如NullPointerException、IllegalArgumentException),也可以是自定义异常;
异常信息可选,用于描述异常原因,方便调试。
使用场景
throw通常用于以下场景:
检测到 “逻辑错误” 时主动抛出异常(如参数不合法);
在自定义异常中,配合业务逻辑触发异常。
实例代码
以 “判断年龄是否合法” 为例,当年龄小于 0 或大于 150 时,主动抛出IllegalArgumentException:
public class ThrowExample {// 检查年龄是否合法public static void checkAge(int age) {// 逻辑判断:年龄不合法时主动抛出异常if (age < 0 || age > 150) {// throw创建并抛出具体的异常对象,附带错误信息throw new IllegalArgumentException("年龄必须在0-150之间,当前值:" + age);}// 若没有异常,执行正常逻辑System.out.println("年龄合法:" + age);}public static void main(String[] args) {try {// 调用方法,可能触发异常checkAge(200);} catch (IllegalArgumentException e) {// 捕获并处理throw抛出的异常System.out.println("捕获到异常:" + e.getMessage());}}
}
运行结果:
捕获到异常:年龄必须在0-150之间,当前值:200
关键说明:
throw必须在方法内部使用,且抛出的是 “具体的异常对象”;
若throw抛出的是受检异常(如IOException),则必须在当前方法中通过try-catch捕获,或通过throws声明抛出(下文会讲);若抛出的是非受检异常(如RuntimeException及其子类),则可选择性处理。
throws——“声明” 方法可能抛出的异常
throws的作用是在方法定义时,“声明” 该方法可能会抛出的异常类型,它属于 “编译时的声明”,告诉调用者:“我这个方法可能会出这些问题,你需要处理或继续声明”。
语法格式
throws后面跟一个或多个异常类名(用逗号分隔),写在方法参数列表和方法体之间,语法如下:
修饰符 返回值类型 方法名(参数列表) throws 异常类名1, 异常类名2, ... {// 方法体
}
异常类可以是受检异常或非受检异常,但通常用于声明受检异常(非受检异常可省略声明);
若方法可能抛出多个异常,用逗号分隔异常类名。
使用场景
throws通常用于以下场景:
方法内部调用了一个 “声明了受检异常” 的方法,且当前方法不想处理该异常,而是将异常交给上层调用者处理;
方法内部通过throw抛出了受检异常,且不想在当前方法中捕获,而是通过throws声明抛出。
实例代码
以 “读取文件内容” 为例,FileReader的构造方法会抛出受检异常FileNotFoundException,我们通过throws将该异常声明给上层调用者:
import java.io.FileReader;
import java.io.FileNotFoundException;public class ThrowsExample {// 声明方法可能抛出FileNotFoundException(受检异常)public static void readFile(String filePath) throws FileNotFoundException {// FileReader构造方法会抛出FileNotFoundException,当前方法不处理,通过throws声明FileReader reader = new FileReader(filePath);System.out.println("文件读取成功");}public static void main(String[] args) {// 调用声明了异常的方法,必须处理或继续声明try {readFile("D:/test.txt"); // 若文件不存在,会触发异常} catch (FileNotFoundException e) {// 处理throws声明的异常System.out.println("捕获到文件不存在异常:" + e.getMessage());}}
}
运行结果(若D:/test.txt不存在):
捕获到文件不存在异常:D:\test.txt (系统找不到指定的文件。)
关键说明:
throws仅用于 “声明” 异常,不会主动抛出异常;异常的实际抛出,仍需方法内部通过throw或调用其他抛出异常的方法;
若调用了声明了受检异常的方法,调用者必须通过try-catch捕获,或继续通过throws向上声明(直到main方法,main方法可声明抛出,最终由 JVM 处理,表现为程序崩溃并打印异常栈)。
1.5 重写方法的异常处理要求
重写方法时,异常声明需要遵循"两小一大"原则:
- 子类重写的方法不能抛出比父类更多或更宽泛的编译时异常
- 子类可以抛出更少或更具体的异常
- 子类可以不抛出任何异常
class Parent {public void method() throws IOException {// 父类方法声明抛出IOException}
}class Child extends Parent {// 正确:抛出更具体的异常@Overridepublic void method() throws FileNotFoundException {}// 错误:抛出更宽泛的异常/*@Overridepublic void method() throws Exception {}*/
}
二、Object类核心方法
2.1 clone()方法
用于创建对象的副本,实现对象克隆。使用时需要:
- 类实现
Cloneable
接口(标记接口) - 重写
clone()
方法
class Person implements Cloneable {private String name;private int age;public Person(String name, int age) {this.name = name;this.age = age;}// 重写clone方法@Overrideprotected Person clone() throws CloneNotSupportedException {return (Person) super.clone();}@Overridepublic String toString() {return "Person{name='" + name + "', age=" + age + "}";}
}public class CloneDemo {public static void main(String[] args) {Person p1 = new Person("张三", 20);try {Person p2 = p1.clone();System.out.println(p1);System.out.println(p2);System.out.println(p1 == p2); // false,不同对象} catch (CloneNotSupportedException e) {e.printStackTrace();}}
}
2.2 finalize()方法
已过时,用于在对象被垃圾回收前执行清理工作。不推荐使用,因为无法保证执行时机。
2.3 final、finally、finalize区别
final
:修饰类(不可继承)、方法(不可重写)、变量(不可修改)finally
:与try-catch搭配使用,无论是否异常都会执行finalize
:Object类的方法,用于垃圾回收前的清理工作(已过时)
三、常用类详解
3.1 包装类
包装类是Java为每种基本数据类型提供的对象封装类,它们将基本数据类型封装成对象,从而可以在面向对象的环境中操作基本数据类型,并提供了丰富的操作方法。
包装类对照表:
基本类型 | 包装类 | 说明 |
---|---|---|
byte | Byte | 8位二进制整数封装类 |
short | Short | 16位整数封装类 |
int | Integer | 32位整数封装类 |
long | Long | 64位整数封装类 |
float | Float | 32位单精度浮点数封装类 |
double | Double | 64位双精度浮点数封装类 |
char | Character | 16位Unicode字符封装类 |
boolean | Boolean | 布尔值封装类 |
包装类特点详解:
-
不可变性:
- 包装类对象一旦创建,其值就不能改变。
- 任何修改操作都会返回一个新的对象。
- 例如:
Integer x = 5; x = x + 1;
这里实际上是创建了一个新的Integer对象
-
缓存机制:
- Integer类缓存了-128到127之间的值
- Byte、Short、Long缓存了-128到127的值
- Character缓存了0到127之间的值
- Boolean缓存了TRUE和FALSE两个值
- 当使用自动装箱创建这些范围内的对象时,会直接返回缓存对象
代码示例解析:
public class WrapperDemo {public static void main(String[] args) {// 自动装箱(Autoboxing):基本类型自动转换为包装类Integer a = 100; // 编译器自动转换为 Integer.valueOf(100)// 自动拆箱(Unboxing):包装类自动转换为基本类型int b = a; // 编译器自动转换为 a.intValue()// 缓存机制演示Integer c = 127; // 使用缓存Integer d = 127; // 使用同一个缓存对象System.out.println(c == d); // true,因为指向同一个缓存对象Integer e = 128; // 超出缓存范围,创建新对象Integer f = 128; // 创建另一个新对象System.out.println(e == f); // false,因为是两个不同的对象// 字符串与基本类型转换int num = Integer.parseInt("123"); // 字符串转intdouble dNum = Double.parseDouble("3.14"); // 字符串转double// 其他常用方法System.out.println(Integer.toBinaryString(10)); // 输出二进制表示System.out.println(Integer.toHexString(255)); // 输出十六进制表示System.out.println(Double.isNaN(0.0/0.0)); // 检查是否为非数字}
}
常见应用场景:
-
集合框架中的使用:
- Java集合类(如ArrayList、HashMap等)只能存储对象
- 需要存储基本类型时,必须使用对应的包装类
-
泛型编程:
- Java泛型不支持基本类型
- 必须使用包装类作为类型参数
-
数据库操作:
- JDBC操作中,结果集返回的数值通常使用包装类
- 可以表示SQL中的NULL值(基本类型不能表示null)
-
数值边界检查:
- 包装类提供了最大值/最小值的常量
- 例如:Integer.MAX_VALUE, Double.MIN_VALUE
-
类型转换工具:
- 提供各种类型转换方法
- 如:Integer.parseInt(), Double.toString()等
3.2 数学相关类
BigInteger类
BigInteger是Java中用于处理超大整数的类,属于java.math包。它支持任意精度的整数运算,解决了基本数据类型(int,long等)在数值范围上的限制问题。当需要处理超过long类型范围(±9223372036854775807)的整数时,BigInteger就变得非常有用。
主要特点:
- 不可变性:BigInteger对象一旦创建就不能改变
- 任意精度:理论上可以表示无限大的整数(受JVM内存限制)
- 提供完整的算术运算方法
- 线程安全(因为不可变)
常见应用场景:
- 密码学计算(如RSA加密)
- 高精度科学计算
- 金融领域的大额计算
- 处理数据库中的超大ID值
代码示例详解:
import java.math.BigInteger;public class BigIntegerDemo {public static void main(String[] args) {// 创建BigInteger对象 - 推荐使用字符串构造器BigInteger a = new BigInteger("12345678901234567890");BigInteger b = new BigInteger("98765432109876543210");// 基本运算操作BigInteger sum = a.add(b); // 加法BigInteger product = a.multiply(b); // 乘法BigInteger difference = a.subtract(b); // 减法BigInteger quotient = a.divide(b); // 除法// 其他常用操作BigInteger mod = a.mod(b); // 取模BigInteger pow = a.pow(3); // 幂运算BigInteger gcd = a.gcd(b); // 最大公约数// 比较操作int compareResult = a.compareTo(b); // 比较大小System.out.println("和:" + sum);System.out.println("积:" + product);System.out.println("a的3次方:" + pow);System.out.println("最大公约数:" + gcd);}
}
注意事项:
- 性能考虑:BigInteger运算比基本数据类型慢
- 内存消耗:大整数会占用较多内存
- 构造方法:推荐使用字符串构造器,避免数值溢出
- 常量:BigInteger提供常用常量如ZERO, ONE, TEN等
其他常用方法:
- abs():绝对值
- negate():取反
- isProbablePrime():素数测试
- bitLength():二进制位数
- toString(int radix):按指定进制输出
BigDecimal类
BigDecimal是Java中用于高精度计算的类,主要用于处理需要精确计算的金融、科学计算等场景。相比基本数据类型的double和float,BigDecimal能够避免浮点数运算中的精度丢失问题。
核心特性
- 精确计算:BigDecimal使用字符串构造数字,可以精确表示任意精度的小数
- 多种舍入模式:提供8种舍入模式(RoundingMode),包括:
- HALF_UP:四舍五入(常用)
- HALF_DOWN:五舍六入
- CEILING:向正无穷舍入
- FLOOR:向负无穷舍入
- UP:绝对值向上舍入
- DOWN:绝对值向下舍入
- HALF_EVEN:银行家舍入法
常用方法示例
import java.math.BigDecimal;
import java.math.RoundingMode;public class BigDecimalDemo {public static void main(String[] args) {// 推荐使用字符串构造BigDecimalBigDecimal a = new BigDecimal("0.1");BigDecimal b = new BigDecimal("0.2");// 基本运算BigDecimal sum = a.add(b); // 加法 0.3BigDecimal difference = a.subtract(b); // 减法 -0.1BigDecimal product = a.multiply(b); // 乘法 0.02// 除法(必须指定精度和舍入模式)BigDecimal c = new BigDecimal("1");BigDecimal d = new BigDecimal("3");BigDecimal result = c.divide(d, 2, RoundingMode.HALF_UP); // 0.33// 比较int comparison = a.compareTo(b); // -1表示小于,0表示等于,1表示大于// 设置小数位数BigDecimal pi = new BigDecimal("3.1415926");BigDecimal rounded = pi.setScale(2, RoundingMode.HALF_UP); // 3.14System.out.println("0.1 + 0.2 = " + sum);System.out.println("1/3 ≈ " + result);System.out.println("π ≈ " + rounded);}
}
最佳实践
-
构造方式:
- 优先使用字符串构造(
new BigDecimal("0.1")
) - 避免使用double构造(
new BigDecimal(0.1)
会导致精度问题)
- 优先使用字符串构造(
-
运算注意:
- 除法必须指定精度和舍入模式
- 乘法的精度是两个操作数精度之和
-
应用场景:
- 金融计算(金额、利率)
- 科学计算(需要精确结果)
- 工程计算(高精度测量)
-
性能考虑:
- BigDecimal运算比基本类型慢
- 对于不需要高精度的场景,仍可使用基本类型
扩展示例:货币计算
// 计算商品总价(含税)
BigDecimal unitPrice = new BigDecimal("19.99");
BigDecimal quantity = new BigDecimal("3");
BigDecimal taxRate = new BigDecimal("0.08"); // 8%税率BigDecimal subtotal = unitPrice.multiply(quantity);
BigDecimal tax = subtotal.multiply(taxRate).setScale(2, RoundingMode.HALF_UP);
BigDecimal total = subtotal.add(tax);System.out.println("商品总价:$" + total);
3.3 日期时间类
JDK8之前的日期类(Date、Calendar)
JDK8之前,Java主要使用java.util.Date
和java.util.Calendar
类来处理日期时间,但这些类存在设计缺陷和使用不便的问题。
import java.util.Date;
import java.util.Calendar;public class OldDateTimeDemo {public static void main(String[] args) {// Date类示例Date date = new Date(); // 创建当前时间的Date对象System.out.println("当前时间:" + date); // 输出默认格式的日期时间// Calendar类示例Calendar calendar = Calendar.getInstance(); // 获取Calendar实例// 获取年、月、日等字段int year = calendar.get(Calendar.YEAR); // 获取当前年份int month = calendar.get(Calendar.MONTH) + 1; // 获取月份(0-11,需要+1)int day = calendar.get(Calendar.DAY_OF_MONTH); // 获取当前日int hour = calendar.get(Calendar.HOUR_OF_DAY); // 获取小时(24小时制)int minute = calendar.get(Calendar.MINUTE); // 获取分钟System.out.println("当前日期:" + year + "-" + month + "-" + day);System.out.println("当前时间:" + hour + ":" + minute);// 修改日期calendar.set(2023, 11, 31); // 设置日期为2023年12月31日System.out.println("修改后的日期:" + calendar.getTime());}
}
JDK8新日期时间类(推荐使用)
JDK8引入了全新的日期时间API(java.time
包),解决了旧API的各种问题,提供了更直观、线程安全且功能丰富的日期时间处理方式。
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;public class NewDateTimeDemo {public static void main(String[] args) {// 获取当前日期时间LocalDateTime now = LocalDateTime.now(); // 获取当前日期和时间System.out.println("当前时间:" + now);// 格式化日期DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");String formattedDateTime = now.format(formatter); // 格式化为指定格式System.out.println("格式化后:" + formattedDateTime);// 日期计算LocalDate today = LocalDate.now();LocalDate tomorrow = today.plusDays(1); // 加1天LocalDate nextWeek = today.plusWeeks(1); // 加1周System.out.println("明天:" + tomorrow);System.out.println("下周:" + nextWeek);// 时间计算LocalTime currentTime = LocalTime.now();LocalTime afterTwoHours = currentTime.plusHours(2); // 加2小时System.out.println("两小时后:" + afterTwoHours);// 日期比较long daysBetween = ChronoUnit.DAYS.between(today, tomorrow);System.out.println("相差天数:" + daysBetween);// 创建特定日期LocalDate specialDate = LocalDate.of(2023, 12, 25);System.out.println("特定日期:" + specialDate);}
}
3.4 数组工具类(Arrays)
Arrays
类是 Java 提供的数组操作工具类,位于 java.util
包中,包含多种静态方法用于处理数组。它提供了数组排序、查找、复制、比较等常用操作,极大简化了数组处理的复杂度。
主要功能和方法
-
数组排序
sort()
方法:对数组进行升序排序- 可以排序基本类型数组和对象数组
- 对于对象数组,可以传入
Comparator
自定义排序规则 - 示例:
Arrays.sort(arr);
-
二分查找
binarySearch()
方法:在已排序的数组中使用二分查找算法- 返回元素索引(找到时)或负数(未找到时)
- 示例:
int index = Arrays.binarySearch(arr, key);
-
数组复制
copyOf()
方法:复制指定长度的数组copyOfRange()
方法:复制数组指定范围- 示例:
int[] copy = Arrays.copyOf(arr, newLength);
-
数组比较
equals()
方法:比较两个数组内容是否相同deepEquals()
方法:比较多维数组
-
数组填充
fill()
方法:用指定值填充整个数组或指定范围
-
数组转字符串
toString()
方法:返回数组内容的字符串表示deepToString()
方法:用于多维数组
使用示例
import java.util.Arrays;
import java.util.Comparator;public class ArraysDemo {public static void main(String[] args) {// 基本类型数组操作int[] arr = {3, 1, 4, 1, 5, 9};// 1. 排序(升序)Arrays.sort(arr);System.out.println("排序后:" + Arrays.toString(arr));// 输出:[1, 1, 3, 4, 5, 9]// 2. 二分查找(必须先排序)int index = Arrays.binarySearch(arr, 4);System.out.println("4的位置:" + index); // 输出:3// 3. 数组复制int[] copyArr = Arrays.copyOf(arr, 10); // 新长度大于原数组时,多余位置补0System.out.println("复制后的数组:" + Arrays.toString(copyArr));// 输出:[1, 1, 3, 4, 5, 9, 0, 0, 0, 0]// 4. 对象数组排序Person[] people = {new Person("张三", 20),new Person("李四", 18),new Person("王五", 22)};// 按年龄升序排序Arrays.sort(people, Comparator.comparingInt(Person::getAge));System.out.println("按年龄排序:" + Arrays.toString(people));// 5. 数组填充int[] filledArr = new int[5];Arrays.fill(filledArr, 100);System.out.println("填充后的数组:" + Arrays.toString(filledArr));// 输出:[100, 100, 100, 100, 100]}
}class Person {private String name;private int age;// 构造方法和getter/setter省略@Overridepublic String toString() {return name + "(" + age + ")";}
}
注意事项
binarySearch()
必须在已排序的数组上使用,否则结果不可预测- 对象数组排序时,要么实现
Comparable
接口,要么提供Comparator
- 数组复制时,新数组长度可以大于原数组,多余元素会填充默认值
- 多维数组操作应使用
deepToString()
和deepEquals()
方法
应用场景
- 数据处理:快速排序和搜索数组数据
- 算法实现:为各种算法提供基础数组操作
- 数据备份:复制数组内容进行备份
- 测试验证:快速生成测试数据(如填充特定值)
Arrays 类的方法都是静态的,使用时无需创建实例,直接通过类名调用即可。这些方法经过高度优化,比手动实现的相同功能通常有更好的性能。
3.5 字符串相关类
String类(不可变字符串)
String类是Java中最常用的类之一,它代表不可变的字符序列。一旦创建,String对象的内容就不能被修改,所有看似修改字符串的操作实际上都是创建了新的String对象。
public class StringDemo {public static void main(String[] args) {// 字符串创建方式String s1 = "hello"; // 字符串字面量,存储在字符串常量池String s2 = new String("world"); // 通过构造方法创建// 字符串拼接String s3 = s1 + s2; // 使用+运算符拼接字符串System.out.println(s3); // 输出:helloworld// 字符串常用方法System.out.println("长度:" + s3.length()); // 获取字符串长度System.out.println("包含hello:" + s3.contains("hello")); // 检查是否包含子串System.out.println("子串:" + s3.substring(0, 5)); // 获取子串,从索引0到5(不包括5)System.out.println("转换为大写:" + s3.toUpperCase()); // 转换为大写System.out.println("比较:" + s1.equals("hello")); // 内容比较// 字符串转换char[] chars = s1.toCharArray(); // 转换为字符数组byte[] bytes = s1.getBytes(); // 转换为字节数组,使用平台默认字符集// 字符串分割String names = "Tom,Jerry,Spike";String[] nameArray = names.split(","); // 按逗号分割// 字符串格式化String formatStr = String.format("姓名:%s,年龄:%d", "张三", 25);System.out.println(formatStr);}
}
应用场景:
- 用户输入处理:验证和格式化用户输入的字符串
- 文本处理:解析日志文件、配置文件等
- 网络通信:构建和解析HTTP请求/响应
- 数据库操作:拼接SQL语句(注意要使用预编译防止SQL注入)
注意事项:
- 字符串拼接频繁时建议使用StringBuilder提高性能
- 比较字符串内容要使用equals()方法,不要用==
- 注意字符串的编码问题,特别是在I/O操作时
- 字符串常量池可以提高内存使用效率
StringBuffer与StringBuilder(可变字符串)
Java中提供了两种可变字符串类,用于高效处理字符串修改操作:
StringBuffer
:线程安全的可变字符串类,所有方法都用synchronized
关键字修饰,适合多线程环境使用,但同步机制会带来一定的性能开销StringBuilder
:非线程安全的可变字符串类,API与StringBuffer完全兼容,在单线程环境下性能更高(推荐在单线程程序中使用)
典型应用场景对比:
- StringBuffer:适用于多线程共享字符串缓冲区的场景,如Web应用中的请求处理
- StringBuilder:适用于局部变量或方法内部的字符串操作,如拼接SQL语句、构建日志信息等
public class StringBufferDemo {public static void main(String[] args) {// StringBuilder示例 - 创建容量为16的空缓冲区StringBuilder sb = new StringBuilder();// 链式调用示例sb.append("hello").append(" ").append("world"); // 当前内容:"hello world"// 在索引5处插入逗号sb.insert(5, ","); // 结果:"hello, world"// 修改索引6处的字符为大写Wsb.setCharAt(6, 'W'); // 结果:"hello,World"System.out.println(sb.toString()); // 输出:hello,World// 反转字符串sb.reverse();System.out.println(sb.toString()); // 输出:dlroW,olleh// 其他常用方法sb.delete(0, 5); // 删除前5个字符sb.replace(0, 3, "ABC"); // 替换指定范围int capacity = sb.capacity(); // 获取当前容量sb.ensureCapacity(100); // 确保最小容量}
}
性能优化建议:
- 预估字符串最终长度,在构造时指定初始容量
- 优先使用StringBuilder的链式调用
- 避免在循环中重复创建StringBuilder对象
- 复杂字符串操作考虑使用StringJoiner或String.format()
四、JUnit单元测试
JUnit是Java最流行的单元测试框架,属于xUnit测试框架家族的一员。它主要用于对Java应用程序中的最小可测试单元(通常是方法)进行独立验证,确保代码按预期工作。JUnit遵循"测试优先"的开发理念,支持自动化测试和持续集成。
基本特性
- 注解驱动:使用@Test等注解标注测试方法
- 断言机制:提供assert系列方法验证预期结果
- 测试隔离:每个测试方法独立运行,互不影响
- 测试报告:生成详细的测试执行报告
常用注解
- @Test:标记一个方法为测试方法
- @Before:在每个测试方法前执行
- @After:在每个测试方法后执行
- @BeforeClass:在所有测试前执行(静态方法)
- @AfterClass:在所有测试后执行(静态方法)
import org.junit.*;
import static org.junit.Assert.*;public class CalculatorTest {private Calculator calc;@Beforepublic void setUp() {// 测试前置操作:创建测试对象calc = new Calculator();System.out.println("初始化Calculator实例");}// 测试加法功能@Testpublic void testAdd() {int result = calc.add(2, 3);assertEquals("加法计算结果不符预期", 5, result); // 带消息的断言// 边界值测试assertEquals(0, calc.add(0, 0));assertEquals(-1, calc.add(2, -3));}// 测试减法功能@Testpublic void testSubtract() {int result = calc.subtract(5, 2);assertEquals(3, result);// 测试负数结果assertEquals(-3, calc.subtract(2, 5));}@Test(expected = ArithmeticException.class)public void testDivideByZero() {calc.divide(5, 0); // 预期抛出除零异常}@Afterpublic void tearDown() {// 测试后置操作:释放资源calc = null;System.out.println("清理测试环境");}
}class Calculator {public int add(int a, int b) {return a + b;}public int subtract(int a, int b) {return a - b;}public int divide(int a, int b) {if(b == 0) {throw new ArithmeticException("除数不能为零");}return a / b;}
}
最佳实践
- 测试方法命名:使用test[被测方法名]的格式
- 单一职责原则:每个测试方法只测试一个功能点
- 使用有意义的断言消息:方便定位失败原因
- 包含边界条件测试:如空值、极值等特殊情况
- 测试异常场景:使用expected参数验证异常抛出
进阶用法
- 参数化测试:使用@Parameters注解实现多组数据测试
- 测试套件:使用@SuiteClasses注解组合多个测试类
- 规则扩展:使用@Rule添加自定义测试行为
- 超时测试:使用timeout参数限制测试执行时间
JUnit通常与Mockito等模拟框架配合使用,可以更好地隔离被测代码的依赖项,构建更纯粹的单元测试环境。
总结
本文详细介绍了Java中的异常处理机制和常用类,包括:
- 异常的分类、体系结构和处理方式
- Object类的核心方法及使用场景
- 包装类、数学类、日期时间类等常用API
- 数组工具类和字符串相关类的使用
- JUnit单元测试的基本用法
掌握这些知识点对于Java开发至关重要,能够帮助我们编写更健壮、高效的代码。在实际开发中,应根据具体场景选择合适的类和处理方式,遵循Java的最佳实践。