你真的理解final关键字的用法吗?

final 是 Java 中的一个关键字,final从字面意思上看 “最终的","不可改变的”。它可以用来修饰变量、方法或者类,而且在修饰不同的地方时,效果、含义和侧重点也会有所不同。

(1)final修饰变量,意味着一旦被赋值就不能被修改;

(2)final修饰方法,意味着不能被重写;

(3)final修饰类,意味着不能被继承。

(1)final修饰变量

关键字 final 修饰变量,意味着这个变量一旦被赋值就不能被修改(只能被赋值一次)。如果我们尝试对一个已经赋值过 final 的变量再次赋值,就会报编译错误。

为什么要对某个变量去加 final 关键字呢?

(1)第一个目的是出于设计角度去考虑的,比如我们希望创建一个一旦被赋值就不能改变的量,那么就可以使用 final 关键字。比如声明常量的时候,通常都是带 final 的:

public static final String AUTHOR = "WHL";

(2)第二个目的是从线程安全的角度去考虑的。不可变的对象天生就是线程安全的.如果 final 修饰的是基本数据类型,那么它自然就具备了不可变这个性质,所以自动保证了线程安全。

赋值时机

被 final 修饰的变量的赋值时机,变量可以分为以下三种:

(1)成员变量,类中的非 static 修饰的属性;
(2)静态变量,类中的被 static 修饰的属性;
(3)局部变量,方法中的变量。

成员变量

成员变量指的是一个类中的非 static 属性,对于这种成员变量而言,被 final 修饰后,它有三种赋值时机(或者叫作赋值途径)。

 需要注意的是,这里讲了三种赋值时机,我们必须从中挑一种来完成对 final 变量的赋值。如果是普通变量,可以不用在这三种情况下赋值,完全可以在其他的时机赋值;或者如果你不准备使用这个变量,那么自始至终不赋值甚至也是可以的。但是对于 final 修饰的成员变量而言,必须在三种情况中任选一种来进行赋值,而不能一种都不挑、完全不赋值,那是不行的,这是 final 语法所规定的。否则会报错如下:

空白 final

下面讲解一种概念:“空白 final”。如果我们声明了 final 变量之后,并没有立刻在等号右侧对它赋值,这种情况就被称为“空白 final”。这样做的好处在于增加了 final 变量的灵活性,比如可以在构造函数中根据不同的情况,对 final 变量进行不同的赋值,这样的话,被 final 修饰的变量就不会变得死板,同时又能保证在赋值后保持不变。我们用下面这个代码来说明:

/**
 * 描述:     空白final提供了灵活性
 */
public class BlankFinal {

    //空白final
    private final int a;

    //不传参则把a赋值为默认值0
    public BlankFinal() {
        this.a = 0;
    }

    //传参则把a赋值为传入的参数
    public BlankFinal(int a) {
        this.a = a;
    }
}

在这个代码中,我们有一个 private final 的 int 变量叫作 a,该类有两个构造函数,第一个构造函数是把 a 赋值为 0,第二个构造函数是把 a 赋值为传进来的参数,所以你调用不同的构造函数,就会有不同的赋值情况。这样一来,利用这个规则,我们就可以根据业务去给 final 变量设计更灵活的赋值逻辑。所以利用空白 final 的一大好处,就是可以让这个 final 变量的值并不是说非常死板,不是绝对固定的,而是可以根据情况进行灵活的赋值,只不过一旦赋值后,就不能再更改了。

静态变量

静态变量是类中的 static 属性,它被 final 修饰后,只有两种赋值时机。

 需要注意的是,我们不能用普通的非静态初始代码块来给静态的 final 变量赋值。同样有一点比较特殊的是,这个 static 的 final 变量不能在构造函数中进行赋值。因为被static修饰的静态变量为类变量,在类初次加载时会被初始化。只能定义在类的内部、方法的外部,不能在方法中进行声明,不论是静态方法还是非静态方法。而非静态变量是对象所拥有的,在创建对象的时候通过构造函数初始化。

局部变量

局部变量指的是方法中的变量,如果你把它修饰为了 final,它的含义依然是一旦赋值就不能改变。

但是它的赋值时机和前两种变量是不一样的,因为它是在方法中定义的,所以它没有构造函数,也同样不存在初始代码块,所以对应的这两种赋值时机就都不存在了。实际上,对于 final 的局部变量而言,它是不限定具体赋值时机的,只要求我们在使用之前必须对它进行赋值即可(方法中的普通变量的要求一样),否则使用一个未赋值的变量,自然会报错

 特殊用法:final 修饰参数

关键字 final 还可以用于修饰方法中的参数。在方法的参数列表中是可以把参数声明为 final 的,这意味着我们没有办法在方法内部对这个参数进行修改。例如:

/**
 *  final参数
 */
public class FinalPara {
    public void withFinal(final int a) {
        System.out.println(a);//可以读取final参数的值
//        a = 9; //编译错误,不允许修改final参数的值
    }
}

在这个代码中有一个 withFinal 方法,而且这个方法的入参 a 是被 final 修饰的。接下来,我们首先把入参的 a 打印出来,这是允许的,意味着我们可以读取到它的值;但是接下来我们假设想在方法中对这个 a 进行修改,比如改成 a = 9,这就会报编译错误,因此不允许修改 final 参数的值。

以上我们就把 final 修饰变量的情况都讲完了,其核心可以用一句话总结:一旦被赋值就不能被修改了。

(2)final 修饰方法

目前我们使用 final 去修饰方法的唯一原因,就是想把这个方法锁定,意味着任何继承类都不能修改这个方法的含义,也就是说,被 final 修饰的方法不可以被重写,不能被 override。我们来举一个代码的例子:

 (3)final 修饰类

final 修饰类的含义很明确,就是这个类“不可被继承”。我们举个代码例子:

如果我们这样设计,就代表不但我们自己不会继承这个类,也不允许其他人来继承,它就不可能有子类的出现,这在一定程度上可以保证线程安全。比如非常经典的 String 类就是被 final 修饰的,所以我们自始至终也没有看到过哪个类是继承自 String 类的,这对于保证 String 的不可变性是很重要的。但这里有个注意点,假设我们给某个类加上了 final 关键字,这并不代表里面的成员变量自动被加上 final。事实上,这两者之间不存在相互影响的关系,也就是说,类是 final 的,不代表里面的属性就会自动加上 final。不过我们也记得,final 修饰方法的含义就是这个方法不允许被重写,而现在如果给这个类都加了 final,那这个类连子类都不会有,就更不可能发生重写方法的情况。所以,其实在 final 的类里面,所有的方法,不论是 public、private 还是其他权限修饰符修饰的,都会自动的、隐式的被指定为是 final 修饰的

如果必须使用 final 方法或类,请说明原因

这里有一个注意点,那就是如果我们真的要使用 final 类或者方法的话,需要注明原因。为什么呢?因为未来代码的维护者,他可能不是很理解为什么我们在这里使用了 final,因为使用后,对他来说是有影响的,比如用 final 修饰方法,那他就不能去重写了,或者说我们用 final 修饰了类,那他就不能去继承了。所以为了防止后续维护者有困惑,我们其实是有必要或者说有义务说明原因,这样也不至于发生后续维护上的一些问题。在很多情况下,我们并需要不急着把这个类或者方法声明为 final,可以到开发的中后期再去决定这件事情,这样的话,我们就能更清楚的明白各个类之间的交互方式,或者是各个方法之间的关系。所以你可能会发现根本就不需要去使用 final 来修饰,或者不需要把范围扩得太大,我们可以重构代码,把 final 应用在更小范围的类或方法上,这样造成更小的影响。

为什么String要用final修饰?这样设计有什么好处呢?

使用 final 修饰的第一个好处是安全;第二个好处是高效

(1)第一个好处是安全,Java 语言之父 詹姆斯·高斯林 (James Gosling),说迫使String类设计成不可变的另一个原因是安全,当你在调用其他方法时,比如调用一些系统级操作指令之前,可能会有一系列校验,如果是可变类的话,可能在你校验过后,它的内部的值又被改变了,这样有可能会引起严重的系统崩溃问题,这是迫使 String 类设计成不可变类的一个重要原因。(这是James Gosling在2001年5月的一次访谈中,谈到了不可变类和String)

(2)第二个好处是高效,以 JVM 中的字符串常量池来举例,只有字符串是不可变时,我们才能实现字符串常量池,字符串常量池可以为我们缓存字符串,提高程序的运行效率;

常见面试题

(1) final的三种用法?

(2) 为什么String要用final修饰?为什么被设计为不可变的?这样设计有什么好处呢?

希望本文章对您有帮助,您的转发、点赞是我的创作动力,十分感谢。

扫描下方二维码关注微信公众号,您会收到更多优质文章推送。

希望本文章对您有帮助,您的转发、点赞是我的创作动力,十分感谢。更多好文推荐,请关注我的微信公众号--JustJavaIt
原文地址:https://www.cnblogs.com/liaowenhui/p/12497108.html