final关键字和不变性
最后更新于:2022-08-13 12:50:36
不变性
如果对象在被创建后,状态就不能被修改,那么他就是不可变的,不仅仅包括指向和引用不可变。还包括包含的对象和成员变量都是不可变的
具有不变性的对象一定是线程安全的,我们不需要对其采取任何额外的安全措施,也能保证其线程安全。
演示
/**
* 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属性)
- 在声明变量的等号右边直接赋值
- 构造函数中赋值
- 在类的初始代码块中赋值(不常用)
- 如果不采用第一种赋值方法,那么必须在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属性)
- 等号右边直接赋值
- 用static初始化代码块直接赋值
- 不能用普通的初始化代码块赋值
final local variable(方法中的static final属性)
- 由于这里的变量是在方法中,所以没有构造函数,也不存在初始化代码块
- final local variable 不规定赋值时机,只要求在使用前必须赋值,这和方法中的非final变量的要求也是一样的
修饰方法
构造方法不允许final修饰
不可以被重写,也就是不能被override
修饰类
不可以被继承
String类就不可以被继承,想要继承String类会被编译器提示
不变性和final的关系
不变性并不意味着,简单的用final修饰就是不可变
对于基本数据类型,确实被final修饰后就具有不变性
对于对象类型,需要该对象保证自身被创建后,状态永远不会变才可以
满足以下条件时,对象才是不可变的
- 对象创建后,其状态就不能修改
- 所有属性都是final修饰的
- 对象创建过程中没有发生逸出
栈封闭
把方法里新建的局部变量,实际上是存储在每个线程私有的栈空间,而每个栈的栈空间是不能被其他线程所访问到的,所以不会有线程安全问题。这就是著名的栈封闭技术,是“线程封闭”技术的一种情况。
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