【Java21】final修饰符

final可以用来修饰类、变量和方法。类似C++里的const,表示被修饰的类、变量和方法不可改变。具体表现为:

  • 对变量,一旦初始化之后就不可改变。final即可修饰成员变量(类变量|实例变量),也可修饰局部变量、形参。

final修饰的变量并不是不能赋值,而是不能被重新赋值。一旦获得初始值之后,就不能修改的意思。

final修饰成员变量

成员变量本身是随着类初始化或者对象初始化而初始化的。

成员变量在何种情况下会被初始化?

  • 类初始化时,系统为类变量分配内存空间并分配默认值;

    • 类初始化块代码中可以对类变量赋初始值;
  • 创建对象时,系统为实例变量分配内存空间并分配默认值;

    • 实例初始化块、构造器中可以对实例变量赋初始值。

如果不显示给final修饰的变量赋初始值,那么它将始终是默认值。那这个变量就失去了声明的意义了。因此,Java约定,final成员变量必须显式指定初始值

根据上面的情形,重新总结一下:

  1. final修饰的类变量,必须在类初始化块中赋初值,或者在声明该类变量时赋值。二者只能选其一(因为不能重复赋值)。
  2. final修饰的实例变量,必须在实例初始化块、构造器中赋初值,或者在声明的时候赋值。三者只能选其一。

注意,一定要对final修饰的成员变量显式赋初始值,而且不要在初始化之前访问它。

Java不允许在final变量初始化前直接访问它;但是,Java又允许通过方法,在初始化之前访问final修饰的成员变量。此时不会报错,但是只会输出默认值。这显然违背了final的使用初衷:让程序始终访问到固定的值的变量。

因此,我们在编程时不要在final显式初始化之前访问它。

final局部变量

局部变量必须由程序员显式初始化。因此,有两个位置可以对final修饰的局部变量赋值,一个是在定义时;一个是第一次正常赋值。

形参在调用该方法时,由外部传入的参数在初始化。因此在方法内不能对final修饰的形参赋值。

public void test(final int a)
{
  a = 5; // 非法
}
final修饰基本类型和引用类型

final修饰基本类型,就是值,一旦赋初值之后就无法改变,这很好理解。

但是,final修饰引用类型时,因为引用类型实际上是指针,final只能确保它指向的对象不变,即永远引用同一个对象。但是对象本身是可以改变的。

class Person
{
    private int age;
    public Person(){}
    public Person(int age)
    {
        this.age = age;
    }
    // 省略set和get方法
}

public class Test
{
    public static void main(String[] args)
    {
        // 使用final修饰数组,数组是引用变量
        final int[] iArr = {5,6,12,9};
        // 对数组排序,合法
        Arrays.sort(iArr);
        // 对数组元素赋值,合法
        iArr[2] = -8;
        // 但是更改iArr的值,非法
        // iArr = null;
        // ------
        // 使用final修饰对象,对象也是引用变量
        final var p = new Person();
        // 修改p指向对象的age的值,合法
        p.setAge(10);
        // 试图修改p的指向,非法
        //p = null;
    }
}
final与宏替换

若final变量,不管是类变量、实例变量还是局部变量,只要:

  • 使用final修饰;
  • 定义final的同时指定了初值;
  • 该初值在编译时就可以确定下来;

那么这个变量其实就是一个“宏变量”,编译器在编译时会直接把该变量替换成那个值。

变量不存在了!

public class FinalLocalTest
{
  public static void main(String[] args)
  {
    final var a = 5; // 满足上述3条特征
    System.out.println(a);
  }
}

第6行,编译后实际上就是System.out.println(5);

注意,上面第3条,只要在编译时能确定初值就行。也就是说,赋值运算符右边(右值)可以是表达式、字符串连接等。只要不包含访问其他变量、调用方法就可以:

public class FinalReplaceTest()
{
  public static void main(String[] args)
  {
    final var a = 5 + 2; // OK
    final var b = 1.2 / 3; // OK
    final var str = "疯狂" + "Java"; // OK
    final var book = "疯狂" + 99.0; // 字符串连接,OK
    final var book 2 = "疯狂" + String.valueOf(99.0); // 调用了方法,不OK
  }
}

为什么调用方法或者访问其他变量不行?因为Java的特点是所有方法和变量都是类成员。那么在调用时,一定要经过实例化才行。实例化分配堆内存,在编译时是不能确定的。

final方法

final修饰的方法不可被重写。当不希望子类重写父类的某个方法时,可以用final修饰。

public class FinalMethodTest
{
	public final void test(){}
}

class Sub extends FinalMethodTest
{
  public void test(){} // 不OK,试图重写父类的final方法
}

但请注意,这里也有一个“bug”,或者说final失灵的情况:当父类的方法是private时,由于子类本身就看不见,无法访问,所以不涉及到重写。如果子类定义了一个一模一样的方法,也不算重写,而是定义了一个新方法。在这种情况下,即使父类方法使用final修饰,也没用。

public class PrivateFinalMethodTest
{
  private final void test(){};
}

class Sub extends PrivateFinalMethodTest
{
  public void test(){} // OK
}

另外,要注意final只能阻止重写,不能阻止重载!

public class FinalOverload
{
  public final void test(){}
  public final void test(String arg){} // 完全OK
}
final

final修饰的类不能有子类。

小结

final可以修饰变量、方法和类,其效果分别是:

  • 修饰变量,该变量只能被赋一次初始值;
  • 修饰方法,该方法不能被子类重写;
  • 修饰类,该类不能被继承。
  • 21
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值