深入了解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域,这两个操作之间不能重排序。

 

前进时,请别遗忘了身后的脚印。
原文地址:https://www.cnblogs.com/liudaihuablogs/p/13462708.html