final关键字作用
final
关键字可用于修饰类、方法、变量,通过final
修饰后可以使类不可被继承,方法不可被重写,变量不可被修改。
正是因为这样使得final
关键字修饰的东西天生自带线程安全属性,而且也没有额外的开销。
final使用注意事项
final修饰的成员变量
final
必须在声明时候就进行初始化,否则无法正常通过编译:
private static final Object person =null;
final修饰局部变量
正确的使用方式应该是如下所示,如果b没有赋值的话,编译期都过不了:
void testFinal() {
final int b = 7;
int c =b;
}
当然若变量还有static修饰的话,还可以在静态代码块中被初始化
public class FinalVariableDemo {
private static final Person person = null;
private static final int a;
static {
a = 7;
}
}
若没有static
修饰,也可以在构造方法或者普通代码块中被初始化。
public class FinalVariableDemo {
private static final Person person = null;
private final int a;
{
a = 7;
}
}
final修饰方法
如下所示,经过final
修饰的方法,不可被重写,这里我们也注意到一点,经过static
修饰的方法也不可被修改,因为static
是随着类创建而创建的,没有重写这一说。
package immutable;
/**
* 描述: final的方法
*/
public class FinalMethodDemo {
public void drink() {
}
public final void eat() {
}
public static void sleep() {
}
}
class SubClass extends FinalMethodDemo {
@Override
public void drink() {
super.drink();
eat();
}
// public final void eat() {
//
// }
public static void sleep() {
}
}
final修饰类
final修饰的类最常用的就是String
类,经过final
修饰后的String
就不可被修改
关于final的不可变性
我们都说经过final
修饰后的变量是不变的,但是我们必须注意以下几点
final
修饰的基本类型是不变的,但是引用类型则不一定,用final
修饰的引间对象的值- 总的来说,要保证不可变性,必须保证:1.对象创建后不可被修改。2.所有属性都是final修饰的。
- 对象创建过程中,没有发生逸出
(例如某个私有成员变量通过public方法返回给外部,外部可以随意修改这个值)
栈封闭技术
实际上保证变量的不可变还有一种技术。即栈封闭技术,说白了就是将线程需要使用的变量放到方法内部作为局部变量使用。这个变量就会存到线程的私有栈空间,其他线程是无法访问到的
**
* 描述: 演示栈封闭的两种情况,基本变量和对象 先演示线程争抢带来错误结果,然后把变量放到方法内,情况就变了
*/
public class StackConfinement implements Runnable {
int index = 0;
public void inThread() {
int neverGoOut = 0;
synchronized (this) {
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
13661
相关面试题
问题1
请说明下面输出结果的原因
public static void main(String[] args) {
String a = "test1";
final String b = "test";
String d = "test";
String c = b + 1;
String e = d + 1;
System.out.println((a == c));//true
System.out.println((a == e));//false
}
如下代码就是反编译字节码文件后的样子
public static void main(String[] args) {
String a = "test1";
String b = "test";
String d = "test";
String c = "test2";
String e = d + 1;
System.out.println(a == c);
System.out.println(a == e);
}
a==c返回true
:因为b被final修饰,所以编译器即知晓值,b+1相当于C++宏替换的操作直接变成常量test1,从常量池中获取。a == e返回false
:e= d + 1
,而d没有被final修饰,可以动态变化,编译器无法计算其具体结果,所以直接从堆区开辟一个空间常见对象,导致最终返回false。
问题2
这题同样返回false,因为final修饰的引用后面赋值的变量是方法,方法内部的变量可能是会变化的,编译期无法计算,所以c的值最终也不是从常量池中取,所以和a是不一样的,返回false。
public class FinalStringDemo2 {
public static void main(String[] args) {
String a = "test2";
final String b = getDashixiong();
String c = b + 2;
System.out.println(a == c);
}
private static String getDashixiong() {
return "test";
}
}