java这三个一定要区分清楚final, finally, finalize

422 阅读8分钟

一、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;
    }
}

例如

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();
        }
    }

    // 其他类成员和方法
    // ...
}