`

java中final的用法(转载)

阅读更多

许多程序设计语言都有自己的办法告诉编译器某个数据是“常数”。常数主要应用于下述两个方面:
(1) 编译期常数,它永远不会改变
(2) 在运行期初始化的一个值,我们不希望它发生变化
对于编译期的常数,编译器(程序)可将常数值“封装”到需要的计算过程里。也就是说,计算可在编译期间提前执行,从而节省运行时的一些开销(这个应该是static final 吧。。。我的想法错了的,编译期间执行和加载时执行是不同的,在编译时执行的必须是基本数据类型。在Java中,这些形式的常数必须属于基本数据类型(Primitives),而且要用final关键字进行表达。在对这样的一个常数进行定义的时候,必须给出一个值。
无论static还是final字段,都只能存储一个数据,而且不得改变。
若随同对象句柄使用final,而不是基本数据类型,它的含义就稍微让人有点儿迷糊了。对于基本数据类型,final会将值变成一个常数;但对于对象句柄, final会将句柄变成一个常数。进行声明时,必须将句柄初始化到一个具体的对象。而且永远不能将句柄变成指向另一个对象。然而,对象本身是可以修改的。 Java对此未提供任何手段,可将一个对象直接变成一个常数(但是,我们可自己编写一个类,使其中的对象具有“常数”效果)。这一限制也适用于数组,它也属于对象。
下面是演示final字段用法的一个例子:

package finaldata;

//: FinalData.java
//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");
		
		FinalData fd3 = new FinalData();
		fd3.print("fd3");
		//fd3.i1++;
		//fd2.i4++;
		System.out.println(fd3.a[1]);
		fd3.a[1]++;
		System.out.println(fd3.a[1]);
	}
} // /:~
 

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

 fd1: i4 = 15, i5 = 9
Creating new FinalData
fd1: i4 = 15, i5 = 9
fd2: i4 = 10, i5 = 9

注意对于fd1和fd2来说,i4的值是唯一的,但i5的值不会由于创建了另一个FinalData对象而发生改变。那是因为它的属性是static,而且在载入时初始化,而非每创建一个对象时初始化。
从v1 到v4的变量向我们揭示出final句柄的含义。正如大家在main()中看到的那样,并不能认为由于v2属于final,所以就不能再改变它的值。然而,我们确实不能再将v2绑定到一个新对象,因为它的属性是final。这便是final对于一个句柄的确切含义。我们会发现同样的含义亦适用于数组,后者只不过是另一种类型的句柄而已。将句柄变成final看起来似乎不如将基本数据类型变成final那么有用。

2. 空白final
Java 1.1允许我们创建“空白final”,它们属于一些特殊的字段。尽管被声明成final,但却未得到一个初始值。无论在哪种情况下,空白final都必须在实际使用前得到正确的初始化。而且编译器会主动保证这一规定得以贯彻。然而,对于final关键字的各种应用,空白final具有最大的灵活性。举个例子来说,位于类内部的一个final字段现在对每个对象都可以有所不同,同时依然保持其“不变”的本质。下面列出一个例子:

//: BlankFinal.java
// "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字段在使用前获得正确的初始化。

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

//: 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
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);
}
} ///:~
 

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

分享到:
评论

相关推荐

    java编写建议(转载)

    - Java 1.1 中的AWT组件应当谨慎使用。 - 选择合适的GUI框架,例如Swing或JavaFX,以提高用户界面的响应性和美观性。 #### 五、面向接口编程与抽象类 **5.1 接口与抽象类** - 当不确定类的具体实现细节时,优先...

    java编写弹跳的小球源代码(转载)

    2. **性能优化**:对于大量计算的任务(如`move`方法中未使用的循环),应考虑将其放在独立的线程中执行,以免影响主程序的流畅度。 3. **用户交互**:增加更多的用户交互功能,比如允许用户调整小球的颜色、速度等...

    Java字节码(.class文件)格式详解((转载)

    - **方法表集合**:描述类或接口中的方法信息,包括方法名、返回类型、参数列表等。 - **属性表集合**:包含其他各种属性信息,如代码、异常处理、源文件名等。 2. **字节码指令集** 字节码指令集是JVM执行的...

    Java面试题

    面试官可能会询问关于final关键字的用法,以及如何实现方法重载和覆盖。 2. **面向对象**:理解类、对象、封装、继承、多态等概念是必须的。深入讨论抽象类、接口、访问修饰符的区别和使用场景,以及如何利用多态...

    转载:cglib动态代理介绍(一)

    CGlib是Java编程语言中的一个库,主要用于创建子类,也称为子类代理或动态代理。这个库在很多场合被广泛使用,特别是在那些需要在运行时动态创建对象或增强已有对象功能的场景下,比如Spring AOP(面向切面编程)...

    Jboss7快速入门手册

    Jboss7是一种流行的Java应用服务器,它为开发者和系统管理员提供了部署、运行和管理Java EE应用的能力。Jboss7快速入门手册是一份指南,...如果读者在使用过程中发现文档中的错误,或在转载时应保持文档的最新状态。

    二十三种设计模式【PDF版】

    设计模式之 Template(模板方法) 实际上向你介绍了为什么要使用 Java 抽象类,该模式原理简单,使用很普遍. 设计模式之 Strategy(策略) 不同算法各自封装,用户端可随意挑选需要的算法. 设计模式之 Chain of ...

    spring源代码解析

    如转载请注明,转载自:关注Java[http://www.gbsou.com] 本文链接: http://www.gbsou.com/2009/08/11/214.html - - Java代码 public interface WebApplicationContext extends ApplicationContext { //这里定义...

Global site tag (gtag.js) - Google Analytics