十、final 关键字详解

概述

final 的意思是恒定不变的,和 C 语言中的 const 关键字类似,指的是恒定不变的量。

根据其修饰的主体的不同,含义会有一定差异,Java 中一共可以修饰三种情况,即数据、方法和类。

final 数据

使用 final 修饰的数据通常是指一个常量,例如:

public final int i = 9;

上面代码中的变量 i 在运行时的值是不可变的。

使用 final 关键字有两个目的:

  1. 作为一个永不改变的编译时常量
  2. 在运行时被初始化后,永不改变

第一点中的编译时常量是指在代码中已经确定常量的值,而不是运行时进行初始化,因此该值可以在编译时用到其他计算式中进行计算,从而提高效率。

那么当我们使用 final 修饰一个引用对象时,final 限定的是对象内的成员变量不可变还是这个引用不可变呢?

答案是引用不可变,例如下面代码:

class apple {
    public String str;
    apple(String str){
        this.str = str;
        System.out.println(str);
    }
}
public class text{
    public final apple a = new apple("apple1");
    public static void main(String [] args) {
        a = new apple("apple2") // 报错,a 是 final 的,无法修改。
        a.str = "apple2"; // 通过,对象内部的变量 str 不是 final 的,因此可以改变。
    }
}

我们再来看一下,当 final 遇上 static 会发生什么呢?

使用 final 和 static 同时修饰的,其重点是在强调只有一份并且不可改变,通常这样的常量使用大写字母加下划线分隔来命名。例如:

class value {
    String str;
    value(String a) {
        this.str = a;
    }
    public String toString() {
        return str;
    }
}

public class test {
    private static Random r = new Random(47);

    public static final int VALUE_A = r.nextInt(20);
    public static final value VALUE_B = new value("value1");
    public final int value = r.nextInt(20);
    
    public static void main(String [] args) {
        test t1 = new test();
        test t2 = new test();
        t1.VALUE_B = new value("value2");// 报错,final 修饰的引用不可变
        System.out.println("t1:"+t1.VALUE_A+"--"+t1.VALUE_B+"--"+t1.value);
        System.out.println("t2:"+t2.VALUE_A+"--"+t2.VALUE_B+"--"+t2.value);
    }
}
/*output:
t1:18--value1--15
t2:18--value1--13
*/

通过上述代码可以发现,VALUE_A的值是唯一的,而value的值在不同对象中值不一样。

即带 static 的在运行时不同对象中始终是同一个值,并且不可变,不带 static 修饰的不同对象中值不同,运行时同样不可变。并且 final 修饰引用,意味着无法将该引用指向新的对象。

在 final 修饰数据的部分,还有一种特殊情况:空白 final。

空白 final 的存在提供了更大的灵活性,可以做到根据对象不同而有所不同,但又同时保持了不变的特性。

class person {
    String say;
    person(String say) {
        this.say = say;
    }
}

public class test {
    private final int a = 9;
    private final int b;// 空白 final
    private final person p1 = new person("abc");
    private final person p2;// 空白 final
    
    test(int x, person p) {
        this.b = x;
        this.p2 = p;
    }
    
    public static void main(String args) {
        test t1 = new test(10, new person("t1"));
        test t2 = new test(19, new person("t2"));
    }
}

最后,final 还可以用于修饰方法中的参数,如此可以确保该参数在方法体内不可变,通常修饰引用类型参数。

class person {
    public say() {}
}

public class test {
    void personSay(person p) {
        p = new person();// 没问题
        p.say();
    }
    void personSay1(final person p) {
        p = new person();// 报错,final 修饰的引用不可变
    }
}

final 方法

使用 final 修饰方法的原因有两个:

  1. 出于设计的考虑,将方法锁定,确保方法不被继承
  2. 提高效率,final 修饰的方法在调用处,在编译阶段会将方法体复制到调用处,因此不必额外消耗方法调用的时间,这被称为内嵌调用。

上述第二点中,如果 final 修饰的代码块很大,那么将会得不偿失,因此在Java SE5/6 以上版本中,对于 final 方法块代码很多的情况,虚拟机可以探测到这些情况,并且直接优化掉这些降低效率的内嵌调用。

因此在 Java SE5/6 以上版本时,效率问题交给虚拟机和编译器考虑,我们只需要关注第一点即可。

值得注意的是,private 修饰的方法本身就不能被子类所继承,因此隐含是 final 的,当然,我们显示的写上 final 也没问题,不过这样做非但没有任何意义,还会引起混淆。

class WithFinals {
    private final void f() {
        System.out.println("WithFinals_f()"); 
    }
    private void g() {
        System.out.println("WithFinals_g()"); 
    }
}

class test1 extends WithFinals {
    private final void f() {
        System.out.println("test1_f()"); 
    }
    private void g() {
        System.out.println("test1_g()"); 
    }
}

上述代码中,可能很容易会产生这样的疑惑:f() 函数
明明是 final 的,怎么子类中依然可以继承呢?

要弄明白这个疑惑,首先得知道子类可以重写父类的哪些方法,实际上,子类只能够重写父类接口的方法,而 private 方法是父类的私有代码的一部分,并不是父类接口的一部分。

因此,上述代码中,虽然父类和子类都存在 f() 方法,但是它们并不是重写的关系,在实际开发中,为了避免这种混淆,通常在子类中重写父类的方法前加 @Override 标注。

final 类

当某个类被标记为 final 时,表示这个类不接受任何类的继承。也就是这个类的设计不需要做任何变动,不希望它有子类。

如此一来,final 类中的方法实际上是隐式定义为了 final 方法,你可以显示标注为 final,但并没有实际意义。

final 使用注意事项

在使用 final 时,需要格外谨慎和小心,因为当你在设计一个类或方法时,可能你觉得不需要被复用,并且使用 final 修饰,但这可能会妨碍其他程序员在项目中通过继承或重写来复用你的类或方法。

值得一提的几个案例:

  1. Java 1.0/1.1 中的 Vector 类应用很广泛,如果其中的方法均没有 final 修饰,那么将会更加有用,但遗憾的是设计者不这么认为。不过令人意外的是,Stack 类却继承自 Vector 类,不知道设计者在继承的时候有没有意识到 final 方法过于严苛。
  2. Vector 类中许多重要的方法都是同步的,因此会导致很大的开销,这直接影响到了 final 带来的好处。
  3. 同样值得注意的是 Hashtable 类,它也是 Java 1.0/1.1 重要的类,却没有一个方法是 final 的。当然目前 Hashtable 已经用HashMap代替了。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值