一、final关键字
final,用于表示不可更改的特性。它可以应用于类、方法和变量上。
final关键字的使用可以提供程序的安全性、性能优化以及代码的清晰性。
在设计类、方法和变量时,根据需求考虑是否使用final关键字来限制其特性。
1. 当应用于类时
final关键字表示该类是不可继承的,即不能有子类。这样可以防止其他类继承并修改原始类的行为。由于final类不允许被继承,编译器在处理时把它的所有方法都当作final的,因此final类比普通类拥更高的效率。
final class Main {
// 类的定义
}
2. 当应用于方法时
final关键字表示该方法是不可重写的,即不能在子类中对其进行覆盖实现。
class ParentClass {
final void test() {
// 方法的定义
}
}
class ChildClass extends ParentClass {
// 无法重写test()方法
}
3. 当应用于变量时
final关键字表示该变量是一个常量,一旦赋值后就不能再修改。常量通常使用大写字母表示,并在声明时进行初始化。
public class MyFinal {
// 1 被final变量如果未被初始化,编译时会报错
public final int a;
// 1 静态final变量未被初始化,编译时就会报错
public static final int a;
// ======================================================
//2. 在定义时初始化
public final int a = 1;
public final int b;
{ //2. 在初始化块中初始化,也是可以的
b = 2;
}
// ======================================================
// 3 非静态final变量不能在静态初始化块中初始化
public final int c;
static{
c=3;
}
//3 静态常量,在定义时初始化
public static final int d = 3;
//3 静态常量,在静态初始化块中初始化
public static final int e;
static {
e = 4;
}
//3 静态变量不能在初始化块中初始化
public static final int f;
{
f=60;
}
//========================================================
public final int g;
//静态final变量不可以在构造器中初始化
public static final int h;
//在构造器中初始化
public void finalTest() {
g = 70;
// 静态final变量不可以在构造器中初始化
h=80;
// 给final的变量第二次赋值时,编译会报错
a=99;
d=99;
}
}
-
对于基本数据类型的final变量,其值一旦被初始化就不能更改。
-
对于引用类型的final变量,其引用地址一旦被初始化就不能更改,但是对象自身的状态(属性)仍然可以修改。(即当final关键字修饰一个对象常量时,对象的引用不能改变,但是对象本身的状态(即对象的属性)仍然可以改变。)
例如:
class TestClass {
private final int value;
public TestClass(int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
public class Main {
public static void main(String[] args) {
final TestClass obj = new TestClass(10);
System.out.println(obj.getValue()); // 输出: 10
// obj = new TestClass(20); // 错误!无法修改引用
// 改变对象的状态
obj.setValue(20);
System.out.println(obj.getValue()); // 输出: 20
}
}
在上述示例中,我们创建了一个TestClass类,其中的value属性被声明为final。这意味着一旦在构造函数中初始化后,value属性的值将不能改变。
我们创建了一个名为obj的对象常量,并将其初始化为一个TestClass实例。在初始化后,我们不能再将obj引用指向其他对象,因为它被声明为final。
然而,虽然我们不能改变obj的引用,但我们仍然可以通过调用obj.setValue()方法来更改TestClass对象的状态。这是因为final关键字只约束了引用不可变,而不是对象本身的状态。
因此,当final关键字修饰对象常量时,对象的引用是不可变的,但对象的状态仍然可以改变。
注意
-
由关键字 abstract 定义的抽象类含必须由继承自它的子类重载实现的抽象方法,因此无法同时用final和abstract来修饰同一个类。
-
final也不能用来修饰接口。 final的类的所方法都不能被重写,但这并不表示final的类的属性(变量值也是不可改变的,要想做到final类的属性值不可改变,必须给它增加final修饰。
二、finally关键字
finally关键字,用于定义一个代码块,该代码块中的语句无论是否发生异常,都会被执行。
finally块通常与try-catch块一起使用,用于执行清理操作或确保某些代码始终得到执行。
finally块的执行不受控制流语句(如return、break、continue等)的影响,它总是会在合适的时机执行以确保代码的完整性和资源的正确释放。
1. finally块的用法
try块包含可能会抛出异常的代码,catch块用于捕获和处理异常,而finally块用于定义无论是否发生异常都要执行的代码。
try {
// 可能会抛出异常的代码块
} catch (Exception e) {
// 异常处理的代码块
} finally {
// 无论是否发生异常,都会执行的代码块
}
2. finally块的特点
-
finally块是可选的,可以与try块单独使用,不一定要有catch块。
-
finally块始终会被执行,无论是否发生异常。
-
即使在try块或catch块中遇到return语句,finally块中的代码仍然会在方法返回之前执行。
注意
嵌套的try-catch-finally块:可以存在嵌套的try-catch-finally块。在这种情况下,最内层的finally块首先执行,然后是外层的finally块,依此类推。
不可达代码:在finally块中的代码将始终执行,即使在前面的try或catch块中存在return语句。然而,finally块中的return语句可能会覆盖前面的try或catch块中的return语句。
3. finally块的常见用途
-
资源清理:finally块常用于关闭打开的资源,如文件、数据库连接或网络连接,以确保资源的正确释放。
-
收尾操作:finally块可以用于执行最终的操作,如日志记录、统计信息收集或发送通知。
-
带有返回语句的清理:如果方法中的try块包含返回语句,finally块将在执行返回语句之前执行。这允许在方法返回之前执行清理操作。
-
异常处理:finally块可以与try-catch块一起使用以处理异常。它提供了一种在捕获和处理异常后执行必要清理代码的方式。
示例
divide()方法尝试执行除法操作,如果除数为零,则会抛出ArithmeticException异常。
在main()方法中,我们使用try-catch-finally来捕获异常并执行相应的操作。
无论是否发生异常,finally块中的代码都会被执行。
public class FinallyExample {
public static void main(String[] args) {
try {
int result = divide(10, 0);
System.out.println("Result: " + result);
} catch (ArithmeticException e) {
System.out.println("Exception caught: " + e.getMessage());
} finally {
System.out.println("Finally block executed.");
}
}
public static int divide(int dividend, int divisor) {
try {
return dividend / divisor;
} catch (ArithmeticException e) {
throw e;
}
}
}
三、finalize(非关键字)
finalize是一个方法,而不是一个关键字。finalize()方法是在对象被垃圾回收(GC)之前调用的特殊方法。当一个对象不再被引用或被GC判定为可回收时,JVM会调用该对象的finalize()方法,以给予对象一个机会在被销毁之前执行一些清理或释放资源的操作。
finalize()方法虽然提供了一种在对象被垃圾回收之前执行清理操作的机会。然而,由于其执行时机的不确定性和性能开销,建议使用显式资源管理和清理代码来取代依赖于finalize()方法的资源释放。
注意:当我们调用System.gc() 方法时候,由垃圾回收器调用 finalize()方法,回收垃圾,JVM 并不保证此方法总被调用.
finalize()方法的签名如下:
protected void finalize() throws Throwable
1. 执行和执行时机
-
finalize()方法会在垃圾回收器(GC)回收对象内存之前自动调用。
-
finalize()方法的执行时机是非确定性的,取决于JVM实现和系统资源。无法保证方法一定会被调用,也无法确定调用的具体时机。
-
需要注意的是,finalize()方法的执行不是对象销毁的唯一条件。对象可能被GC回收而没有调用finalize()方法,或者在调用finalize()方法之后对象可能仍然存在。
2. 覆盖方法
-
要使用finalize()方法,需要在自定义类中覆盖该方法,并根据需要实现清理操作。
-
覆盖方法的签名必须与protected void finalize() throws Throwable一致,并且应该在方法的最后调用super.finalize()。
3. 用途和注意事项
-
资源释放:finalize()方法可以用于释放非Java对象的资源,例如打开的文件、数据库连接或网络连接。然而,由于无法确定finalize()方法的执行时机,以及其性能开销较大,推荐使用显式资源管理和清理代码来替代依赖于finalize()方法的资源释放。
-
不推荐使用:由于finalize()方法的执行时机不确定且性能开销较大,它在现代Java应用程序中并不常用。通常更好的做法是使用显式的资源管理和清理代码。
注意
覆盖finalize()方法时,应确保在方法的最后调用super.finalize(),以确保正确的清理过程。
public class MyObject {
// 覆盖finalize()方法
@Override
protected void finalize() throws Throwable {
try {
// 清理资源的操作
// ...
} finally {
super.finalize();
}
}
// 其他类成员和方法
// ...
}