final关键字
java中的final关键字通常是指它所修饰的元素“是无法改变的”。而根据它所修饰的元素的不同,所起的作用存在着细微的区别。下面就讨论可能用到final的三种情况:数据、方法和类。
final数据
final修饰变量本身并不复杂,就是变量一经初始化就不能再改变(如果是基本数据类型,就是其数值不可以改变;如果是引用类型,就是其不可以再重新指向其他对象)。
关于final成员变量的初始化——总之一句话:就是在构造方法调用结束之前完成final成员变量的初始化。
很多文章都这么说——其初始化可以在两个地方,一是其定义处,二是在构造方法中,两者只能选其一。这种说法是不正确的,final变量可以在任何可以被初始化的地方初始化,但只能被初始化一次。一旦被初始化后就不能再次赋值。作为成员变量一定要显式初始化,而作为临时变量则可以只定义不初始化(当然也不能引用)——既空白final。下面是final成员变量初始化的小例子:
package com.ygc;
import java.util.Random;
/**
* final成员变量初始化的几种情况, 可以在定义处初始化,可以在语句块中初始化,可以在构造方法中初始化。
* 可以采用随机值进行初始化,甚至可以使用方法进行初始化,只要初始化在构造方法结束前完成,并且只初始化一次就可以。
* */
public class FinalFieldInitialization {
Random rand = new Random(47);
/**
* 可以在定义时初始化, 可以使用随机值初始化
* */
final int finalIntegerValue = 1;
final int randomFinalIntegerValue = rand.nextInt();
final String finalReference = "final reference";
/**
* 可以在语句块中初始化
* */
final int finalIntegerValue1;
final int randomFinalIntegerValue1;
final String finalReference1;
{
finalIntegerValue1 = 1;
randomFinalIntegerValue1 = rand.nextInt();
finalReference1 = "final reference";
}
/**
* 可以在构造方法中初始化, 有多少个构造方法就得初始化多少次
* */
final int finalIntegerValue2;
final String finalReference2;
public FinalFieldInitialization() {
finalIntegerValue2 = 1;
finalReference2 = "final reference";
}
public FinalFieldInitialization(int i) {
finalIntegerValue2 = 1;
finalReference2 = "final reference";
}
/**
* 使用方法初始化
* */
public FinalFieldInitialization(String s) {
finalIntegerValue2 = 1;
finalReference2 = getString();
}
private String getString() {
return "final refrence";
}
}
final修饰方法参数
final修饰方法参数的效果也是一样的,如果修饰的是基本数据类型,则不可以修改它的值;而如果修饰的是引用类型,则不能重新指向其它对象,但对象本身是可以改变的。而final修饰方法参数最常用的目的就是供匿名内部类使用,而且如果想要在匿名内部类中使用局部变量或者方法参数,则必须使用final修饰,而使用外部类的成员变量则不需要使用final修饰。因为当你在匿名内部类中使用局部变量或方法参数时,编译器会把它传递给匿名内部类的构造方法,然后隐式地对它做一个拷贝,使它成为匿名内部类的一个成员变量,而如果这个局部变量或方法参数不是final的,那么他们在匿名内部类的内部或外部就可以被修改(如果是基本数据类型 就可以修改它们的数值;而如果是引用类型,可以修改它们所指向的对象),一旦对这个变量进行了修改,就会导致外部变量和内部变量的不一致,从而产生一些不可预测的错误。为了避免这种情况的发生,匿名内部类在引用局部变量或方法参数时必须使用final修饰。而使用成员变量时,编译器会隐式地给匿名内部类传入一个外部类的this引用 ,而这个this引用是final的,所以匿名 内部类可以随意地访问外部类的成员。下面是一个小例子:
private String enclosingClassField;
private void updateStatus() {
final StringBuffer stringBuffer = new StringBuffer();
Runnable doUpdateStatus = new Runnable() {
public void run() {
// 你可以使用FinalFieldInitialization.this, 因为它永远都是final的
FinalFieldInitialization.this.enclosingClassField = "";
// 下面这种写法是简便的写法,因为每一个内部类在实例化的时候,编译器都会隐式地帮它们添加一个外部类的this引用
enclosingClassField = "";
// 你不可以改变stringBuffer的值,但是你可以修改它指向的对象的内容
stringBuffer.append("hello final");
}
};
}
上述说法可以通过查看字节码得到 验证,有兴趣的同学可以研究一下。
final方法
final修饰方法的第一个原因是把方法锁定,以防止任何继承类修改它的定义。既,用final修饰的方法不可以重写。
final修饰方法的第二个原因是为了提高效率,在早期的java实现中会起到一定的作用,现在不建议使用,因为使用final修饰方法并不会得到性能的显著提升。所以只有当你确定你的方法不想被重写时,才应该考虑用final修饰。
类中所有private修饰的方法都隐式地被指定为final的。因为在继承类中无法访问private方法,所以也无法覆盖。如果你试图重写一个父类中的private修饰的方法,实际上你只是定义了一个新的方法。
class WithFinals {
// Identical to "private" alone:
private final void f() {
print("WithFinals.f()");
}
// Also automatically "final":
private void g() {
print("WithFinals.g()");
}
}
class OverridingPrivate extends WithFinals {
private final void f() {
print("OverridingPrivate.f()");
}
private void g() {
print("OverridingPrivate.g()");
}
}
class OverridingPrivate2 extends OverridingPrivate {
public final void f() {
print("OverridingPrivate2.f()");
}
public void g() {
print("OverridingPrivate2.g()");
}
}
public class FinalOverridingIllusion {
public static void main(String[] args) {
OverridingPrivate2 op2 = new OverridingPrivate2();
op2.f();
op2.g();
// You can upcast:
OverridingPrivate op = op2;
// But you can't call the methods:
// ! op.f();
// ! op.g();
// Same here:
WithFinals wf = op2;
// ! wf.f();
// ! wf.g();
}
}
“覆盖”只有在某方法是基类的接口的一部分时才会出现。即,必须能将一个对象向上转型为它的基本类型并调用相同的方法。如果某方法为private,它就不是基类接口的一部分。它仅是一些隐藏于类中的程序代码,只不过具有相同的名称而已。但如果在导出类中以相同的名称生成一个public、protected 或包访问权限(package access)方法的话,此时你并没有覆盖该方法,你仅是生成了一个新的方法。
final 类
final修饰类的目的在于你不想有任何类从此类继承。final类中的所有方法都隐式地是final的,因为类无法被继承,所以其中的方法也不会被重写。但是final类中的域并不是final的,除非 显示地设置。