final

    final 是 Java 中的一个关键字,简而言之,final 的作用意味着“这是无法改变的”。不过由于 final 关键字一共有三种用法,它可以用来修饰变量、方法或者类,而且在修饰不同的地方时,效果、含义和侧重点也会有所不同。

    final 修饰变量
    关键字 final 修饰变量的作用是很明确的,那就是意味着这个变量一旦被赋值就不能被修改了,也就是说只能被赋值一次,直到天涯海角也不会“变心”。如果我们尝试对一个已经赋值过 final 的变量再次赋值,就会报编译错误。如果我们尝试对一个已经赋值过 final 的变量再次赋值,就会报编译错误。
    代码示例:

    1. /**
    2. * 描述: final变量一旦被赋值就不能被修改
    3. */
    4. public class FinalVarCantChange {
    5. public final int finalVar = 0;
    6. public static void main(String[] args) {
    7. FinalVarCantChange finalVarCantChange = new FinalVarCantChange();
    8. // finalVarCantChange.finalVar=9; //编译错误,不允许修改final的成员变量
    9. }
    10. }

    为什么要对某个变量去加 final 关键字呢?主要有以下两点目的。

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

      public static final int YEAR = 2021;
      
    • 第二个目的是从线程安全的角度去考虑的。不可变的对象天生就是线程安全的,所以不需要我们额外进行同步等处理,这些开销是没有的。如果 final 修饰的是基本数据类型,那么它自然就具备了不可变这个性质,所以自动保证了线程安全。


    赋值时机**
    下面我们就来看一下被 final 修饰的变量的赋值时机,变量可以分为以下三种:

    • 成员变量,类中的非 static 修饰的属性;
    • 静态变量,类中的被 static 修饰的属性;
    • 局部变量,方法中的变量。

    1)成员变量

    • 第一种是在声明变量的等号右边直接赋值,例如:

      public class FinalFieldAssignment1 {
        private final int finalVar = 0;
      }
      
    • 第二种是在构造函数中赋值,例如: ```java class FinalFieldAssignment2 {

      private final int finalVar;

      public FinalFieldAssignment2() {

        finalVar = 0;
      

      }

    }

    
    - 第三种就是在类的构造代码块中赋值(不常用),例如:
    ```java
    class FinalFieldAssignment3 {
    
        private final int finalVar;
    
        {
            finalVar = 0;
        }
    
    }
    

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

    • 第一种同样是在声明变量的等号右边直接赋值,例如:

      /**
      * 描述:     演示final的static类变量的赋值时机
      */
      public class StaticFieldAssignment1 {
        private static final int a = 0;
      }
      
    • 第二种赋值时机就是它可以在一个静态的 static 初始代码块中赋值,这种用法不是很多,例如: ```java class StaticFieldAssignment2 {

      private static final int a;

      static {

        a = 0;
      

      }

    }

    3)局部变量<br />局部变量指的是方法中的变量,如果你把它修饰为了 final,它的含义依然是**一旦赋值就不能改变**。<br />它的赋值时机和前两种变量是不一样的,因为它是在方法中定义的,所以它没有构造函数,也同样不存在初始代码块,所以对应的这两种赋值时机就都不存在了。实际上,对于 final 的局部变量而言,它是不限定具体赋值时机的,只要求我们**在使用之前必须对它进行赋值**即可。
    ```java
    /**
     * 描述:     本地变量的赋值时机:使用前赋值即可
     */
    public class LocalVarAssignment1 {
        public void foo() {
            final int a = 0;//等号右边直接赋值
        }
    }
    
    class LocalVarAssignment2 {
        public void foo() {
            final int a;//这是允许的,因为a没有被使用
        }
    }
    
    class LocalVarAssignment3 {
        public void foo() {
            final int a;
            a = 0;//使用前赋值
            System.out.println(a);
        }
    }
    


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

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

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

    /**
     * 描述:     final的方法不允许被重写
     */
    public class FinalMethod {
        public void drink() {
    
        }
        public final void eat() {
    
        }
    }
    
    class SubClass extends FinalMethod {
    
        @Override
        public void drink() {
            //非final方法允许被重写
        }
    //    public void eat() {}//编译错误,不允许重写final方法
    //    public final SubClass() {} //编译错误,构造方法不允许被final修饰
    
    }
    

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

    /**
     * 描述:     测试final class的效果
     */
    public final class FinalClassDemo {
        //code
    }
    
    //class A extends FinalClassDemo {}//编译错误,无法继承final的类
    


    不变性
    如果对象在被创建之后,其状态就不能修改了,那么它就具备“不变性”。
    “被 final 修饰的变量意味着一旦被赋值就不能修改”,而这个规则对于基本类型的变量是没有歧义的,但是对于对象类型而言,final 其实只是保证这个变量的引用不可变,而对象本身依然是可以变化的。这一点同样适用于数组,因为
    在 Java 中数组也是对象**。那我们就来举个例子,看一看以下 Java 程序的输出:

    class Test {
        public static void main(String args[]) {
           final int arr[] = {1, 2, 3, 4, 5};  //  注意,数组 arr 是 final 的
           for (int i = 0; i < arr.length; i++) {
               arr[i] = arr[i]*10;
               System.out.println(arr[i]);
           }
        }
    }
    

    打印出来结果如下:

    10 
    20 
    30 
    40 
    50
    

    final 修饰一个指向对象的变量的时候,对象本身的内容依然是可以变化的

    **