Java的final关键字浅析
Java的final关键字表示“不可改变的”,不想改变的原因可能有两个理由:设计和效率。然而根据上下文环境,其含义有着细微的差别。final关键字可以修饰数据、方法和类。
1. final数据
在定义一个变量时,final关键字告诉编译器这个变量是一个不可改变的数据。这种情况可能有两个出发点:1. 这是一个永不改变的编译时常量;2. 这是一个一旦初始化就不被改变的变量。
1. 对于编译时常量,编译器会把该常量的值带入到任何使用到它的计算式中,也就是说在编译时直接执行计算式,可以减轻运行时的负担。在Java中这些常量必须基于基本数据类型,并且用final关键字表示,在对这个常量定义的时候必须对其赋值。
2. 对于非基本类型的数据,用final关键字表示,其含义与基本类型数据不同。对于基本数据类型,final关键字是数值恒定不变;而对于非基本数据类型,final关键字使对象引用恒定不变。一旦引用被初始化指向一个对象,就在也不能改为指向另一个对象。然而对象自身是可以修改的,Java并不提供任何使对象恒定不变的方案。这一限制同样适用于数组,Java中数组也是对象。
3. 对于既是static有时final的变量,占据了一段不能改变的内存空间。
class Value { private static final String TAG = "VALUE"; private final int ERROR_CODE = -1; private final int SUCCESS_CODE; public int number = 0; Value(int i) { // final 关键字修饰的变量不可以被修改 //! this.ERROR_CODE = 0; this.SUCCESS_CODE = i; } public void setSuccessCode(int i) { // final 关键字修饰的变量必须在直接初始化为常量或者在构造方法中初始化 // this.SUCCESS_CODE = i; } } public class FinalData { public static void main(String[] args) { final Value value = new Value(0); // final 关键字修饰的对象不可以再次被引用为其他对象 //! value = new Value(1); System.out.println(value.number); value.number = 1; System.out.println(value.number); } }
执行结果:
0
1
2. final方法
使用final方法的原因有两个。第一个原因是把方法锁定,不允许子类重载该方法。这是出于设计的角度考虑:想要在继承中保持方法的行为不变,并且不会被重写。
第二个原因是效率。被final关键字修饰的方法会被编译器解释为内嵌调用。正常的函数调用会执行参数压栈,然后在执行该方法的代码段,执行结束再跳回并清理栈中的参数。内嵌调用没有压栈和弹栈的处理,相当于将该方法代码段复制到程序运行处执行。这可以消除方法调用的开销。不过这种方式能够节约的开销越来越不被重视,因为能够节约的资源对于现在的程序来讲实在是微不足道。已经所以不应该单纯为了效率而使用final方法,这会让继承你的类的同事无法重写你的方法。
private和final关键字
private关键字本就可以使得基类的方法不被继承和重写,如果再加上一个final关键字就毫无意义。只有对于希望可以被子类调用,却不希望子类修改的方法,才有必要使用final关键字。
用下面的例子来说明:
public class Instrument { final void what() { System.out.println("This is an Instrument"); } public void play() { System.out.println("Instrument play!"); } public static void main(String[] args) { Instrument piano = new Piano(); Instrument violin = new Violin(); piano.play(); violin.play(); piano.what(); violin.what(); } } class Piano extends Instrument { /*public void what() { System.out.println("This is a Piano"); }*/ public void play() { System.out.println("Piano play!"); } } class Violin extends Instrument { /*public void what() { System.out.println("This is a Violin"); }*/ public void play() { System.out.println("Violin play!"); } }
上述例子中,基类中的what方法用final修饰,使得继承他的子类都不能重写what方法,但是他们依然可以调用该方法。我们用private关键字依然可以不允许子类重写what方法,那么final修饰符有什么好处呢?
可以避免多态中“覆盖”私有方法的缺陷。看下面的例子。
public class Instrument { private void what() { System.out.println("This is an Instrument"); } public void play() { System.out.println("Instrument play!"); } public static void main(String[] args) { Instrument piano = new Piano(); Instrument violin = new Violin(); piano.play(); violin.play(); piano.what(); violin.what(); } } class Piano extends Instrument { public void what() { System.out.println("This is a Piano"); } public void play() { System.out.println("Piano play!"); } } class Violin extends Instrument { public void what() { System.out.println("This is a Violin"); } public void play() { System.out.println("Violin play!"); } }
运行结果:
Piano play! Violin play! This is an Instrument This is an Instrument
看起来,我们成功的运用了多态。编译成功了,但是what方法的输出与我们期望的不同,它调用了基类的方法。这是因为基类的私有方法,在子类中是不可见的,当然也不会被重写。在多态执行过程中,也就仅仅执行了基类的方法。
因此,可以得到结论:如果希望一个方法不被子类重写,却可以被子类调用,那就把它声明为final方法。
3. final类
当将某个类整体定义为final类的时候,这就表明这个类不打算允许被继承。或者出于安全考虑,你不希望这个类有子类。
4. 关于final的使用
在设计类时,有些方法没有必要被重写,将其指明为final的,会让设计变得更清晰更安全。但是过多的使用final方法,会使得你的类很难使用,因为子类没办法重写你的方法。