1.Java中的final、finally和finalize有什么区别?
1.1 final
final
是一个关键字,用于修饰类、方法和变量,表示“不可改变的”。
用法:
修饰变量:表示变量一旦赋值后,其值不能被修改(常量)。
final int x = 10;
// x = 20; // 编译错误,x 不可修改
修饰方法:表示方法不能被子类重写。
class Parent {final void display() {System.out.println("This is a final method.");}
}class Child extends Parent {// void display() { } // 编译错误,不能重写 final 方法
}
修饰类:表示类不能被继承。
final class FinalClass { }
// class SubClass extends FinalClass { } // 编译错误,不能继承 final 类
1.2 finally
finally
是一个关键字,用于异常处理中的try-catch-finally
块。无论是否发生异常,finally
块中的代码都会执行。
用法:
try {// 可能抛出异常的代码int result = 10 / 0;
} catch (ArithmeticException e) {System.out.println("Exception caught: " + e.getMessage());
} finally {System.out.println("This will always execute.");
}//示例
public int testFinally() {try {return 1;} catch (Exception e) {return 2;} finally {return 3; // 最终返回 3}
}
特点:
finally
块通常用于释放资源(如关闭文件、数据库连接等)。即使
try
或catch
块中有return
语句,finally
块也会执行。如果
finally
块中有return
语句,它会覆盖try
或catch
中的return
。
1.3 finalize
finalize
是Object
类中的一个方法,用于在垃圾回收器回收对象之前执行一些清理操作。
用法:
class MyClass {@Overrideprotected void finalize() throws Throwable {try {System.out.println("Finalize method called.");} finally {super.finalize();}}
}public class Main {public static void main(String[] args) {MyClass obj = new MyClass();obj = null; // 使对象成为垃圾System.gc(); // 建议 JVM 进行垃圾回收}
}
特点:
finalize
方法在对象被垃圾回收之前调用。它通常用于释放非内存资源(如文件句柄、网络连接等)。
finalize
的执行时间不确定,甚至可能永远不会执行,因此不推荐依赖它来释放关键资源。
注意事项:
Java 9 开始,
finalize
方法被标记为deprecated
,推荐使用Cleaner
或PhantomReference
替代。
总结
-
final
:用于定义不可变的变量、方法或类。 -
finally
:用于确保某些代码无论是否发生异常都会执行。 -
finalize
:用于对象销毁前的清理操作,但不推荐使用。
2. Java中的== 和equals() 方法有什么区别?
2.1 ==
==
是一个比较运算符,用于比较两个变量的值。
行为:
对于基本数据类型(如 int
、char
、boolean
等):比较的是它们的值。
int a = 10;
int b = 10;
System.out.println(a == b); // true,因为值相等
对于引用数据类型(如对象、数组等):比较的是它们的内存地址(即是否指向同一个对象)。
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1 == s2); // false,因为 s1 和 s2 指向不同的对象
2.2 equals() 方法
equals()
是Object
类中的一个方法,用于比较两个对象的内容是否相等。
行为:
默认实现:在 Object
类中,equals()
方法的行为与 ==
相同,比较的是内存地址。
public boolean equals(Object obj) {return (this == obj);
}
重写实现:大多数 Java 类(如 String
、Integer
等)都重写了 equals()
方法,用于比较对象的内容。
String s1 = new String("hello");
String s2 = new String("hello");
System.out.println(s1.equals(s2)); // true,因为内容相等
重写
equals()
的规则:
自反性:
x.equals(x)
必须返回true
。对称性:如果
x.equals(y)
返回true
,那么y.equals(x)
也必须返回true
。传递性:如果
x.equals(y)
和y.equals(z)
都返回true
,那么x.equals(z)
也必须返回true
。一致性:多次调用
x.equals(y)
应该始终返回相同的结果。非空性:
x.equals(null)
必须返回false
。
//示例
class Person {private String name;private int age;public Person(String name, int age) {this.name = name;this.age = age;}@Overridepublic boolean equals(Object obj) {if (this == obj) return true; // 如果是同一个对象if (obj == null || getClass() != obj.getClass()) return false; // 如果对象为空或类型不同Person person = (Person) obj;return age == person.age && name.equals(person.name); // 比较内容}
}
特性 | == | equals() |
类型 | 运算符 | 方法 |
比较对象 | 基本类型和引用类型 | 引用类型 |
比较内容 | 基本类型:值;引用类型:地址 | 对象的内容(需重写 equals() ) |
默认行为 | 比较值或地址 | 比较地址(除非重写) |
注意事项:
-
字符串比较:
-
使用
==
比较字符串时,可能会因为字符串常量池的优化导致意外的结果。 -
推荐使用
equals()
比较字符串内容。String s1 = "hello"; String s2 = "hello"; System.out.println(s1 == s2); // true,因为字符串常量池优化
-
-
重写
equals()
时必须重写hashCode()
:-
如果两个对象通过
equals()
比较相等,那么它们的hashCode()
也必须相等。 -
这是为了确保在使用哈希表(如
HashMap
)时的一致性。
-
总结
-
==
:比较值(基本类型)或内存地址(引用类型)。 -
equals()
:比较对象的内容(需重写equals()
方法)。 -
在大多数情况下,引用类型的比较应该使用
equals()
,而不是==
。
3.Java中的String为什么是不可变的?
在 Java 中,String
是不可变的(immutable),这意味着一旦一个 String
对象被创建,它的值就不能被修改。这种设计是 Java 语言的核心特性之一,具有重要的优点和意义。
3.1 什么是不可变性?
不可变对象:对象的状态(数据)在创建后不能被修改。
String
的不可变性:一旦一个String
对象被创建,它的字符序列(内容)就不能被改变。任何对String
的修改操作(如拼接、替换等)都会创建一个新的String
对象。
3.2 String不可变的原因
(1)安全性
-
字符串常量池:Java 使用字符串常量池(String Pool)来存储字符串字面量。如果
String
是可变的,那么多个引用指向同一个字符串时,修改一个引用会导致其他引用的值也被修改,从而引发安全问题。
String s1 = "hello";
String s2 = "hello"; // s1 和 s2 指向字符串常量池中的同一个对象
// 如果 String 是可变的,修改 s1 会影响 s2//安全性示例:
String username = "admin";
String query = "SELECT * FROM users WHERE username = '" + username + "'";
// 如果 String 是可变的,恶意代码可能会修改 username 的值,导致 SQL 注入
(2)线程安全
-
不可变对象天生是线程安全的,因为它们的状态不能被修改。多个线程可以共享同一个
String
对象,而不需要额外的同步机制。
String s = "hello";
// 多个线程可以安全地共享 s,无需担心数据竞争
(3)哈希码缓存
-
String
类重写了hashCode()
方法,用于计算字符串的哈希值。由于String
是不可变的,它的哈希值在创建时就可以计算并缓存,后续调用hashCode()
时可以直接返回缓存值,提高性能。
String s = "hello";
int hash = s.hashCode(); // 哈希值被缓存
(4)字符串常量池优化
-
Java 使用字符串常量池来存储字符串字面量。如果
String
是可变的,那么常量池中的字符串可能会被修改,导致其他引用该字符串的代码出现意外行为。
String s1 = "hello";
String s2 = "hello"; // s1 和 s2 指向常量池中的同一个对象
// 如果 String 是可变的,修改 s1 会影响 s2
(5)性能优化
-
不可变性使得编译器可以进行一些优化,例如字符串字面量的复用。
-
例如,
String
的substring()
、concat()
等方法可以共享原始字符串的字符数组,而不需要复制数据。
3.3 String不可变的实现
(1)String
类的定义
String
类的核心字段是一个 final
的字符数组 value
,它在对象创建后不能被修改。
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {private final char value[]; // 存储字符串内容的字符数组private int hash; // 缓存哈希值// 其他方法...
}
(2)final
关键字
-
String
类被声明为final
,防止被继承和重写。 -
value
数组被声明为final
,防止被重新赋值。
(3)修改操作创建新对象
任何对 String
的修改操作(如拼接、替换等)都会创建一个新的 String
对象,而不是修改原始对象。
String s1 = "hello";
String s2 = s1.concat(" world"); // 创建一个新的 String 对象
System.out.println(s1); // 输出 "hello"(原始对象未被修改)
System.out.println(s2); // 输出 "hello world"
总结
-
String
不可变的原因:安全性、线程安全、哈希码缓存、字符串常量池优化、性能优化。 -
不可变性的优点:安全、线程安全、性能优化。
-
不可变性的缺点:内存开销、性能问题(可通过
StringBuilder
或StringBuffer
解决)。
4.Java中的 StringBuilder 和 StringBuffer 有什么区别?
StringBuilder
和 StringBuffer
是 Java 中用于处理可变字符串的两个类,它们的主要区别在于线程安全性和性能。
4.1 线程安全性
-
StringBuffer:是线程安全的,它的方法大多使用
synchronized
关键字进行同步,因此多个线程可以安全地操作同一个StringBuffer
对象。 -
StringBuilder:不是线程安全的,它的方法没有使用同步机制,因此在多线程环境下使用可能会导致数据不一致的问题。
4.2 性能
-
StringBuilder:由于没有同步开销,
StringBuilder
的性能通常比StringBuffer
更高。在单线程环境下,推荐使用StringBuilder
。 -
StringBuffer:由于同步机制的存在,
StringBuffer
的性能相对较低。只有在多线程环境下需要确保线程安全时,才推荐使用StringBuffer
。
4.3 使用场景
-
单线程环境:使用
StringBuilder
,因为它性能更好。 -
多线程环境:使用
StringBuffer
,因为它提供了线程安全的操作。
4.4 共同点
-
两者都继承自
AbstractStringBuilder
类。 -
两者都提供了类似的方法,如
append()
、insert()
、delete()
、reverse()
等。
//示例代码
// 使用 StringBuilder
StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(" World");
System.out.println(sb.toString()); // 输出: Hello World// 使用 StringBuffer
StringBuffer sbf = new StringBuffer();
sbf.append("Hello");
sbf.append(" World");
System.out.println(sbf.toString()); // 输出: Hello World
总结
-
StringBuilder:适用于单线程环境,性能更高。
-
StringBuffer:适用于多线程环境,线程安全但性能较低。
5.Java中的 ArrayList 和 LinkedList 有什么区别?
在Java中,ArrayList
和 LinkedList
是两种常用的集合类,它们都实现了 List
接口,但底层实现和性能特点有所不同。以下是它们的主要区别:
5.1 底层数据结构
ArrayList:基于动态数组实现。数组在内存中是连续存储的,因此可以通过索引快速访问元素。
LinkedList:基于双向链表实现。链表中的每个元素(节点)都包含数据和指向前后节点的指针,因此在内存中是非连续存储的。
5.2 访问性能
ArrayList:由于基于数组,支持随机访问,通过索引访问元素的时间复杂度为 O(1)。
LinkedList:由于基于链表,访问元素需要从头或尾遍历链表,时间复杂度为 O(n)。
5.3 插入和删除性能
ArrayList:
在末尾插入或删除元素的时间复杂度为 O(1)。
在中间或开头插入或删除元素时,需要移动后续元素,时间复杂度为 O(n)。
LinkedList:
在任意位置插入或删除元素的时间复杂度为 O(1)(前提是已经定位到插入或删除的位置)。
如果需要先找到插入或删除的位置,时间复杂度为 O(n)。
5.4 内存占用
ArrayList:内存占用较少,因为只需要存储元素和数组的容量。
LinkedList:内存占用较多,因为每个节点都需要存储前后节点的指针。
5.5 适用场景
ArrayList:
适合频繁访问元素的场景。
适合在列表末尾进行插入和删除操作。
LinkedList:
适合频繁在列表中间或开头进行插入和删除操作。
适合实现队列或栈等数据结构。
5.6 其他特性
ArrayList:
支持动态扩容,默认初始容量为10,扩容时容量增加50%。
可以使用
trimToSize()
方法将容量调整为当前元素数量。LinkedList:
实现了
Deque
接口,可以用作双端队列。提供了更多的方法,如
addFirst()
、addLast()
、removeFirst()
、removeLast()
等。
总结
-
如果需要频繁访问元素,选择 ArrayList。
-
如果需要频繁在列表中间或开头插入或删除元素,选择 LinkedList。
6. Java中的 HashMap 和 Hashtable 有什么区别?
在Java中,HashMap
和 Hashtable
都是用于存储键值对的集合类,它们都实现了 Map
接口,但在实现细节和特性上有一些重要区别。以下是它们的主要区别:
6.1 线程安全性
HashMap:非线程安全。在多线程环境下,如果没有额外的同步措施,可能会导致数据不一致。如果需要线程安全,可以使用
Collections.synchronizedMap(new HashMap<>())
或者使用ConcurrentHashMap
。Hashtable:线程安全。它的方法都是同步的(使用
synchronized
关键字),因此在多线程环境下可以直接使用,但性能较低。
6.2 性能
HashMap:由于没有同步开销,性能通常比
Hashtable
高。Hashtable:由于方法都是同步的,性能较低,尤其是在高并发环境下。
6.3 Null 键和 Null 值
HashMap:允许 一个
null
键和多个null
值。Hashtable:不允许
null
键或null
值,否则会抛出NullPointerException
。
6.4 继承和实现
HashMap:继承自
AbstractMap
类,实现了Map
接口。Hashtable:继承自
Dictionary
类,实现了Map
接口。
6.5 初始容量和扩容机制
HashMap:默认初始容量为 16,扩容时容量变为原来的 2 倍。
Hashtable:默认初始容量为 11,扩容时容量变为原来的 2 倍加 1。
6.6 迭代器
HashMap:使用
Iterator
进行遍历,并且是 fail-fast 的(如果在迭代过程中集合被修改,会抛出ConcurrentModificationException
)。Hashtable:使用
Enumeration
进行遍历,不是 fail-fast 的。
6.7 使用场景
HashMap:适用于大多数场景,尤其是在单线程环境下。如果需要线程安全,可以使用
ConcurrentHashMap
。Hashtable:由于其同步特性,适用于需要线程安全的场景,但通常推荐使用
ConcurrentHashMap
,因为它的性能更好。
6.8 历史
Hashtable:是 Java 早期版本中的类,属于遗留类。
HashMap:是 Java 集合框架的一部分,设计更加现代和灵活。
总结
-
如果需要线程安全的 Map,推荐使用
ConcurrentHashMap
而不是Hashtable
。 -
在单线程环境下,
HashMap
是更好的选择,因为它性能更高且更灵活。
7. Java中的 HashSet 和 TreeSet 有什么区别?
在Java中,HashSet
和 TreeSet
都是实现了 Set
接口的集合类,用于存储不重复的元素。它们的底层实现和特性有显著区别,以下是它们的主要区别:
7.1 底层数据结构
HashSet:
基于 哈希表(
HashMap
)实现。使用哈希算法来存储元素,元素的存储顺序与插入顺序无关。
TreeSet:
基于 红黑树(一种自平衡的二叉搜索树)实现。
元素按照自然顺序或自定义比较器排序。
7.2 元素顺序
HashSet:
不保证元素的存储顺序,元素的顺序可能与插入顺序不一致。
由于基于哈希表,元素的顺序取决于哈希函数和哈希桶的分布。
TreeSet:
元素按照升序排序(自然顺序或自定义比较器)。
支持有序遍历。
7.3 性能
HashSet:
添加、删除和查找操作的平均时间复杂度为 O(1)。
性能受哈希冲突的影响,但在理想情况下性能非常高。
TreeSet:
添加、删除和查找操作的平均时间复杂度为 O(log n),因为需要维护红黑树的平衡。
性能比
HashSet
稍低,但支持有序操作。
7.4 Null 值支持
HashSet:
允许存储 一个
null
值。TreeSet:
不允许存储
null
值,否则会抛出NullPointerException
(因为需要比较元素大小,而null
无法比较)。
7.5 排序
HashSet:
不支持排序,元素存储顺序不确定。
TreeSet:
支持自然排序(元素必须实现
Comparable
接口)或通过自定义Comparator
排序。
7.6 线程安全性
-
HashSet 和 TreeSet 都是非线程安全的。如果需要在多线程环境下使用,可以使用
Collections.synchronizedSet()
方法包装它们,或者使用ConcurrentSkipListSet
(TreeSet
的线程安全替代)。
7.7 使用场景
HashSet:
适用于需要快速查找、插入和删除的场景。
不关心元素的顺序。
TreeSet:
适用于需要元素有序的场景。
支持范围查找(如
subSet()
、headSet()
、tailSet()
等方法)。
//HashSet示例
Set<String> hashSet = new HashSet<>();
hashSet.add("Apple");
hashSet.add("Banana");
hashSet.add("Cherry");
System.out.println(hashSet); // 输出顺序不确定,可能是 [Apple, Cherry, Banana]
//TreeSet示例
Set<String> treeSet = new TreeSet<>();
treeSet.add("Apple");
treeSet.add("Banana");
treeSet.add("Cherry");
System.out.println(treeSet); // 输出顺序为自然排序 [Apple, Banana, Cherry]
总结
特性 | HashSet | TreeSet |
底层数据结构 | 哈希表 | 红黑树 |
元素顺序 | 无序 | 有序(自然排序或自定义排序) |
性能 | O(1)(平均) | O(log n) |
Null 值支持 | 允许一个 null | 不允许 null |
排序支持 | 不支持 | 支持 |
使用场景 | 快速查找、插入、删除 | 需要有序集合 |
8. Java中的 static 关键字有什么作用?
在Java中,static
是一个非常重要的关键字,用于修饰类的成员(变量、方法、代码块和内部类)。它的主要作用是使被修饰的成员与类本身关联,而不是与类的实例关联。
8.1 静态变量(Static Variables)
-
作用:
-
静态变量属于类,而不是类的实例。
-
所有实例共享同一个静态变量。
-
静态变量在类加载时初始化,且在程序运行期间只有一份内存空间。
-
//语法
static 数据类型 变量名;//示例
class Counter {static int count = 0; // 静态变量Counter() {count++; // 每次创建实例时,count 增加}
}public class Main {public static void main(String[] args) {Counter c1 = new Counter();Counter c2 = new Counter();System.out.println(Counter.count); // 输出 2}
}
8.2 静态方法(Static Methods)
-
作用:
-
静态方法属于类,而不是类的实例。
-
可以直接通过类名调用,无需创建类的实例。
-
静态方法中不能直接访问非静态成员(变量或方法),因为非静态成员依赖于实例。
-
//语法
static 返回类型 方法名(参数列表) {// 方法体
}//示例
class MathUtils {static int add(int a, int b) {return a + b;}
}public class Main {public static void main(String[] args) {int result = MathUtils.add(5, 3); // 直接通过类名调用System.out.println(result); // 输出 8}
}
8.3 静态代码块(Static Block)
-
作用:
-
静态代码块在类加载时执行,且只执行一次。
-
通常用于初始化静态变量或执行一些只需要运行一次的操作。
-
//语法
static {// 代码块
}//示例
class Test {static int num;static {num = 10; // 初始化静态变量System.out.println("静态代码块执行");}
}public class Main {public static void main(String[] args) {System.out.println(Test.num); // 输出 10}
}
8.4 静态内部类(Static Nested Class)
-
作用:
-
静态内部类是类的一个静态成员。
-
它不依赖于外部类的实例,可以直接创建。
-
静态内部类不能直接访问外部类的非静态成员。
-
//语法
class OuterClass {static class StaticNestedClass {// 类体}
}//示例
class Outer {static class Inner {void display() {System.out.println("静态内部类方法");}}
}public class Main {public static void main(String[] args) {Outer.Inner inner = new Outer.Inner(); // 直接创建静态内部类实例inner.display(); // 输出 "静态内部类方法"}
}
8.5 静态导入(Static Import)
-
作用:
-
允许直接使用静态成员(变量或方法)而无需通过类名引用。
-
可以提高代码的可读性,但过度使用可能导致命名冲突。
-
//语法
import static 包名.类名.静态成员;//示例
import static java.lang.Math.PI;
import static java.lang.Math.pow;public class Main {public static void main(String[] args) {double radius = 5.0;double area = PI * pow(radius, 2); // 直接使用 PI 和 powSystem.out.println("面积: " + area);}
}
8.6 静态成员的特点
类级别:静态成员属于类,而不是实例。
共享性:所有实例共享同一个静态成员。
生命周期:静态成员在类加载时初始化,在程序结束时销毁。
访问限制:
静态方法中不能直接访问非静态成员。
非静态方法可以访问静态成员。
8.7 使用场景
静态变量:用于存储类级别的数据,例如计数器、配置信息等。
静态方法:用于工具类或不需要实例化的操作,例如
Math
类中的方法。静态代码块:用于初始化静态变量或加载资源。
静态内部类:用于与外部类解耦,或者当内部类不需要访问外部类实例时。
总结
特性 | 静态成员(static) | 非静态成员(实例成员) |
所属对象 | 类 | 实例 |
内存分配 | 类加载时分配 | 实例创建时分配 |
共享性 | 所有实例共享 | 每个实例独立 |
访问方式 | 通过类名访问 | 通过实例访问 |
生命周期 | 类加载到程序结束 | 实例创建到实例销毁 |
9.Java中的 this 和 super 关键字有什么区别?
在Java中,this
和 super
是两个常用的关键字,它们都与对象的引用相关,但用途和行为有所不同。
9.1 this
关键字
-
作用:
-
用于引用当前对象的实例。
-
可以访问当前类的成员变量、方法和构造器。
-
-
主要用途:
-
区分成员变量和局部变量:
当成员变量与局部变量同名时,使用this
明确引用成员变量。class Person {String name;Person(String name) {this.name = name; // 使用 this 区分成员变量和参数} }
-
调用当前类的其他构造器:
在一个构造器中调用另一个构造器,使用this()
。class Person {String name;int age;Person(String name) {this.name = name;}Person(String name, int age) {this(name); // 调用当前类的其他构造器this.age = age;} }
-
传递当前对象:
将当前对象作为参数传递给其他方法。
-
class Printer {void print(Person person) {System.out.println(person.name);}
}
class Person {String name;void display(Printer printer) {printer.print(this); // 传递当前对象}
}
9.2 super
关键字
-
作用:
-
用于引用父类的实例。
-
可以访问父类的成员变量、方法和构造器。
-
-
主要用途:
-
调用父类的构造器:
在子类构造器中调用父类的构造器,使用super()
。class Animal {String name;Animal(String name) {this.name = name;} } class Dog extends Animal {Dog(String name) {super(name); // 调用父类构造器} }
-
访问父类的成员变量或方法:
当子类重写了父类的方法或隐藏了父类的成员变量时,使用super
访问父类的成员。class Animal {void sound() {System.out.println("Animal sound");} } class Dog extends Animal {void sound() {super.sound(); // 调用父类方法System.out.println("Dog sound");} }
-
9.3 this
和 super
的区别
特性 | this | super |
引用对象 | 当前对象 | 父类对象 |
访问范围 | 当前类的成员变量、方法和构造器 | 父类的成员变量、方法和构造器 |
调用构造器 | 使用 this() 调用当前类的其他构造器 | 使用 super() 调用父类的构造器 |
使用场景 | 区分成员变量和局部变量、传递当前对象 | 调用父类构造器、访问父类成员 |
是否必须第一行 | 调用 this() 时必须在构造器的第一行 | 调用 super() 时必须在构造器的第一行 |
9.4 注意事项
构造器中的调用顺序:
在子类构造器中,
super()
或this()
必须是第一行代码。如果子类构造器中没有显式调用
super()
或this()
,编译器会自动插入super()
(调用父类的无参构造器)。如果父类没有无参构造器,子类必须显式调用父类的有参构造器。
不能同时使用
this()
和super()
:
在一个构造器中,
this()
和super()
不能同时出现,因为它们都必须在第一行。静态上下文:
this
和super
不能在静态方法或静态代码块中使用,因为它们依赖于实例。
9.5 示例对比
使用 this
class Person {String name;Person(String name) {this.name = name; // 使用 this 区分成员变量和参数}void display() {System.out.println("Name: " + this.name); // 使用 this 引用当前对象}
}
使用 super
class Animal {void sound() {System.out.println("Animal sound");}
}
class Dog extends Animal {void sound() {super.sound(); // 调用父类方法System.out.println("Dog sound");}
}
总结
-
this
:用于引用当前对象,主要解决变量名冲突、调用当前类的其他构造器或传递当前对象。 -
super
:用于引用父类对象,主要解决子类调用父类构造器或访问父类成员的问题。
10. Java中的 try-catch-finally 块是如何工作的?
在Java中,try-catch-finally
是用于异常处理的关键结构。它的主要作用是捕获和处理代码中可能发生的异常,同时确保无论是否发生异常,某些必要的清理操作都能执行。
10.1 基本语法
try {// 可能抛出异常的代码
} catch (ExceptionType1 e1) {// 处理 ExceptionType1 类型的异常
} catch (ExceptionType2 e2) {// 处理 ExceptionType2 类型的异常
} finally {// 无论是否发生异常,都会执行的代码
}
10.2 各部分的作用
(1)
try
块
包含可能抛出异常的代码。
如果在
try
块中发生异常,程序会立即跳转到匹配的catch
块。(2)
catch
块
用于捕获并处理特定类型的异常。
可以有多个
catch
块,每个catch
块处理一种类型的异常。异常类型从上到下匹配,因此应该将更具体的异常类型放在前面,更通用的异常类型放在后面。
(3)
finally
块
无论是否发生异常,
finally
块中的代码都会执行。通常用于释放资源(如关闭文件、数据库连接等),确保资源不会被遗漏。
10.3 执行流程
正常执行:
如果
try
块中的代码没有抛出异常,程序会执行完try
块后跳过所有catch
块,直接执行finally
块。发生异常:
如果
try
块中的代码抛出异常,程序会立即跳转到匹配的catch
块。执行完
catch
块后,程序会继续执行finally
块。
finally
块的强制性:
即使
try
或catch
块中有return
、break
或continue
语句,finally
块仍然会执行。如果
finally
块中有return
语句,它会覆盖try
或catch
块中的return
语句。
10.4 示例代码
示例 1:基本用法
public class Main {public static void main(String[] args) {try {int result = 10 / 0; // 抛出 ArithmeticExceptionSystem.out.println("结果: " + result);} catch (ArithmeticException e) {System.out.println("捕获异常: " + e.getMessage());} finally {System.out.println("finally 块执行");}}
}//输出:
//捕获异常: / by zero
//finally 块执行
示例 2:多个 catch
块
public class Main {public static void main(String[] args) {try {int[] arr = {1, 2, 3};System.out.println(arr[5]); // 抛出 ArrayIndexOutOfBoundsException} catch (ArithmeticException e) {System.out.println("捕获算术异常: " + e.getMessage());} catch (ArrayIndexOutOfBoundsException e) {System.out.println("捕获数组越界异常: " + e.getMessage());} finally {System.out.println("finally 块执行");}}
}//输出:
//捕获数组越界异常: Index 5 out of bounds for length 3
//finally 块执行
示例 3:finally
块的强制性
public class Main {public static void main(String[] args) {System.out.println("返回值: " + test());}static int test() {try {return 1; // 返回 1,但 finally 块会覆盖返回值} finally {return 2; // finally 块中的 return 会覆盖 try 块中的 return}}
}//输出
//返回值: 2
10.5 注意事项
catch
块的顺序:
如果有多个
catch
块,应该将更具体的异常类型放在前面,更通用的异常类型(如Exception
)放在后面。如果
catch
块的顺序不正确,编译器会报错。
finally
块的作用:
即使
try
或catch
块中有return
语句,finally
块仍然会执行。如果
finally
块中有return
语句,它会覆盖try
或catch
块中的返回值。
总结
特性 | try 块 | catch 块 | finally 块 |
作用 | 包含可能抛出异常的代码 | 捕获并处理异常 | 无论是否发生异常,都会执行 |
执行顺序 | 优先执行 | 发生异常时执行 | 最后执行 |
资源管理 | 无 | 无 | 通常用于释放资源 |
强制性 | 无 | 无 | 即使有 return 也会执行 |