java - final关键字
final是java的保留关键字,字面意思是”最终的、不可更改的”,对应到java的使用场景完全适用。
java的final关键字可以修饰类、方法、成员变量、局部变量、方法参数。接下来将会分别说明这些用法。
用法
修饰类
final修饰类,表示该类是不可被继承的,该类已经足够完整了。jdk源码中的String
和Integer
等包装类都是final的。如String
类的声明
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
}
Integer
类的声明
public final class Integer extends Number implements Comparable<Integer> {
}
声明为final的类如果被子类继承,编译期即会报错。
修饰方法
final修饰方法,表示该方法不可被重写。如果确认方法功能已经足够完整,不需要子类重写,可以在方法前加上该修饰符。如下例
public class A {
//该方法被声明为final
public final void aMethod() {
}
}
public class B extends A{
//该方法试图重写类A的方法,编译期将会报错,提示final方法不可被重写
public final void Method() {
}
}
修饰成员变量
final修饰成员变量,表示该变量一旦被赋值就不能改变,final变量也称为java常量。
对于基本数据类型,表示该值本身不可被改变,对于指向对象的引用,表示该引用不可改变,但是引用指向的对象确是可以改变的。
final变量和其他普通的成员变量不太一样,普通成员变量会初始化为该类型的”零”值,例如:
public class Test {
//int型的"零值"是0
private int x;
private long y;
//对象引用的"零值"是null
private Long l;
private Object obj;
public void print() {
System.out.println(x);
System.out.println(y);
System.out.println(l);
System.out.println(obj);
}
public static void main(String[] args) {
Test test = new Test();
test.print();
}
}
将会打印出:
0
0
null
null
可以看出,int型默认为0,long型默认也是0,对象引用类型的默认为null。
如果成员变量用final来修饰,则必须对其进行初始化,否则编译期即报错,final变量有3种初始化方式:
直接赋值、构造函数、静态初始化块:
直接赋值
public class Test { //直接给final成员变量赋值 private final int x = 0; }
构建函数
public class Test { private final int x; public Test() { //构造函数初始化 x = 0; } }
静态初始化块
public class Test { private static final int x; static { //static final修饰的成员变量只能通过直接赋值或者静态初始化块赋值 x = 0; } }
final修饰的成员变量存储在常量池中
修饰局部变量
final还可以修饰局部变量,final修饰的局部变量在使用前必须显示初始化,如果该变量未被使用则不需要初始化。
同样地,final修饰的局部变量一经赋值,就不能更改。
另外,方法中的局部内部类(包括匿名内部类)访问方法的局部变量必须是final修饰的,例如:
public class Outer {
public Object method() {
//被局部内部类访问的局部变量必须是final修饰的
final int k = 0;
class Inner {
void innerMethod() {
//此处引用了方法的局部变量
System.out.println(k);
}
}
return new Inner();
}
}
方法中的普通变量在线程退出该方法之后,局部变量k就消失了,但是方法method的返回值对该变量有引用,可能会访问到不存在的变量。所以该变量在方法退出之后不能消失。那么java是如何解决的呢?
我们知道内部类和外部类是如何通信的,内部类其实是持有外部类的引用Outer.this,通过这个引用可以访问外部类的成员变量。方法中的局部内部类也是如此(方法中的内部类也有自己独立的类文件)。
局部内部类如果想访问方法的局部变量按理来说是没法办到的,但是为了解决这个问题,java通过将方法中的局部变量传递给(通过局部类的构造函数)局部内部类并变成局部内部类的成员变量来实现的,以后局部内部类访问方法的局部变量其实是在访问自己的成员变量,也就是说局部内部类维护了方法局部变量的副本,既然是副本,那就有一致性的问题,如何保证局部变量的值和局部内部类中该变量副本的值是一样的?利用final来保证,将变量用final修饰,禁止修改。
此时方法退出后,局部内部类仍然可以访问到自己的成员变量(该变量是方法局部变量的副本),造成了局部内部类仍然可以访问方法局部变量的假象。
修饰方法参数
方法参数其实也是局部变量,不再说明。
注意事项
java引入final关键字其中一个重要原因是效率,那么为何用final修饰后会提高效率呢?
编译器遇到final方法时会采用内联机制,节省了方法调用的开销,但是如果方法体过大,编译器可能不会采用内联机制。
参考:
- Thinking in java
- http://blog.csdn.net/zhangjg_blog/article/details/19996629 (这篇分析内部类的文章非常好,分析的很透彻)