“本文已参与好文征集,点击查看:后端、大前端双轨提交,2万元奖池等你来挑战!”
前言
对于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指的是字符串常量池; 变量b是final修饰的,变量b的值在编译时就已经确定了。 换句话说,变量b的内容是预先已知的。 它相当于一个编译时常量; 变量c是通过b + 1得到的。由于b是一个常量,所以在使用b时,直接相当于使用b的原始值hello进行计算,所以c生成的也是一个常量,a也是一个常量, c也是一个常量,两者都是,并且Java中常量池中只生成唯一的字符串,所以a和c相等; d指向常量池中的hello,但是由于d不是final的,也就是说在使用d的时候,不会提前知道d的值,所以在计算e的时候就不一样了。 对于e,使用了d的引用计算,但是运行时需要通过链接访问变量d。 ,所以这个计算会在堆上生成,所以最后e指向堆上的,所以a和e不相等。
结论:a和c在常量池中,e在堆上。
由final关键字修饰的变量称为常量,常量意味着不能改变。变量是基本数据类型,不能改变,很容易理解
可以看到基本变量如果用final修饰的话就不是变量了
那么引用类型呢? 是否无法更改其引用地址或对象的内容?
我们首先构造一个实体类:
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;
}
}
然后根据以下内容创建一个对象:
可以看到,先用final关键字修饰了一个对象p,然后将p对象指向另一个新对象,就报错了,也就是说final修饰的引用类型不能改变它的引用地址。
接下来我们更改 p 对象的 name 属性:
发现程序没有报错,输出结果也是小军
结论:被final修饰的变量不能改变其引用地址,但可以改变其内部属性。
修改方法
用final关键字修饰的方法不能被覆盖。
使用final方法有两个原因:
第一个原因是锁定方法以防止任何继承类修改其含义。 这是出于设计考虑:希望确保方法的行为在继承中保持不变并且不能被重写。
第二个原因是效率。 在Java的早期实现中,如果你将一个方法声明为final,那么你就同意编译器会将所有对该方法的调用转换为内联调用。 内联调用可以提高方法调用的效率,但是如果方法很大,内联调用并不会提高性能。 在目前的Java版本中(JDK1.5以后),虚拟机可以自动优化,无需使用final方法。
所以final关键字只有在明确禁止重写方法时才使用其修饰的方法。
PS:《Java编程思想》指出,类中的所有方法都隐式指定为final,所以对于方法,我们显式声明final是没有效果的。 但我们创建一个父类,并在父类中声明一个方法,它的子类就可以重写其父类的方法。 为什么?
父类:.class
public class Teacher {
private void study(){
System.out.println("teacher");
}
}
子类:.class
public class Student extends Teacher{
private void study(){
System.out.println("student");
}
}
其实仔细看看,这种写法是不是方法的覆盖率呢? 我们无法通过多态调用父类的say()方法:
并且,如果我们在子类的say()方法中添加@注解,也会报错。
所以这种形式不算方法覆盖率。
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类
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中有一个最明显的类,它是用final修饰的。 用final修饰类很重要的原因之一是常量池。
蛋
面试题:说说final、、、和三者的区别?
关键字一般用在异常中,与try catch一起使用,表示无论是否发生异常,都会执行其中的内容。
1. 有时try中的执行顺序
该语句不是函数的最终退出。 如果有语句,则稍后执行(语句的值会暂存在栈中,等待执行然后返回)
2、情况一与异常获取语句的位置(在try中,不在try中)
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
分析:“num += 80”被拆分为两条语句“num = num+80”和“num”,该行在try中执行语句“num =num+80”,保存下来,放在try之前执行“num”时,先执行in中的语句,然后返回90。
情况2(尝试和中间)
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中的被“覆盖”,不再执行。
情况3(否,但改变返回值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
分析:虽然在 中改变了返回值 num,但由于 中 没有 num 的值,所以执行完 中的语句后,test()函数会得到 try 中返回的 num 的值,以及 try 中 num 的值仍然是程序进入代码块之前保留的值,所以得到的返回值为10。并且函数的最后一条语句不会被执行。
情况4:(将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
分析:如果数据是引用数据类型,并且影响了引用数据类型的属性值的改变,则try中的语句返回的是改变后的属性值。
()是类的一个方法,java技术运行时使用()方法在垃圾收集器从内存中清除对象之前进行必要的清理工作。 当垃圾收集器确定该对象没有对其的引用时,将调用此方法。 () 方法是在垃圾收集器删除该对象以组织系统资源或执行其他清理操作之前对此对象调用的子类重写 () 方法。
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 关键字可以提高性能。 JVM 和 Java 应用程序都会缓存最终变量。 Final 变量可以在多线程环境中安全地共享,而无需额外的同步开销。 使用final关键字,JVM将优化方法、变量和类。end
我是一个正在被打败却仍在努力前进的程序员。 如果文章对您有帮助,记得点赞和关注,谢谢!