final关键字
基本用法:修饰类、方法、变量
1.修饰一个类时:表明该类不能被继承,final类中成员变量隐式指定为final
2.修饰方法时:锁定方法,防止继承类修改其定义(防止覆盖)(另外:为了效率,早期java版本中,final方法转为内嵌调用,这个有兴趣的可以查查,现在基本用不到了)
补充:**类中private方法会隐式的指定为final
3.修饰变量
即声明一个常量
对于final变量,若为基本数据类型,其数值初始化后不可更改,如为引用类型,则初始化后不可指向其他对象
**对于final和static
两者作用于变量作用并不相同,最主要区别在于:final保证变量不可变,static表示只对变量保存一份副本
深入理解final
1.编译期常量和运行时常量
final声明的常量分为编译期常量和运行时常量
编译期常量:如果在编译时,final变量是基本类型或String类型,且jvm可以确定它的确切值,那么编译器会把它当做编译期常量使用,使用字面量替换并存入class常量池,在需要它的时候,直接访问这个常量
private final String q="b";
运行时常量:即并不直接用字面量为final常量赋值,中间经过引用或获取的过程(可以是对局部变量的获取或某些处理过程结果的获取),例如:
final int len = "sjt".length();
对于运行时常量,在程序编译期,程序并不关注其本身的值,而是只用知道其类型即可,在运行阶段才对其值进行确定,而编译期常量在编译期中直接被替换为字面量,被写入class常量池中
2.对类的依赖
若在程序入口分别调用编译期常量/运行时常量 则:
编译期常量在编译阶段存入class常量池,不引起类的初始化,即不依赖类
运行时常量依赖类,会引起类的初始化(类立即执行)
前面我们提到,编译期常量在编译期已经被确定,而运行时常量在运行时才被确定,这也正是为什么编译期常量不依赖类,而运行时常量依赖类的原因
下面的例子(1)(2)分别为调用基本数据类型和引用数据类型的运行时常量引起类的初始化的情况
//例子(1)
class Test {
//静态代码块
static {
System.out.println("Class Test Was Loaded!");
}
//编译期常量
public static final int num = 10;
//运行时常量
public static final int len = "Rhien".length();
}
public class Main {
public static void main(String[] args) throws Exception {
System.out.println("num:"+Test.num);
System.out.println("=== after get num ===");
System.out.println("len:"+Test.len);
}
}
/* 打印输出:
* num:10
* === after get num ===
* Class Test Was Loaded!
* len:5
*/
//例子(2)
class Test1 {
public int len_test=0;
//静态代码块
static {
System.out.println("Class Test Was Loaded!");
}
//编译期常量
public static final int num = 10;
//运行时常量
public static final Test1 len =new Test1();
}
public class Test {
public static void main(String[] args) throws Exception {
System.out.println("num:"+Test1.num);
System.out.println("=== after get num ===");
System.out.println("len:"+Test1.len.len_test);
}
}
/*打印输出
*num:10
*=== after get num ===
*Class Test Was Loaded!
*len:0
**/
由上面代码可以看到,num是编译期常量,调用它不会引起类的初始化,而对运行时常量len的调用不论len是基本数据类型或引用数据类型均引起了类的初始化。
3.关于“编译期常量可直接使用”探讨
看一个例子
public class Test {
public static void main(String[] args) {
String a = "hello2";
final String b = "hello";
String d = "hello";
String c = b + 2;
String e = d + 2;
System.out.println((a == c));
System.out.println((a == e));
}
}
运行结果为:
true
false
对于b来说,在编译期已经被字面量“hello”替换,其值存入了class常数池,那么对于c的等式而言,在编译期中“+”左右两边均为字符串常量,jvm在编译期直接将其相连并确定了c的值存入常量池中;对e来说,“+”两边存在变量,那么对java而言,会new一个StringBuilder来进行拼接(具体细节自行查阅),此时用到了new,那么e的值被放在了堆中,a与e的地址当然不同,故flase。
4.引用变量被final修饰之后,虽然不能再指向其他对象,但是它指向的对象的内容是可变的。
5.final和static
static作用于成员变量用来表示只保存一份副本,而final的作用是用来保证变量不可变。
关于4、5的解析可查看这篇博客