• Java的 final 关键字


    本文主要探讨Java final 关键字修饰变量时的用法。
    !!!!文末有彩蛋!!!!

    1.修饰类

      当用final修饰一个类时,表明这个类不能被继承。也就是说,如果一个类你永远不会让他被继承,就可以用final进行修饰。final类中的成员变量可以根据需要设为final,但是要注意final类中的所有成员方法都会被隐式地指定为final方法。

      在使用final修饰类的时候,要注意谨慎选择,除非这个类真的在以后不会用来继承或者出于安全的考虑,尽量不要将类设计为final类。

    2.修饰方法

      下面这段话摘自《Java编程思想》第四版第143页:
      “使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的Java版本中,不需要使用final方法进行这些优化了。“
      因此,如果只有在想明确禁止 该方法在子类中被覆盖的情况下才将方法设置为final的。
      注:类的private方法会隐式地被指定为final方法。
    注:以上1,2点从浅析Java中的final关键字引用

    3.修饰变量

    1.思考,参考String类中几个final修饰的全局变量。

    private final byte[] value;
    private final byte coder;
    private static final long serialVersionUID = -6849794470754667710L;
    static final boolean COMPACT_STRINGS;
    

    可以明显看到,只有serialVersionUID 被赋了值(或者说实例化,以下都用赋值二字代替)。
    新建一个类,模拟下:
    1.png
    对于bc都会提示错误,即"还没有被初始化",然而String类中的变量却没有报错,原因在于:

    • String类的所有构造方法都给valuecoder赋了值(直接或者间接
      对于直接或者间接的解释如下:

      2个参数的构造方法并没有直接给b赋值,但调用了给b赋值的另一个无参的构造方法,也不会报错(也可以在类初始化的过程中给b赋值)
    • 必须有且仅有一个static代码块给COMPACT_STRINGS赋值,多次赋值会出错,如下图:
      2.png

    2.回顾,听说final修饰的变量不可变?
    答案为肯定,但不可变却可以再继续讨论。
    不可变可以简单的解释为:

    • final修饰的基本类型不能再被赋值;
    public class Demo {
        private final int b = 1;
        public void testInt(String[] args) {
            b = 2;//很显然此处会报错
        }
    }
    
    • final修饰的对象与数组,不能再指向新的对象(数组),但是属性(索引的值)可以改变;
    public class Demo {
        public void testObject() {
            final User user = new User();
            user.setName("111");//本行与下行不会报错,对象指向不能改变,但是属性可以改变
            user.setName("222");
            user = new User();//user不能 = new xx,也不能 = user02
        }
        public void testArray() {
            final int[] array = new int[]{1,2,3,4,5,6};
            array = new int[]{1,2,3,4,5,6};//报错
            array[1] = 2;//正常
        }
    }
    class User{
        private String name;
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
    }
    

    3.深入,final还能带来什么?

    • 对于编译器,如下栗子:
    public class Demo04 {
        public static void main(String[] args) {
    
            String a = "a";
            String b = "b";
            String ab_add = a + b;
            String ab_new = "ab";
            System.out.println(ab_add == ab_new);//输出为 false
    
            final String  c = "c";
            String cd_add = c + "d";
            String cd_new = "cd";
            System.out.println(cd_add == cd_new);//输出为 true
        }
    }
    

    为什么上面指向的不是一个对象,下面指向的是一个对象:因为下面的cfinal修饰后,与常量"d"一样,被编译器当成常量,所以
    String cd_new = "cd"; 指向的是已经存在的"cd"

    • 对于虚拟机,如下栗子:
    public class Demo05 {
    
        static {
            Demo05 demo05 = new Demo05();
        }
        Demo05() {
            System.out.println("a = "+a+" b = "+b);
        }
        public static void main(String[] args) {
        
        }
    
        static int a = 123;
        static final int b = 456;
    }
    

    分析:
    1.按照正常的类加载顺序,应该是先加载静态代码与变量(按照前后顺序),然后是成员变量与构造方法。
    2.对于上方代码,static加载后,遇到类的构造方法,导致需要去加载构造方法(static相关暂停,在加载完次构造方法后,继续加载),此时a还没有被赋实际值,暂为0。所以输出时,a为0。
    3.对于b,由于被final修饰导致先被赋上实际值,所以输出不为0。

    对String类的采访!

    Q:听说你被final修饰的事情,大家都知道了?(●’◡’●)ノ
    A: ( •́ .̫ •̀ )是啊。但这是原因的,你听我解释~这并不丢人!!
    Q:哦?( ‘-ωก̀ )。
    A:.巴拉巴拉、解释中
    Q:你的值真的不能修改吗?
    A:当然啦,除非。。。(•ิ_•ิ)
    Q:快说,除非什么? (..•˘_˘•..)
    A:reflect。。。。溜了溜了~(/゚Д゚)
    画风突变------>“代码如下”:

    public class Demo04 {
        public static void main(String[] args) {
            String str = "1234";
            Field field = getField("java.lang.String","value");
            field.setAccessible(true);//不写报错:Demo04 cannot access a member of class java.lang.String
            // (in module java.base) with modifiers "private final"
            byte[] value = {};
            try {
                value = (byte[])field.get(str);
            }catch (Exception e) {
                e.printStackTrace();
            }
            value[0] = 'a';
            value[1] = 'b';
            System.out.println(str);
            //输出为:ab34
        }
        /**
         * Description:指定类名,指定属性名,获取属性
         * @param className,fieldName
         * @return field
         */
        public static Field getField(String className, String fieldName) {
            try {
                //获得类名
                Class c = Class.forName(className);
                //获得类对象
                Object object = c.getConstructor().newInstance();
                //获得指定属性
                Field field = object.getClass().getDeclaredField(fieldName);
                return field;
            } catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    }
    

    至此,本文结束

    本文参考:
    1.# 浅析Java中的final关键字
    2.# Java虚拟机类加载机制——案例分析
    3.# 菜鸟学Java(十五)——Java反射机制(二)
    4.# 颜文字(ฅ´ω`ฅ)

    作者:kangkaii
    如果您觉得本文有帮助的话,可以点个推荐呐!! 若有不对或者不合理的地方也欢迎指出,感谢~
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
  • 相关阅读:
    c语言宏
    vim的跨文件复制粘贴
    c语言文件包含
    CSS Modules 解决 react 项目 css 样式互相影响的问题
    react 执行 yarn build 页面无法显示
    create-react-app 引入 antd 及 解决 antd 样式无法显示的bug
    yarn 和 npm 的区别
    create-react-app项目添加less配置
    react 项目实战(十)引入AntDesign组件库
    react 项目实战(九)登录与身份认证
  • 原文地址:https://www.cnblogs.com/kangkaii/p/8419076.html
Copyright © 2020-2023  润新知