Java中的final使用

一、基本用法:

  • final可以修饰类,被final修饰的类不能被继承
  • final可以修饰方法,被final修饰的方法不能被重写
  • final可以用来修饰变量,被其修饰的变量被赋初始值后,不能对它重新赋值。

二、final修饰类

当用final修饰一个类时,表明这个类不能被继承。final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法,但是final类中的成员变量可以被定义为final或非final形式。Java中的String就被设计为final。

public final class FinalTest {
    int a=5;
    public void getA(){}
    
    public static void main(String[] args) {
        FinalTest finalTest = new FinalTest();
        finalTest.a++;
        //此处输出a为6
        System.out.println(finalTest.a);
    }
}
//此处编译器报错,显示不能继承一个final类
class Test extends FinalTest{
    
}

三、final修饰方法

public class Son extends Father {

    public void getA() {
        System.out.println("Son.getA()");
    }
}

class Father {
    /**
     * 因为private修饰,子类中不能继承到此方法,
     * 因此,子类中的getA方法是重新定义的、属于子类本身的方法,编译正常
     * 此处Idea会提示方法被private与final同时修饰,被private修饰是不能被继承重写的,被定义为final是多余的
     * 类的private方法会隐式地被指定为final方法。
     */
    private final void getA() {
        System.out.println("Father.getA()");
    }

    /**
     * 在子类中写方法签名一样的函数时,会编译报错overridden method is final
     */
    //  public final void getA() {
    //     System.out.println("Father.getA()");
    // }
}

四、final修饰变量

1.修饰实例变量

​ 被final修饰的实例变量必须显式的指定初始值,而且只能在如下三个位置指定初始值:

  • 定义final实例变量时指定初始值
  • 在非静态初始化块中为final实例变量指定初始值
  • 在构造器中为final实例变量指定初始值

对于普通实例变量,Java成语可以对它执行默认的初始值0或者null,但是对于final实例变量,必须显式指定初始值。

public final class FinalTest {
    //定义final实例变量时赋初始值
    final int var1 = 1;
    final int var2;
    final int var3;
//在初始化块中为var2赋值
    {
        var2 = 2;
    }
//在构造器中为var3赋初始值
    public FinalTest() {
        this.var3 = 3;
    }


    public static void main(String[] args) {
        FinalTest finalTest = new FinalTest();
        System.out.println(finalTest.var1);
        System.out.println(finalTest.var2);
        System.out.println(finalTest.var3);
    }

查看javap -c FinalTest.class

Compiled from "FinalTest.java"
public final class com.practice.FinalTest {
  final int var1;

  final int var2;

  final int var3;
//构造器代码
  public com.practice.FinalTest();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: aload_0
       5: iconst_1
       6: putfield      #2                  // Field var1:I
       9: aload_0
      10: iconst_2
      11: putfield      #3                  // Field var2:I
      14: aload_0
      15: iconst_3
      16: putfield      #4                  // Field var3:I
      19: return
       ...............
}

如上所示:本质上final实例变量都是在构造函数里被赋予初始值的,final实例变量不可以被再次赋值。

2.修饰类变量

final类变量也必须显式的指定初始值,并且只能在以下两个地方指定:

  • 定义final类变量时指定初始值
  • 在静态代码块中为final类变量指定初始值
public final class FinalTest {
    //定义final类变量时赋初始值
    private final static int var1 = "Hello".length();
    private final static int var2;

    //在静态初始化块中为var2赋值
    static {
        var2 = "World".length();
    }

    public static void main(String[] args) {
        System.out.println(FinalTest.var1);
        System.out.println(FinalTest.var2);
    }

}

再看一下javap之后的:

public final class com.practice.FinalTest {
//构造方法
public com.practice.FinalTest();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

..................

//静态代码块
  static {};
    Code:
       0: ldc           #6                  // String Hello
       2: invokevirtual #7                  // Method java/lang/String.length:()I
       5: putstatic     #3                  // Field var1:I
       8: ldc           #8                  // String World
      10: invokevirtual #7                  // Method java/lang/String.length:()I
      13: putstatic     #5                  // Field var2:I
      16: return
}

如上可以看到:final修饰的类变量是在静态代码块中执行初始化的。

3.修饰局部变量

final局部变量由程序员进行显式初始化,如果final局部变量已经进行了初始化则后面就不能再次进行更改,如果final变量未进行初始化,可以进行赋值,当且仅有一次赋值,一旦赋值之后再次赋值就会出错。

public final class FinalTest {
    public static void main(final String[] args) {
       //此处编译报错,不能为final参数指定值
        args=new String[3];
       final int a;
       a=1;
       //此处编译报错,参数已经被赋值
       a=2;
    }

}

Tips:

1.final基本数据类型和final引用数据类型的区别
public final class FinalTest {
    private final static Person finalPerson = new Person(5, "Alen");
    private final static int finalInt = 5;

    public static void main(String[] args) {
        //此处会编译报错,不能为final变量再次赋值
        // finalPerson = new Person(6, "lucy");
        //finalInt=6;
        finalPerson.setAge(6);
        finalPerson.setName("Lucy");
        //此处打印出来的为Person{age=6, name='Lucy'}
        System.out.println(finalPerson);
    }

}

class Person {
    private int age;
    private String name;

    Person(int age, String name) {
        this.age = age;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{" +
                "age=" + age +
                ", name='" + name + '\'' +
                '}';
    }
    ............

当final修饰基本数据类型变量时,不能对基本数据类型变量重新赋值,因此基本数据类型变量不能被改变。而对于引用类型变量而言,它仅仅保存的是一个引用,final只保证这个引用类型变量所引用的地址不会发生改变,即一直引用这个对象,但这个对象属性是可以改变的。

2.宏变量(常量)

在项目中经常会有一个或多个类叫做XXXConstants之类的,在其中定义了很多的常量。

对于一个变量满足以下三个条件就可以成为常量:

  1. 使用final修饰符修饰;
  2. 在定义该final变量时就指定了初始值;
  3. 该初始值在编译时就能够唯一指定。

在程序中其他地方使用该宏变量时,编译器会直接替代成该变量的值。

五、多线程中的final

由于重排序的作用,一个线程读取到一个对象的引用时,该对象可能尚未初始化完毕,即这个线程可能读取到该对象字段的默认值而不是初始值(通过构造函数或者初始化语句指定的值)。

在多线程环境下final关键字有其特殊的作用:

当一个对象被发布到其他线程的时候,该对象的所有final字段(实例变量)都是初始化完毕的,即其他线程读取这些字段的时候读取到的值都是相应字段的初始值而不是默认值。而非final字段则没有这种保障,即这些线程读取该对象的非final字段时读取到的值可能仍然是相应字段的默认值。对于引用型final字段,final关键字还进一步确保该字段所引用的对象已经初始化完毕,即这些线程读取该字段所引用的对象的各个字段时所读取到的值都是相应字段的初始值。
。。。待续

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值