• 深入了解final


    深入了解final

     

    参考:

    final和volatile:

    https://www.cnblogs.com/jhxxb/p/10944691.html

    如何理解String类型值的不可变?

    https://www.zhihu.com/question/20618891

     

     

    final?

    final深入

    final对象初始化方式

    final类修饰的引用类型变量是引用不可变,还是对象不可变

    final类的实例引用也是final的?

    为什么String类型值不可变

    内存模型 final 和 volatile的内存语义

     

     

     

    final?

    final的本意是不可改变的,绝对性的,最终的。

    final是JAVA中的一个关键字,创造的意图在于中如果想声明一个类不让别人继承,或某个方法不让别人重写,或某个变量不可改变,可以使用final修饰符修饰。

    final修饰的变量称为常量。

    final修饰符:

    修饰类:

    改类不能有子类

    修饰变量:

    引用类型:

    变量指向的对象地址[stack里存的是个地址]不可改变。

    基本数据类型:

    变量的值不能改变

    修饰的变量一般大写

    修饰方法:

    该方法不能被重新

     

     

    final深入

    final对象初始化方式

     声明变量后直接赋值

    private final int age = 20; // 
    private final Size size = new Size();

     声明变量后,在构造方法中赋值

    // 如果使用这种方式需要在每一个构造函数里都对final成员变量赋值
    import com.sun.glass.ui.Size;
    
    public class TestFinal {
        private final int age;
        private final Size size;
    
        public TestFinal() {
            age = 0;
            size = null;
        }
    
        // 如果解开注释,定义的成员变量会编译不通过
    //    public TestFinal(int age) {
    //
    //    }
        
        public TestFinal(int age) {
            this();
        }
    
    //    public TestFinal(int age) {
    //        this();
    //        // 不能重复赋值,编译不通过
    ////        this.age = age;
    //    }
    
        public TestFinal(int age, Size size) {
            this.age = age;
            this.size = size;
        }
    
        public void m() {
            final String ss = "asdadas";
        }
    
    }

     声明变量后,在构造代码块中为其赋值

    import com.sun.glass.ui.Size;
    
    // 构造代码块中的代码会在构造函数之前执行
    public class TestFinal {
        private final int age;
        private final Size size;
    
        {
            age = 20;
            size = new Size();
        }
    
        // 在构造函数里重复赋值,编译不通过
    //    public TestFinal(int age, Size size) {
    //        this.age = 20;
    //    }
    }

    final静态成员变量可以直接赋值

    import com.sun.glass.ui.Size;
    // final static直接赋值
    public class TestFinal {
        private final static int age = 20;
        private final static Size size = new Size();
    }

    final静态成员变量,可以在静态代码块中赋值

    import com.sun.glass.ui.Size;
    
    public class TestFinal {
        private final int sex;
        private final static int age;
        private final static Size size;
    
        {
            this.sex = 1;
            // static类型不能使用this
    //        this.age = 20;
        }
    
    // 静态代码块作用在于类的初始化,只在类被JVM加载时执行一次,是给类初始化的
    // 构造代码块是给对象初始化的
    // 在这里也可以直接对final static成员变量做修饰
        static {
            age = 20;
            size = new Size();
        }
        
        public void m() {
            System.out.println(age);
        }
    }

    引申:

    初始化顺序

    静态变量 > 静态代码块 > 构造代码块 > 构造函数

    静态变量和静态代码块是在类被JVM加载到方法区时就依旧初始化了的,是线程共享的,属于类。构造代码块和构造函数是创建类的实例时才初始化的。

     

     

    final类修饰的引用类型变量是引用不可变,还是对象不可变

    针对引用类型的变量,对其初始化只会不能再让其指向另一个对象。当一个对象被创建时,会在堆中开辟一小块空间存放对象实例,并将该对象的地址copy一份存放到stack中的变量中,这个变量也称为对象实例的引用。

    final变量指向的对象实例如果被移动(回收)了呢?

    上面说了引用类型变量只是copy堆里对象实例的地址,如果对象实例被移动,那当前的这个地址已经没有数据了,所有引用类型变量持有的地址指向了一个空的内存空间,自始至终这个虚拟机栈里的引用地址都没有被改变,因为他是final修饰的。

     

     

     

     

    final类的实例引用也是final的?

    final修饰类只是说明这个类不能被继承,并不代表着使用final修饰的类的实例引用地址不可变!这句话有点绕,我们知道final修饰的引用形变量,该变量引用的地址不可改变,这里说的不可改变指的是虚拟机栈里面对象的引用地址,这个地址也就是copy堆里那个对象的地址。final类的实例和普通对象实例并没有太大区别,实例和变量只是存在一种强引用关系,实例本身是怎样的和变量并没有太大关西,所以final修饰的类的实例变量是不是final取决于这个变量自身有没有在前面添加final修饰。

     

     

     

    为什么String类型值不可变

    深入阅读String类源码:

    public final class String
        implements java.io.Serializable, Comparable<String>, CharSequence {
        /** The value is used for character storage. */
        private final char value[];

    String类是一个构造极其严谨的类,首先String类是被final修饰的,说明String类不可被继承(使用继承方式改变想法失败)。

    String类的成员变量value也就是我们在使用中赋值的变量value是个char数组,而且是final修饰的,final修饰的变量一旦赋值引用地址就不可在改变,也就是说value不能在指向其他字符数组。(final修饰不能改变引用地址)

    虽然value不能指向其他数据,但是value是一个对象,final修饰的引用型变量地址不可变,但对象内部的属性等可以改变,但是仔细看看,priavet final chat value[]; private的私有访问权限的作用都比final大,value被设置成不允许外界访问,而且在String类的方法里没有去动Array里的元素,没有暴露内部成员字段。(改变引用地址指向数组的元素想法失败)

     

    来个例子:System.arraycopy是浅复制,浅复制是指对对象引用的复制,深复制是指重新开辟空间复制值和对象内容。arraycopy是native静态方法,对于一维数组来说,如果一维数组存放的不是对象,则直接复制值,如果是对象的话,复制结果是一维的引用变量传递给副本的一维数组,修改副本会影响原来的数组。对于二维数组数组赋值的是引用。

    String[] s1 = new String[]{"Hi", "Hi", "Hi"};
    String[] s2 = new String[s1.length];
    System.arraycopy(s1, 0, s2, 0, s1.length);
    
    s1[1] = "Hier";
    System.out.println(Arrays.toString(s1)); // output:[Hi,Hier,Hi]
    
    // 期待输出:[Hi,Hier,Hi]
    System.out.println(Arrays.toString(s2)); // 实际输出:[Hi,Hi,Hi]

    修改前:

     

    修改后:由于String值是不可变,所以重新开辟的新的空间,s1[1]里存放的引用地址发生了改变,而s2里的s2[1]依旧存放的是之前的地址。

     

    同样的问题还会出现在自动装箱类,用int型为Integer对象赋值时,实际是创建了新对象(这点自动装箱和String有点不同,只有[-128,127]区间的数字,虚拟机才会缓存)。

    Integer i = 1000;
    Integer j = 1000;
    System.out.println(i == j); // output:false
    
    Integer a = 10;
    Integer b = 10;
    System.out.println(a == b); // output:true

    内存模型 final 和 volatile的内存语义

    在构造函数内对一个final域的写入,与随后把这个被构造对象的引用赋值给一个引用变量,这两个操作之间不能重排序。

    初次读一个包含final域的对象的引用,与随后初次读这个final域,这两个操作之间不能重排序。

     

    前进时,请别遗忘了身后的脚印。
  • 相关阅读:
    Network(树形dp)洛谷2899
    2590 树的统计
    LCT 最小生成树
    几种贪心小结
    snmp
    div页面跳转
    2017.11.2总结,回顾及成果
    2017.11.1知识总结及回顾
    check,form,单选框与复选框总结
    HTML空格字符
  • 原文地址:https://www.cnblogs.com/liudaihuablogs/p/13462708.html
Copyright © 2020-2023  润新知