简介
final关键字是非常常用的关键字,而且也是面试中经常被问到的考点,本文将对final做出总结和阐释。final的使用如其字面意思,最终变量、禁止修改。我们将从数据、方法和类三个场景讲解final的使用。
final修饰的方法
final修饰的方法无法被其他类继承覆盖,改变其内部含义。
编译器发现一个final方法调用,就会(根据它自己的判断)忽略为执行方法调用机制而采取的常规代码插入方法(将自变量压入堆栈;跳至方法代码并执行它;跳回来;清除堆栈自变量;最后对返回值进行处理)。相反,它会用方法主体内实际代码的一个副本来替换方法调用。这样做可避免方法调用时的系统开销。当然,若方法体积太大,那么程序也会变得雍肿,可能受不到嵌入代码所带来的任何性能提升。因为任何提升都被花在方法内部的时间抵消了。Java编译器能自动侦测这些情况,并颇为“明智”地决定是否嵌入一个final方法。然而,最好还是不要完全相信编译器能正确地作出所有判断。通常,只有在方法的代码量非常少,或者想明确禁止方法被覆盖的时候,才应考虑将一个方法设为final。
类内所有private方法都自动成为final。由于我们不能访问一个private方法,所以它绝对不会被其他方法覆盖(若强行这样做,编译器会给出错误提示)。可以手动为一个private方法添加final指示符,但没有任何额外的含义。
final修饰类
使用final修饰类,则表明该类不可被继承,即不允许该类进行子类化。final类中的数据成员既可以是final,也可以不是。由于类禁止了继承,它的方法将都无法被覆盖,所以一个final类中的所有方法都默认为final。与我们将一个方法明确声明为final一样,编译器此时有相同的效率选择。可为final类内的一个方法添加final指示符,但这样做没有任何意义。
final修饰数据
final修饰的数据称作“常量”。如果使用final修饰对象句柄,final会将句柄变成一个常数,同时在声明时必须将句柄初始化到一个具体的对象。而且永远不能将句柄指向另一个对象。但对象本身是可以修改的。这一原则也适用于数组,它也属于对象。我们有以下例子:
package com.coderap.foundation.identifier;
class Value {
int i = 1;
public void print() {
System.out.println("Value i = " + i);
}
}
class FinalData {
final Integer i = 1;
final static Integer I = 2;
final int i3 = (int)(Math.random()*20);
final static int i4 = (int)(Math.random()*20);
Value value1 = new Value();
final Value value2 = new Value();
final static Value value3 = new Value();
final int[] array = { 1, 2, 3, 4, 5, 6 };
}
public class FinalTest {
public static void main(String[] args) {
FinalData finalData1 = new FinalData();
System.out.print("finalData1 --------------------------------- \n\t");
StringBuffer stringBuffer = new StringBuffer();
// 普通类型
stringBuffer.append("final i = ").append(finalData1.i).append("\n\t");
stringBuffer.append("final statis I = ").append(finalData1.I).append("\n\t");
stringBuffer.append("final i3 = ").append(finalData1.i3).append("\n\t");
stringBuffer.append("final i4 = ").append(finalData1.i4).append("\n\t");
// 对象句柄
stringBuffer.append("value1.i = ").append(finalData1.value1.i).append("\n\t");
stringBuffer.append("final value2.i = ").append(finalData1.value2.i).append("\n\t");
finalData1.value1.i++;
finalData1.value2.i--;
stringBuffer.append("value1.i modified = ").append(finalData1.value1.i).append("\n\t");
stringBuffer.append("final value2.i modified = ").append(finalData1.value2.i).append("\n\t");
// 数组句柄
stringBuffer.append("final array = ").append(finalData1.array[0]).append("\n\t");
finalData1.array[0]++;
stringBuffer.append("final array modified = ").append(finalData1.array[0]).append("\n\t");
System.out.println(stringBuffer.toString());
FinalData finalData2 = new FinalData();
System.out.print("finalData2 --------------------------------- \n\t");
stringBuffer = new StringBuffer();
stringBuffer.append("final i = ").append(finalData2.i).append("\n\t");
stringBuffer.append("final statis I = ").append(finalData2.I).append("\n\t");
stringBuffer.append("final i3 = ").append(finalData2.i3).append("\n\t");
stringBuffer.append("final i4 = ").append(finalData2.i4).append("\n\t");
// 对象句柄
stringBuffer.append("value1.i = ").append(finalData2.value1.i).append("\n\t");
stringBuffer.append("final value2.i = ").append(finalData2.value2.i).append("\n\t");
finalData2.value1.i++;
finalData2.value2.i--;
stringBuffer.append("value1.i modified = ").append(finalData2.value1.i).append("\n\t");
stringBuffer.append("final value2.i modified = ").append(finalData2.value2.i).append("\n\t");
// 数组句柄
stringBuffer.append("final array = ").append(finalData2.array[0]).append("\n\t");
finalData2.array[0]++;
stringBuffer.append("final array modified = ").append(finalData2.array[0]).append("\n\t");
System.out.println(stringBuffer.toString());
finalData1.value1 = new Value(); // Correct
// finalData1.value2 = new Value(); // Error Example: Cannot assign a value to final variable value2
// finalData1.array = new int[]{}; // Error Example: Cannot assign a value to final variable array
}
}
运行后输出:
finalData1 ---------------------------------
final i = 1
final statis I = 2
final i3 = 1
final i4 = 4
value1.i = 1
final value2.i = 1
value1.i modified = 2
final value2.i modified = 0
final array = 1
final array modified = 2
finalData2 ---------------------------------
final i = 1
final statis I = 2
final i3 = 13
final i4 = 4
value1.i = 1
final value2.i = 1
value1.i modified = 2
final value2.i modified = 0
final array = 1
final array modified = 2
从上面的例子中我们可以发现:
final int i3 = (int)(Math.random()*20);
final static int i4 = (int)(Math.random()*20);
对于i3,虽然用final对其修饰了,但它在每个对象中的具体值还是取决于初始化时的计算,而对于i4,由于使用了static对其进行修饰,它在每个对象中只会有一份相同的值。不能由于final修饰了属性就认定它的值能在编译时期知道,类似于i3的值即是在运行期间确定的。而i4的值是在类载入时确定的。
同时,使用final修饰对象和数组,对对象的公开属性进行修改,或对数组中的元素进行修改都是被允许的,但如果尝试使用尝试改变其句柄的指向时,编译器就会直接报错。