Java中“final”关键字

本文详细解析了Java中的final关键字,包括其在数据、方法及类上的应用。解释了final如何阻止改变,提高程序效率,并展示了final在不同场景下的使用示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

final 关键字最一般的意思就是声明“这个东西不能改变”。之所以要禁止改变,可能是考虑到两方面的因素:设计或效率。

讨论final 关键字的三种应用场合:数据、方法以及类。

1.1、f i n a l 数据

许多程序设计语言都有自己的办法告诉编译器某个数据是“常数”。常数主要应用于下述两个方面:
(1) 编译期常数,它永远不会改变
(2) 在运行期初始化的一个值,我们不希望它发生变化

对于基本数据类型,final 会将值变成一个常数;但对于对象句柄,final 会将句柄变成一个常数。进行声明时,必须将句柄初始化到一个具体的对象。而且永远不能将句柄变成指向另一个对象。然而,对象本身是可以修改的。

下面是演示final 字段用法的一个例子:

//The effect of final on fields
class Value {
	int i = 1;
}

public class FinalData {
	// Can be compile-time constants
	final int i1 = 9;
	static final int I2 = 99;
	// Typical public constant:
	public static final int I3 = 39;
	// Cannot be compile-time constants:
	final int i4 = (int) (Math.random() * 20);
	static final int i5 = (int) (Math.random() * 20);
	Value v1 = new Value();
	final Value v2 = new Value();
	static final Value v3 = new Value();
	// ! final Value v4; // Pre-Java 1.1 Error:
	// no initializer
	// Arrays:
	final int[] a = { 1, 2, 3, 4, 5, 6 };

	public void print(String id) {
		System.out.println(id + ": " + "i4 = " + i4 + ", i5 = " + i5);
	}

	public static void main(String[] args) {
		FinalData fd1 = new FinalData();
		// ! fd1.i1++; // Error: can't change value
		fd1.v2.i++; // Object isn't constant!
		fd1.v1 = new Value(); // OK -- not final
		for (int i = 0; i < fd1.a.length; i++)
			fd1.a[i]++; // Object isn't constant!
		// ! fd1.v2 = new Value(); // Error: Can't
		// ! fd1.v3 = new Value(); // change handle
		// ! fd1.a = new int[3];
		fd1.print("fd1");
		System.out.println("Creating new FinalData");
		FinalData fd2 = new FinalData();
		fd1.print("fd1");
		fd2.print("fd2");
	}
}


由于i1 和I2 都是具有final 属性的基本数据类型,并含有编译期的值,所以它们除了能作为编译期的常数使用外,在任何导入方式中也不会出现任何不同。I3 是我们体验此类常数定义时更典型的一种方式:public表示它们可在包外使用;Static 强调它们只有一个;而final 表明它是一个常数。注意对于含有固定初始化值(即编译期常数)的fianl static 基本数据类型,它们的名字根据规则要全部采用大写。也要注意i5 在编译期间是未知的,所以它没有大写。
不能由于某样东西的属性是final,就认定它的值能在编译时期知道。i4 和i5 向大家证明了这一点。它们在运行期间使用随机生成的数字。例子的这一部分也向大家揭示出将final 值设为static 和非static 之间的差异。只有当值在运行期间初始化的前提下,这种差异才会揭示出来。因为编译期间的值被编译器认为是相同的。这种差异可从输出结果中看出:



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

从v1 到v4 的变量向我们揭示出final 句柄的含义。正如大家在main()中看到的那样,并不能认为由于v2属于final,所以就不能再改变它的值。然而,我们确实不能再将v2 绑定到一个新对象,因为它的属性是final。这便是final 对于一个句柄的确切含义。我们会发现同样的含义亦适用于数组,后者只不过是另一种类型的句柄而已。


1.2、空白final

//"Blank" final data members
class Poppet {
}

class BlankFinal {
	final int i = 0; // Initialized final
	final int j; // Blank final
	final Poppet p; // Blank final handle
	// Blank finals MUST be initialized
	// in the constructor:

	BlankFinal() {
		j = 1; // Initialize blank final
		p = new Poppet();
	}

	BlankFinal(int x) {
		j = x; // Initialize blank final
		p = new Poppet();
	}

	public static void main(String[] args) {
		BlankFinal bf = new BlankFinal();
	}
}
现在强行要求我们对final 进行赋值处理——要么在定义字段时使用一个表达 式,要么在每个构建器中。这样就可以确保final 字段在使用前获得正确的初始化。

1.3、final自变量

将自变量设成final 属性,方法是在自变量列表中对它们进行适当的声明。这意味着在一个方法的内部,我们不能改变自变量句柄指向的东西。如下所示:
//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
		g.spin();
	}

	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);	// 会报空指针的错误
		
		Gizmo g = new Gizmo();
		bf.with(g);
	}
}

注意此时仍然能为final 自变量分配一个null(空)句柄,同时编译器不会捕获它。这与我们对非final 自变量采取的操作是一样的。
方法f()和g()向我们展示出基本类型的自变量为final 时会发生什么情况:我们只能读取自变量,不可改变它。

2、final方法

之所以要使用final 方法,可能是出于对两方面理由的考虑。第一个是为方法“上锁”,防止任何继承类改变它的本来含义。
采用final 方法的第二个理由是程序执行的效率。
类内所有private 方法都自动成为final。由于我们不能访问一个private 方法,所以它绝对不会被其他方法覆盖(若强行这样做,编译器会给出错误提示)。可为一个private 方法添加final 指示符,但却不能为那个方法提供任何额外的含义。

3、final类

//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。因为此时再也无法覆盖它们。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值