前言
对于 Java 中的 final 关键字,咱们首先能够从字面意思下来了解,百度翻译显示如下:
final 英文意思示意是最初的,不可更改的。那么对应在 Java 中也是表白这样的意思,能够用 final 关键字润饰变量、办法和类。不论是用来润饰什么,其本意都是指“它是无奈更改的”,这是咱们须要牢记的,为什么要无奈更改?无非就是设计所需或者能提高效率,牢记 final 的不可变的设计理念后再来理解 final 关键字的用法,便会顺其自然了。
注释
润饰变量
首先咱们看一个例子
public static void main(String[] args) {
String a = "hello1";
final String b = "hello";
String d = "hello";
String c = b + 1;
String e = d + 1;
System.out.println(a == c);
System.out.println(a == e);
}
}
输入后果:
true
false
Process finished with exit code 0
为什么会失去这种后果呢?咱们来剖析一下:
- 变量 a 指的是字符串常量池中的
hello1
; - 变量 b 是 final 润饰的,变量 b 的值在编译时候就曾经确定了它的确定值,换句话说就是提前晓得了变量 b 的内容到底是个啥,相当于一个编译期常量;
- 变量 c 是 b + 1 失去的,因为 b 是一个常量,所以在应用 b 的时候间接相当于应用 b 的原始值
hello
来进行计算,所以 c 生成的也是一个常量,a 是常量,c 也是常量,都是hello1
,而 Java 中常量池中只生成惟一的一个hello1
字符串,所以 a 和 c 是相等的; - d 是指向常量池中
hello
,但因为 d 不是 final 润饰,也就是说在应用 d 的时候不会提前晓得 d 的值是什么,所以在计算 e 的时候就不一样了,e 的话因为应用的是 d 的援用计算,变量 d 的拜访却须要在运行时通过链接来进行,所以这种计算会在堆上生成hello1
, 所以最终 e 指向的是堆上的hello1
,所以 a 和 e 不相等。
论断:a、c 是常量池的hello1
,e 是堆上的hello1
。
final 关键字润饰的变量称为常量,常量的意思是不可更改。变量为根本数据类型,不可更改很容易了解
能够看到 根本变量应用 final 润饰了就不可变了
那么对于援用类型呢?不可能改的是其援用地址,还是对象的内容?
咱们首先结构一个实体类:Student
public class Student {
private String name;
public Student(String name){this.name = name;}
public String getName() {return name;}
public void setName(String name) {this.name = name;}
}
接着依据创立一个 Person 对象:
能够看到,首先通过 final 关键字润饰一个对象 p,而后接着将 p 对象指向另一个新的对象,发现报错,也就是说 final 润饰的援用类型是不能扭转其援用地址的。
接着咱们改变 p 对象的 name 属性:
发现程序没有报错,输入的后果也是 小军
论断
:被 final 润饰的变量不可更改其援用地址,然而能够更改其外部属性。
润饰办法
final 关键字润饰的办法不可被笼罩。
应用 final 办法起因有两个:
- 第一个起因是把办法锁定,以避免任何继承类批改它的含意,这是出于设计的思考:想要确保在继承中使办法的行为放弃不变,并且不会被笼罩。
- 第二个起因是效率,在 Java 的晚期实现中,如果将一个办法申明为 final,就是批准编译器将针对该办法的所有调用都转为内嵌调用,内嵌调用可能进步办法调用效率,然而如果办法很大,内嵌调用不会进步性能。而在目前的 Java 版本中(JDK1.5 当前),虚拟机能够主动进行优化了,而不须要应用 final 办法。
所以 final 关键字只有明确禁止笼罩办法时,才应用其润饰办法。
PS:《Java 编程思维》中指出类中所有的 private 办法都隐式指定为 final 的,所以对于 private 办法,咱们显式的申明 final 并没有什么成果。然而咱们创立一个父类,并在父类中申明一个 private 办法,其子类中是可能重写其父类的 private 办法的,这是为什么呢?
父类:Teacher.class
public class Teacher {private void study(){System.out.println("teacher");
}
}
子类:Student.class
public class Student extends Teacher{private void study(){System.out.println("student");
}
}
其实认真看看,这种写法是办法的笼罩吗?咱们通过多态的模式并不能调用到父类的 say() 办法:
并且,如果咱们在子类的 say() 办法中,增加 @Override 注解也是会报错的。
所以这种模式并不算办法的笼罩。
final 润饰的办法不能被子类笼罩,然而能够被子类应用和重载。
父类:A.class
public class A {
public int a = 0;
public int getA() {return a;}
public final void setA(int a) {System.out.println("before set:A =" + this.a);// 必须加 this, 不加就会应用传入的 a
this.a = a;
System.out.println("after set:A =" + a);
}
}
子类:B.class
public class B extends A {public B() {super.setA(2);// 正确,能够应用父类的 final 办法
setA();// 调用本类本人办法}
public final void setA() {System.out.println("before set:super a =" + a);
super.a++;
System.out.println("after set:super a =" + a);
}
}
测试一下:
public static void main(String[] args) {B b = new B();
}
输入后果:
before set:A = 0
after set:A = 2
before set:super a = 2
after set:super a = 3
Process finished with exit code 0
论断
:final 关键字润饰的办法不可被笼罩,然而能够被子类应用和重载。
润饰类
final 润饰类示意该类不可被继承。
- 也就是说不心愿某个类有子类的时候,用 final 关键字来润饰。并且因为是用 final 润饰的类,其类中所有的办法也被隐式的指为 final 办法。
- 在 JDK 中有个最显著的类 String,就是用 final 润饰的,将 String 类用 final 润饰很重要的一个起因是常量池。
彩蛋
面试题:说说 final、finally、finalize 三者的区别?
finally
finally 关键字个别在异样中应用,配合 try catch 一起应用,示意不论是否产生异样,finally 中的内容肯定会被执行。
1、try 中有 return 时执行程序
return 语句并不是函数的最终进口,如果有 finally 语句,这在 return 之后还会执行 finally(return 的值会暂存在栈外面,期待 finally 执行后再返回)
2、return 和异样获取语句的地位
- 状况一(try 中有 return,finally 中没有 return)
public class TryTest {public static void main(String[] args) {System.out.println(test());
}
private static int test() {
int num = 10;
try {System.out.println("try");
return num += 80;
} catch (Exception e) {System.out.println("error");
} finally {if (num > 20) {System.out.println("num>20:" + num);
}
System.out.println("finally");
}
return num;
}
}
输入后果:
try
num>20:90
finally
90
Process finished with exit code 0
剖析
:“return num += 80”被拆分成了“num = num+80”和“return num”两个语句,线执行 try 中的“num =num+80”语句,将其保存起来,在 try 中的”return num“执行前,先将 finally 中的语句执行完,而后再将 90 返回。
- 状况二(try 和 finally 中均有 return)
public class TryTest {public static void main(String[] args) {System.out.println(test());
}
private static int test() {
int num = 10;
try {System.out.println("try");
return num += 80;
} catch (Exception e) {System.out.println("error");
} finally {if (num > 20) {System.out.println("num>20:" + num);
}
System.out.println("finally");
return 100;
}
}
}
输入后果:
try
num>20:90
finally
100
Process finished with exit code 0
剖析
:try 中的 return 被”笼罩“掉了,不再执行。
- 状况三(finally 中没 return,然而 finally 中扭转返回值 num)
public class TryTest {public static void main(String[] args) {System.out.println(test());
}
private static int test() {
int num = 10;
try {System.out.println("try");
return num;
} catch (Exception e) {System.out.println("error");
} finally {if (num > 20) {System.out.println("num>20:" + num);
}
System.out.println("finally");
num = 100;
}
return num;
}
}
输入后果:
try
finally
10
Process finished with exit code 0
剖析
:尽管在 finally 中扭转了返回值 num,但因为 finally 中没有 return 该 num 的值,因而在执行完 finally 中的语句后,test()函数会失去 try 中返回的 num 的值,而 try 中的 num 的值仍然是程序进入 finally 代码块前保留下来的值,因而失去的返回值为 10。并且函数最初面的 return 语句不会执行。
- 状况四:(将 num 的值包装在 Num 类中)
public class TryTest {public static void main(String[] args) {System.out.println(test().num);
}
private static Num test() {Num num = new Num();
try {System.out.println("try");
return num;
} catch (Exception e) {System.out.println("error");
} finally {if (num.num > 20) {System.out.println("num.num>20:" + num.num);
}
System.out.println("finally");
num.num = 100;
}
return num;
}
}
class Num {public int num = 10;}
输入后果:
try
finally
100
Process finished with exit code 0
剖析
:如果 return 的数据是援用数据类型,而在 finally 中对该援用数据类型的属性值的扭转起作用,try 中的 return 语句返回的就是在 finally 中扭转后的该属性的值。
finalize
finalize()是 Object 类的办法,java 技术运行应用 finalize()办法在垃圾收集器将对象从内存中革除进来之前做必要的清理工作。这个办法是在垃圾收集器在确定这个对象没有援用指向它时调用。finalize()办法是在垃圾收集器删除对象之前对这个对象调用的子类笼罩 finalize()办法以整顿系统资源或执行其余清理操作。
public class FinalizeTest {public static void main(String[] args) {Person p = new Person("小马",55);
p = null;// 此时堆当中的 Person 对象就没有变量指向了,就变成了垃圾,等到垃圾回收机制调用的 finalize()的时候会输入
System.gc();}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
protected void finalize() throws Throwable {System.out.println("执行 finalize()回收对象");
}
}
总结
应用 final 关键字的益处:
- final 办法比非 final 快一些。
- final 关键字进步了性能。JVM 和 Java 利用都会缓存 final 变量。
- final 变量能够平安的在多线程环境下进行共享,而不须要额定的同步开销。
- 应用 final 关键字,JVM 会对办法、变量及类进行优化。
结尾
我是一个正在被打击还在致力后退的码农。如果文章对你有帮忙,记得点赞、关注哟,谢谢!