对象及内存管理——final修饰符

4、final修饰符

  • final可以修饰变量,被final修饰的变量被赋值之后,不能重新赋值
  • final可以修饰方法,被final修饰的方法不能被重写
  • final可以修饰类,被final修饰的类不能派生子类

4、1 final修饰变量

        被final修饰的实例变量必须显示指定初始值,而且只能在如下三个位置指定初始值。
  • 定义final实例变量时指定初始值。
  • 非静态初始化块中为final实例变量指定初始值。
  • 在构造器中为final实例变量指定初始值。
       对于普通的实例变量,Java可以对它执行默认的初始化,也就是将实例变量的值指定为默认的初始值0或null;但对于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修饰的实例变量均在构造器中被赋初始值。

       对于final类变量而言,同样必须显示地指定初始值,而且final类变量只能在以下两个地方指定初始值。
  1. 定义final类变量时指定初始值。
  2. 在静态初始化块中为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
       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
       对比之前的之前的结果可以看到,当使用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是一个普通字符串直接量“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
       上面程序定义了三个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修饰符进行修饰。


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值