final修饰基本类型和引用类型的区别
final修饰基本数据类型,基本数据类型属性的值(数值or布尔值or字符值)不能改变。
final修饰引用类型,引用类型属性不能更改对象的指向,即不能更改引用值。
不可变性的定义和拥有不可变性的办法
基本类型属性的值不能变,引用类型属性所指向的对象的状态不能变。
基本数据类型和String类型的属性:
- private修饰且不向外暴露修改属性的方法。
- final修饰即可
这里解释一下为什么引用类型的String属性用final修饰即可。因为修改String属性的值是会返回新的引用的,而final又限定了属性值(即引用)不能变,所以你无法修改String属性的值。
引用类型(String除外)的属性:
- private修饰且不向外暴露修改属性的方法。
- final修饰+引用所指向对象拥有不可变性
final修饰不同位置变量的赋值时机
类中的final属性
- 第一种是在声明变量的等号右边赋值
- 第二种是初始化代码块赋值
- 第三种是在构造函数中赋值
其实第一种和第二种的赋值最终都会挪到构造函数中
package mythread;
/**
* 会讲赋值操作和代码块都移到构造函数
* 假如有构造函数则会把赋值操作和代码块的语句都添加到构造函数的前面
*
*/
public class ThreadTest implements Runnable{
private int age = 0;
{
System.out.println(age);
}
@Override
public void run() {
age++;
}
public ThreadTest(){
age = 1;
}
public ThreadTest(int age){
this.age = age;
}
}
经过javac
编译,再jad
反编译之后得到
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3)
// Source File Name: ThreadTest.java
package mythread;
import java.io.PrintStream;
public class ThreadTest
implements Runnable
{
public void run()
{
age++;
}
public ThreadTest()
{
age = 0;
System.out.println(age);
age = 1;
}
public ThreadTest(int i)
{
age = 0;
System.out.println(age);
age = i;
}
private int age;
}
类中的staic final属性
- 第一种是在声明变量的等号右边赋值
- 第二种是静态初始化代码块赋值
方法中的final变量
使用前赋值即可。
总结
局部变量无论修饰不修饰final,使用前都需要被初始化。
成员变量/静态成员变量修饰final强制初始化,不修饰final则有初始化值
final修饰方法
被final修饰的方法,子类不能重写,可以调用。
另外:子类中允许有和父类static方法同名的static方法,但是本质不一样,这两个static方法分别与父类和子类绑定,分属不同的类。
final修饰类
被final修饰的类不能被继承。
面试题
第一个题目
package immutable;
/**
* 描述: TODO
*/
public class FinalStringDemo1 {
public static void main(String[] args) {
String a = "wukong2";
final String b = "wukong";
String d = "wukong";
String c = b + 2;
String e = d + 2;
System.out.println((a == c));
System.out.println((a == e));
System.out.println(b==d);
}
}
结果:
运行结果
分析:
编译前后对比
b是编译时期常量,编译时用到b的就直接替换成b的值。
e是运行时确定的字符串会在堆上生成字符串对象。
e是指向堆上,ac是指向常量池的。
第二个题目
public class FinalStringDemo2 {
public static void main(String[] args) {
String a = "wukong2";
final String b = getDashixiong();
String c = b + 2;
System.out.println(a == c);
}
private static String getDashixiong() {
return "wukong";
}
}
结果:
运行结果
分析
编译前后对比
编译器此时无法确定final属性的值,所以编译器也不会进行优化,所以c也是运行的时候才生成的,所以c的字符串对象也是建立在堆上的。而a还是指向常量池的。
后续
后续写一篇Class文件常量池,String常量池和运行时常量池的文章,敬请期待。
网友评论