4、final修饰符
- final可以修饰变量,被final修饰的变量被赋值之后,不能重新赋值
- final可以修饰方法,被final修饰的方法不能被重写
- final可以修饰类,被final修饰的类不能派生子类
4、1 final修饰变量
被final修饰的实例变量必须显示指定初始值,而且只能在如下三个位置指定初始值。
- 定义final实例变量时指定初始值。
- 非静态初始化块中为final实例变量指定初始值。
- 在构造器中为final实例变量指定初始值。
public class FinalTest {
final int var1 = "Java学习".length();
final int var2;
final int var3;
{
var2 = "Java实战编程".length();
}
public FinalTest() {
var3 = "Java程序设计与数据结构".length();
}
public static void main(String[] args) {
FinalTest fTest = new FinalTest();
System.out.println(fTest.var1);
System.out.println(fTest.var2);
System.out.println(fTest.var3);
}
}
输出结果为:
6
8
13
上面程序分别在三个不同的地方为final修饰的变量指定初试值,var1是在定义时赋初始值,var2是在非静态初始化块中赋初始值,var3是在构造器中赋初始值。虽然var1和var2变量不是在构造器中被赋初始值,但是从编译器可以看出,其实所有的final修饰的实例变量均在构造器中被赋初始值。
8
13
上面程序分别在三个不同的地方为final修饰的变量指定初试值,var1是在定义时赋初始值,var2是在非静态初始化块中赋初始值,var3是在构造器中赋初始值。虽然var1和var2变量不是在构造器中被赋初始值,但是从编译器可以看出,其实所有的final修饰的实例变量均在构造器中被赋初始值。
对于final类变量而言,同样必须显示地指定初始值,而且final类变量只能在以下两个地方指定初始值。
- 定义final类变量时指定初始值。
- 在静态初始化块中为final类变量指定初始值。
public class FinalTest1 {
final static int var1 = "Java学习".length();
final static int var2;
static {
var2 = "Java实战编程".length();
}
public static void main(String[] args) {
System.out.println(FinalTest1.var1);
System.out.println(FinalTest1.var2);
}
}
输出结果为:
6
8
8
final类变量必须显式地被赋初始值,而且本质上final类变量只能在静态初始化块中被赋初始值,因为即使在静态初始化块中为其赋值,构造器依然会将其在构造器中完成赋值工作。
修改对象及内存管理——实例变量与类变量中1、3中的程序如下:
class Price {
final static Price INSTANCE = new Price(2.8); //类成员是Price实例
final static double initPrice = 20; //修改
double currentPrice;
public Price(double discount) {
currentPrice = initPrice - discount;
}
}
public class PriceTest {
public static void main(String[] args) {
System.out.println(Price.INSTANCE.currentPrice); //①
Price p = new Price(2.8);
System.out.println(p.currentPrice); //②
}
}
输出结果为:
17.2
17.2
17.2
对比之前的之前的结果可以看到,当使用final修饰类变量时,如果定义该final类变量时指定了初始值,而且该初始值可以在编译时就被确定下来,系统将不会在静态初始块中对该类变量赋初始值,而是在类定义中直接使用该初始值代替该final变量,也就是说,所有出现该变量的地方,系统都将直接把它当成对应的值处理。
4、2 执行“宏变量”的变量
对于final变量,不管是类变量、实例变量还是局部变量,只要定义该变量时使用了final修饰符,并在定义final变量时指定初始值,而且该初始值可以在编译时就被确定下来,那么这个final变量本质上就不再是变量,而是相当于一个直接量。
注意:final修饰符的一个重要用途就是定义“宏变量”。当定义final变量时就为该变量制订了初始值,而且该初始值可以在编译时就确定下来,那么这个final变量本质上就是一个“宏变量”。
除了为final变量赋值时赋直接量的情况外,如果被赋的表达式只是基本的算术表达式或字符串连接运算,没有访问普通变量,调用方法,Java编译器同样会将这种final变量当成“宏变量”。
public class StringJoinTest {
public static void main(String[] args) {
String s1 = "Java语言学习";
String s2 = "Java语言" + "学习";
System.out.println("s1 == s2 :" + (s1 == s2));
String str1 = "Java语言";
String str2 = "学习";
String s3 = str1 + str2;
System.out.println("s1 == s3 :" + (s1 == s3));
}
}
输出结果为:
s1 == s2 :true
s1 == s3 :false
s1 == s3 :false
上面程序中,s1是一个普通字符串直接量“Java语言学习”,s2的值是两个字符串直接量进行连接运算,由于编译器可以在编译阶段就确定了s2的值是“Java语言学习”,所以系统就会让s2直接指向字符串池中缓存的“Java语言学习”字符串,由此可见,s1 == s2 :true。对于s3而言,它的值由str1和str2进行连接后运算的到,由于str1和str2只是普通变量,编译器不会执行“宏替换”,因此编译器无法再编译时就确定s3的值,不会让s3指向字符串池中缓存的“Java语言学习”字符串,由此可见,s1 == s3 :false。为了是s1 == s3 :true,可以将程序修改为如下所示的代码:
public class StringJoinTest {
public static void main(String[] args) {
String s1 = "Java语言学习";
String s2 = "Java语言" + "学习";
System.out.println("s1 == s2 :" + (s1 == s2));
final String str1 = "Java语言";
final String str2 = "学习";
String s3 = str1 + str2;
System.out.println("s1 == s3 :" + (s1 == s3));
}
}
对于实例变量而言,除了可以在定义变量时赋值,还可以在非静态初始化块、构造器周静对它赋初始值,但是对于final实例变量而言,只有在定义该变量时指定初始值才会有“宏变量”的效果,其他两种情况则不会有这种效果。对于类变量也一样如此。
public class FinalTest {
final String str1;
final String str2;
final String str3 = "Java";
{
str1 = "Java";
}
public FinalTest() {
str2 = "Java";
}
public void info() {
System.out.println(str1 + str1 == "JavaJava");
System.out.println(str2 + str2 == "JavaJava");
System.out.println(str3 + str3 == "JavaJava");
}
public static void main(String[] args) {
FinalTest fTest = new FinalTest();
fTest.info();
}
}
输出结果为:
false
false
true
false
true
上面程序定义了三个final实例变量,但是只有str3在定义该变量时指定初始值,另外两个分别在非静态初始化块中和构造器中指定初始值,因此系统不会对str1、str2执行“宏替换”,但会对str3执行“宏替换”。
4、3 被final修饰的方法不能重写
如果父类中某个方法使用了final修饰符进行修饰,那么这个方法将不可能被它的子类访问到。与此类似的是,如果父类和子类没有处于同一个包下,父类中包含的某个方法不使用访问控制符(相当于包访问权限)或者仅使用private访问控制符,那么子类也是无法重写该方法的。4、4 内部类中的局部变量
如果程序需要在匿名内部类中使用局部变量,那么这个局部变量必须使用final修饰符修饰。
import java.util.Arrays;
interface IntArrayProductor {
int product();
}
public class CommandTest {
public int[] process(IntArrayProductor cmd , int length) {
int[] result = new int[length];
for ( int i = 0 ; i < length ; i ++ ) {
result[i] = cmd.product();
}
return result;
}
public static void main(String[] args) {
CommandTest ct = new CommandTest();
final int seed = 5;
int[] result = ct.process(new IntArrayProductor() {
@Override
public int product() {
return (int)Math.round(Math.random() * seed);
}
}, 6);
System.out.println(Arrays.toString(result));
}
}
输出结果为:
[4, 2, 4, 2, 0, 2]
上面程序中定义了一个匿名内部类,这个匿名内部类实现了IntArrayProductor接口,该匿名内部类实现了product()方法访问了局部变量seed。这个局部变量必须使用final修饰,否则程序将会提示:Cannot refer to a non-final variable seed inside an inner class defined in a different method。
Java要求所有被内部类访问的局部变量都要使用final修饰。对于普通的局部变量而言,它的作用域就停留在该方法内,当方法执行结束后,该局部变量也就随之消失了。但是内部类则可能产生隐式的“闭包”,闭包将使得局部变量脱离它所在的方法继续存在。由于内部类可能扩大局部变量的作用域,如果被内部类访问的局部变量没有使用final修饰,那么将会引起极大的混乱,因此Java编译器要求被内部类访问的局部变量必须使用final修饰符进行修饰。