final关键字和不变性

不变性

如果对象在被创建后,状态就不能被修改,那么他就是不可变的,不仅仅包括指向和引用不可变。还包括包含的对象和成员变量都是不可变的
具有不变性的对象一定是线程安全的,我们不需要对其采取任何额外的安全措施,也能保证其线程安全。

演示

/**
* person对象,age和name都不可能改变
*/
public class Person {
    final int age = 18;
    final String name = "Alice";
}

public class TestFinal {
    public static void main(String[] args) {
        Person person = new Person();
        person.age = 19;
    }
}

结果

Error:(7, 15) java: 无法为最终变量age分配值

final的作用

类防止被继承、方法防止被重写、变量防止被修改
天生是线程安全的,而不需要额外的同不开销

3种用法:修饰变量、方法、类

修饰变量

被final修饰的变量,意味着值不能被修改。如果变量是对象,那么对象的引用不能被改变,但是对象自身的内容依然可以变化

public class Person {
    final int age = 18;
    final String name = "Alice";
    String bag = "computer";
}

赋值时机

属性被声明为final后,该变量则只能被复制一次。且一旦被赋值,final的变量就不能在被改变,无论如何也不会变。

规定赋值时机的原因

如果初始化不赋值,会有一个默认值,后续赋值等于把默认值改成了对应的赋值,这样就违反了final不变的原则了

final instace variable (类中的final属性)

  1. 在声明变量的等号右边直接赋值
  2. 构造函数中赋值
  3. 在类的初始代码块中赋值(不常用)
  4. 如果不采用第一种赋值方法,那么必须在2、3中挑一种进行赋值,而不能不赋值,这是final语法所规定的
public class Person {
    // 等号右边赋值
    final int age = 18;
    final String name;
    final String bag;
    {
        // 在类的初始代码块中赋值
        bag = "computer";
    }
    //构造函数中赋值
    public Person(String name) {
        this.name = name;
    }
}

final static variable(类中的static final属性)

  1. 等号右边直接赋值
  2. 用static初始化代码块直接赋值
  3. 不能用普通的初始化代码块赋值

final local variable(方法中的static final属性)

  1. 由于这里的变量是在方法中,所以没有构造函数,也不存在初始化代码块
  2. final local variable 不规定赋值时机,只要求在使用前必须赋值,这和方法中的非final变量的要求也是一样的

修饰方法

构造方法不允许final修饰

不可以被重写,也就是不能被override

修饰类

不可以被继承

String类就不可以被继承,想要继承String类会被编译器提示

不变性和final的关系

不变性并不意味着,简单的用final修饰就是不可变

对于基本数据类型,确实被final修饰后就具有不变性
对于对象类型,需要该对象保证自身被创建后,状态永远不会变才可以

满足以下条件时,对象才是不可变的
  1. 对象创建后,其状态就不能修改
  2. 所有属性都是final修饰的
  3. 对象创建过程中没有发生逸出
栈封闭

把方法里新建的局部变量,实际上是存储在每个线程私有的栈空间,而每个栈的栈空间是不能被其他线程所访问到的,所以不会有线程安全问题。这就是著名的栈封闭技术,是“线程封闭”技术的一种情况。

public class StackConfinement implements Runnable {

    int index = 0;

    public void inThread() {
        int neverGoOut = 0;
        for (int i = 0; i < 10000; i++) {
            neverGoOut++;
        }
        System.out.println("栈内保护的数字是线程安全的:" + neverGoOut);
    }

    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
            index++;
        }
        inThread();
    }

    public static void main(String[] args) throws InterruptedException {
        StackConfinement r1 = new StackConfinement();
        Thread thread1 = new Thread(r1);
        Thread thread2 = new Thread(r1);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(r1.index);
    }
}

结果

栈内保护的数字是线程安全的:10000
栈内保护的数字是线程安全的:10000
13931

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注