【JAVA】 final关键字

final关键字

Java关键字final在不同语境意思会有稍微不同,但总的意思是“不能改变”(定义常量)。之所以要禁止改变,可能是要考虑:设计和效率。

final数据
final主要用于定义常量,常量的用处主要在两点:

  1. 编译期常量(compile-time constant),永远不会改变
  2. 运行期初始化的值,不希望改变
    对于编译器常量,编译器允许将常量封装到使用计算过程中。即,会在编译期进行计算,避免运行时(run-time)不必要的开销(overhead)。Java中,这类常数必须为原始类型,用关键字final修饰。final常量在定义时必须要初始化

无论是staic还是final的字段都只能存储一个数据,且不能改变。

当final用于对象句柄,含义有点模糊。用final修饰对象句柄时,必须将句柄初始化到一个具体的对象。而且永远不能将句柄变成指向另一个对象。然而,对象本身是可以修改的。(可以这样理解指路的路牌上的文字是不变的,但是被指的路可能发生变化)Java对此未提供任何手段,可将一个对象直接变成一个常数
(但是,可以自己编写一个类,使其中的对象具有“常数”效果)。这一限制也适用于数组,它也属于对象。
见下例,注意一个惯例,既是static又是final的字段,其名字必须全部大写,用下划线分隔,例如VALUE_TWO

//: reusing/FinalData.java 
// The effect of final on fields. 
import java.util.*; 
import static net.mindview.util.Print.*; 
class Value { 
	int i; // Package access 
	public Value(int i) { this.i = i; } } 
	public class FinalData { 
		private static Random rand = new Random(47); 
		private String id; 
		public FinalData(String id) { this.id = id; } 
		// Can be compile-time constants: 
		private final int valueOne = 9; 
		private static final int VALUE_TWO = 99; 
		// Typical public constant: 
		public static final int VALUE_THREE = 39; 
		// Cannot be compile-time constants: 
		private final int i4 = rand.nextInt(20); 
		static final int INT_5 = rand.nextInt(20); 
		private Value v1 = new Value(11); 
		private final Value v2 = new Value(22); 
		private static final Value VAL_3 = new Value(33); 
		// Arrays: 
		private final int[] a = { 1, 2, 3, 4, 5, 6 }; 
		public String toString() { return id + ": " + "i4 = " + i4 + ", INT_5 = " + INT_5; }
			public static void main(String[] args) { 
		FinalData fd1 = new FinalData("fd1"); 
		//! fd1.valueOne++; // Error: can’t change value 
		fd1.v2.i++; 
		// Object isn’t constant! 
		fd1.v1 = new Value(9); 
		// OK -- not final 
		for(int i = 0; i < fd1.a.length; i++) 
		fd1.a[i]++; // Object isn’t constant! 
		//! fd1.v2 = new Value(0); // Error: Can’t 
		//! fd1.VAL_3 = new Value(1); // change reference //! 
		fd1.a = new int[3]; 
		print(fd1); 
		print("Creating new FinalData"); 
		FinalData fd2 = new FinalData("fd2"); 
		print(fd1); 
		print(fd2); 
	} 
} 
/* Output: 
fd1: i4 = 15, INT_5 = 18 
Creating new FinalData 
fd1: i4 = 15, INT_5 = 18 
fd2: i4 = 13, INT_5 = 18 
*///:~

注意public static final int VALUE_THREE是一种常见的定义常量的方法,其中public表明可在包外使用,static强调只有一个,final指明是常量

不能由于其属性是final,就认定能在编译时知道它的值。i4和i5向大家证明了这一点。它们在运行期间使用随机生成的数字。例子的这一部分也向大家揭示出将final值设为static和非static之间的差异。只有当值在运行期间初始化的前提下,这种差异才会揭示出来。因为编译期间的值被编译器认为是相同的

注意对于fd1和fd2来说,i4的值是唯一的,但i5的值不会由于创建了另一个FinalData对象而发生改变。那是因为它的属性是static,是在载入时初始化,而非每创建一个对象时初始化

见上例v1到VAL_3,在主方法中,并不能认为v2是final的就不能改变它的值。因为final修饰的是句柄,即不能再将v2绑定到新的对象上,但是句柄绑定的对象是可以改变的。同样的含义也适用于数组,因为后者是另一种类型的句柄。将句柄变成final看起来似乎不如将基本数据类型变成final那么有用。

空final
空final即字段被声明为final,但未被初始化。在任何情况下,空final在使用之前都必须被初始化。然而空final更具灵活性。比如,类内部的final字段对于每个对象都不同,但又能保持自己不变(immutable)的特性
见下例:

//: reusing/BlankFinal.java
// "Blank" final fields. 
class Poppet { 
	private int i; 
	Poppet(int ii) { i = ii; } 
} 
public class BlankFinal { 
	private final int i = 0; // Initialized final 
	private final int j; // Blank final 
	private final Poppet p; // Blank final reference 
	// Blank finals MUST be initialized in the constructor: 空final必须在构造方法中初始化
	public BlankFinal() {
		j = 1; // Initialize blank final 
		p = new Poppet(1); // Initialize blank final reference 
	} 
	public BlankFinal(int x) { 
		j = x; // Initialize blank final 
		p = new Poppet(x); // Initialize blank final reference 
	} 
	public static void main(String[] args) { 
		new BlankFinal(); 
		new BlankFinal(47); 
	} 
} ///:~

现在强制要求对final进行赋值处理——要么在定义字段时使用一个表达式,要么在每个构建器中。这样就可以确保final字段在使用前获得正确的初始化
记得在使用前初始化

final自变量
Java 1.1允许将自变量设成final属性,方法在自变量列表中对它们进行适当的声明。这意味着在一个方法的内部,不能改变自变量句柄指向的东西。如下所示:
例如:

//: reusing/FinalArguments.java 
// Using "final" with method arguments. 
class Gizmo { 
	public void spin() {} 
} 
public class FinalArguments { 
	void with(final Gizmo g) { 
		//! g = new Gizmo(); // Illegal -- g is final 
	} 
	void without(Gizmo g) { 
		g = new Gizmo(); // OK -- g not final 
		g.spin(); 
	} 
	// void f(final int i) { i++; } // Can’t change 
	// You can only read from a final primitive: 
	int g(final int i) { return i + 1; } 
	public static void main(String[] args) { 
		FinalArguments bf = new FinalArguments();
		bf.without(null); 
		bf.with(null); 
	} 
} ///:~

注意这时仍然可以为final自变量分配一个null(空)句柄,并且编译器不会捕获它。这和对非final自变量采取的操作是一样的。
方法f()和g()展示出,基本类型的自变量为final时只能读取自变量,不可改变。

final方法
使用final方法有两个原因:

  1. 给方法上锁,防止继承的类对其进行更改。在设计中使用final方法是为了确保继承时方法的行为得到保留,不被重写
  2. 确保程序执行的效率。将一个方法设成final后,编译器就可以把对那个方法的所有调用都置入“嵌入”调用(inline call)里。只要编译器发现一个final方法调用,就会(根据它自己的判断)忽略为执行方法调用机制而采取的常规代码插入方法(将参数压入堆栈;跳至方法代码并执行;跳回来;清除堆栈自变量;最后对返回值进行处理)。相反,它会用方法主体内实际代码的一个副本来替换方法调用(replace the method call with a copy of the actual code in the method body)。这样做可避免方法调用时的系统开销。当然,若方法体积太大,那么程序也会变得雍肿,可能受不到嵌入代码所带来的任何性能提升。因为任何提升都被花在方法内部的时间抵消了。

在最新版本中,让编译器和JVM处理效率问题,只有在明确表示不像让方法被重写的时候才用final

final类
final类不能被继承,不做任何更改,也不生成任何子类

//: Jurassic.java
// Making an entire class final

class SmallBrain {}

final class Dinosaur {
  int i = 7;
  int j = 1;
  SmallBrain x = new SmallBrain();
  void f() {}
}

//! class Further extends Dinosaur {}
// error: Cannot extend final class 'Dinosaur'

public class Jurassic {
  public static void main(String[] args) {
    Dinosaur n = new Dinosaur();
    n.f();
    n.i = 40;
    n.j++;
  }
} ///:~

注意:final类的字段可以是final也可以不是。但是因为final类不被继承,final类的方法也被隐式地为final,因为没法重写这些方法。可以给final类的方法添加final标识符,但没有什么意义。

final注意事项
慎用final,因为很难预测一个类以后会以什么样的形式再生或重复利用。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值