展望2011

2007-01-08 | java的final关键字【转】

上一篇 / 下一篇  2007-04-27 18:07:49 / 个人分类:编程基础

final关键字51Testing软件测试网$[5S$eTV
由于语境(应用环境)不同,final关键字的含义可能会稍微产生一些差异。但它最一般的意思就是声明“这个东西不能改变”。之所以要禁止改变,可能是考虑到两方面的因素:设计或效率。由于这两个原因颇有些区别,所以也许会造成final关键字的误用。51Testing软件测试网:\ P'j^'N7F0W#@-FT
在接下去的小节里,我们将讨论final关键字的三种应用场合:数据、方法以及类。
51Testing软件测试网+V/e@ A2PpX

一 final数据51Testing软件测试网$RR.X+VnE pY
许多程序设计语言都有自己的办法告诉编译器某个数据是“常数”。常数主要应用于下述两个方面:
r0fX7fH9w,\MW0(1) 编译期常数,它永远不会改变
Tui!x lY0(2) 在运行期初始化的一个值,我们不希望它发生变化
(FM/s*s X9R`#n `5X0对于编译期的常数,编译器(程序)可将常数值“封装”到需要的计算过程里。也就是说,计算可在编译期间提前执行,从而节省运行时的一些开销。在Java中,这些形式的常数必须属于基本数据类型(Primitives),而且要用final关键字进行表达。在对这样的一个常数进行定义的时候,必须给出一个值。
-S)i"FQ Ry y eC&H0无论static还是final字段,都只能存储一个数据,而且不得改变。51Testing软件测试网O#b_Pk_'emN
若随同对象句柄使用final,而不是基本数据类型,它的含义就稍微让人有点儿迷糊了。对于基本数据类型,final会将值变成一个常数;但对于对象句柄,final会将句柄变成一个常数。进行声明时,必须将句柄初始化到一个具体的对象。而且永远不能将句柄变成指向另一个对象。然而,对象本身是可以修改的。Java对此未提供任何手段,可将一个对象直接变成一个常数(但是,我们可自己编写一个类,使其中的对象具有“常数”效果)。这一限制也适用于数组,它也属于对象。51Testing软件测试网 s,o7`aX,t,Q
下面是演示final字段用法的一个例子:
51Testing软件测试网Dbp?(ECO9W?

I-h Wn2e(@G9E0//: FinalData.java
s:T%M9b7nza2c|f0// The effect of final on fields
51Testing软件测试网0sd%Mb,V^b~U

"CK7OZ;T%`V0class Value {51Testing软件测试网:Dj,Va j6H` Bi
  int i = 1;51Testing软件测试网V|WR2E$yx*w
}
51Testing软件测试网&w.n/XxX o6`E2g

51Testing软件测试网)i.K+e I3i l,rfn3k

public class FinalData {51Testing软件测试网7rv q N {;l
  // Can be compile-time constants51Testing软件测试网9J&\w_$^iRVT(B
  final int i1 = 9;
Siy]~*N_bA0  static final int I2 = 99;51Testing软件测试网-C5ai-CO1H+\V
  // Typical public constant:51Testing软件测试网E7jW2\+~s*r ^
  public static final int I3 = 39;
L0b.J,Z$m0  // Cannot be compile-time constants:
ATG)a e,_5G%hj*M0  final int i4 = (int)(Math.random()*20);
s'{ A2E}0  static final int i5 = (int)(Math.random()*20);51Testing软件测试网9K1L1Gh^?V
 51Testing软件测试网F#QOxxB2~|
  Value v1 = new Value();51Testing软件测试网&PBwWO$Bl
  final Value v2 = new Value();51Testing软件测试网t,M5EW*w5[
  static final Value v3 = new Value();
_c;y7[%h ^wE0  //! final Value v4; // Pre-Java 1.1 Error:51Testing软件测试网M:m%M8yb-}j%wS
                      // no initializer51Testing软件测试网/C8Npm6D/~(F
  // Arrays:
&m.C~-} ~'i_T0  final int[] a = { 1, 2, 3, 4, 5, 6 };

oPAMhn&Mw0

DL8KXQ]3E'd0  public void print(String id) {
1b c6v^[-L6R0    System.out.println(
m5}:h{`#Fk,U0      id + ": " + "i4 = " + i4 +
0I$lu-d2f2^Bl!I0      ", i5 = " + i5);51Testing软件测试网{t0W1wk:u T
  }
!gq']+xQ?|5h1N-Qn0  public static void main(String[] args) {
xp]ee~0    FinalData fd1 = new FinalData();
A3@Q'idC:L0    //! fd1.i1++; // Error: can't change value
la!X$qq0    fd1.v2.i++; // Object isn't constant!51Testing软件测试网'u2t*q#mf
    fd1.v1 = new Value(); // OK -- not final51Testing软件测试网%r6h%_ {(@rv5u
    for(int i = 0; i < fd1.a.length; i++)
3}!v)[6R;a5O0      fd1.a[i]++; // Object isn't constant!
J9`vZ9`0    //! fd1.v2 = new Value(); // Error: Can't
b$?v"\9]&^vK0    //! fd1.v3 = new Value(); // change handle51Testing软件测试网]Gn Qc$LI
    //! fd1.a = new int[3];
51Testing软件测试网A7ho9\:|*D*st'LBe

0o1r+C&T-E0    fd1.print("fd1");51Testing软件测试网 l+~T.ZZ+_#q V W$n
    System.out.println("Creating new FinalData");51Testing软件测试网? ZFE0aQ
    FinalData fd2 = new FinalData();51Testing软件测试网&rp"W#R kd&f Q3B5l B\R
    fd1.print("fd1");
V J1o$yj?0    fd2.print("fd2");
+uT4\k%},p B#a0  }51Testing软件测试网 E h6HS-Jvq0D
} ///:~
51Testing软件测试网9t$me/cL u(V G

51Testing软件测试网C q iD2h)K"Y!rE

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

mb E Ei4|Du1V0fd1: i4 = 15, i5 = 9
C%|Vi au]WLj0Creating new FinalData51Testing软件测试网A)B0~7e OFA/z9|
fd1: i4 = 15, i5 = 951Testing软件测试网/\;U:b[;]6{
fd2: i4 = 10, i5 = 9
51Testing软件测试网6US4["P D Fc!j.R,l

2KQ2h]8zm v&Hc[0注意对于fd1和fd2来说,i4的值是唯一的,但i5的值不会由于创建了另一个FinalData对象而发生改变。那是因为它的属性是static,而且在载入时初始化,而非每创建一个对象时初始化。
-W'S iZ F*B4x ^1Z0从v1到v4的变量向我们揭示出final句柄的含义。正如大家在main()中看到的那样,并不能认为由于v2属于final,所以就不能再改变它的值。然而,我们确实不能再将v2绑定到一个新对象,因为它的属性是final。这便是final对于一个句柄的确切含义。我们会发现同样的含义亦适用于数组,后者只不过是另一种类型的句柄而已。将句柄变成final看起来似乎不如将基本数据类型变成final那么有用。
51Testing软件测试网}BGMC*Nt6B

51Testing软件测试网.U"nPiKL2hA1QA;G*]

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

,mk,IB8@z w"ZO-f#m0//: BlankFinal.java
M.X*E/QF/B;FfU0// "Blank" final data members
51Testing软件测试网 ^ He1~\4|1L `x

uJY1c~&i0class Poppet { }51Testing软件测试网ro] Kbl

t._:u]zwA9q0class BlankFinal {
.n%@Ix B(a K&px0  final int i = 0; // Initialized final51Testing软件测试网z)Az5u}E
  final int j; // Blank final51Testing软件测试网 d n!N6Qz
  final Poppet p; // Blank final handle51Testing软件测试网-K:o@r5Q.Sg6tEe[E
  // Blank finals MUST be initialized
PWfc4m9c0  // in the constructor:51Testing软件测试网b%d:R8Y4F3C_9U
  BlankFinal() {51Testing软件测试网0v;J8aB4V3Fh
    j = 1; // Initialize blank final51Testing软件测试网/UL&i|x oO&{u3L YZ5o
    p = new Poppet();
l"d*UC zc0Y&s0  }51Testing软件测试网w+~bW:QYY
  BlankFinal(int x) {
f"aw-CO{n1kf0    j = x; // Initialize blank final
0x$sIX,g0    p = new Poppet();51Testing软件测试网X2w6u1vcLO/g
  }
1?;O;p/g n!C}0  public static void main(String[] args) {51Testing软件测试网mZ7Ra/C
    BlankFinal bf = new BlankFinal();51Testing软件测试网gm ?x j3h\
  }51Testing软件测试网2E*poc%V
} ///:~

%y^lg T5C8W,T0

$FRtzl s6[&Y%D0现在强行要求我们对final进行赋值处理——要么在定义字段时使用一个表达 式,要么在每个构建器中。这样就可以确保final字段在使用前获得正确的初始化。51Testing软件测试网D8X1c^f7qJ

51Testing软件测试网}HD2Kh szL%I

3. final自变量
3c@c~6OJ&I;q0Java 1.1允许我们将自变量设成final属性,方法是在自变量列表中对它们进行适当的声明。这意味着在一个方法的内部,我们不能改变自变量句柄指向的东西。如下所示:
51Testing软件测试网XQM8fHs

T]Q3f1j$o0//: FinalArguments.java51Testing软件测试网_V6B2`![7L
// Using "final" with method arguments

%{#m X tq;Y x\]Y051Testing软件测试网C&Fa+D j.|

class Gizmo {
Bz.}Y"H(? A0  public void spin() {}51Testing软件测试网 y"ZP2` _8nT FY
}

f o/vYZ)}8O051Testing软件测试网sO;`6V1HD'M P

public class FinalArguments {
s0vK ?U3X+A0  void with(final Gizmo g) {51Testing软件测试网2{B7vi'@wpqQ!n
    //! g = new Gizmo(); // Illegal -- g is final
d b,U`q&U0    g.spin();51Testing软件测试网@ jXJ SU'j'O4D%ta
  }51Testing软件测试网AG7j/t{:Z
  void without(Gizmo g) {
.n#c%i D'P WC$\x.b0    g = new Gizmo(); // OK -- g not final
F D{5E+t0    g.spin();51Testing软件测试网e K"Cu2V/L5T
  }51Testing软件测试网H'Ou B0H
  // void f(final int i) { i++; } // Can't change51Testing软件测试网f7r YoSpH
  // You can only read from a final primitive:
+o.G0dE9{cE!j0  int g(final int i) { return i + 1; }51Testing软件测试网,RS?]7N$VLu
  public static void main(String[] args) {51Testing软件测试网Z+}eH%{$Qj!@
    FinalArguments bf = new FinalArguments();51Testing软件测试网j2Nql+m~
    bf.without(null);51Testing软件测试网EV)W f8^?b._
    bf.with(null);51Testing软件测试网.L(mMv u
  }51Testing软件测试网3x5f3h,lD6h!M
} ///:~
51Testing软件测试网.wRo R _,d F\K2t

51Testing软件测试网1V(Ez:HO]J

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

Y D&E"c3T _,s#X%~ p0

&u-gp9Tg{t0二 final方法
1ap:}Y:u Z[0之所以要使用final方法,可能是出于对两方面理由的考虑。第一个是为方法“上锁”,防止任何继承类改变它的本来含义。设计程序时,若希望一个方法的行为在继承期间保持不变,而且不可被覆盖或改写,就可以采取这种做法。
y e`6m4Sks0采用final方法的第二个理由是程序执行的效率。将一个方法设成final后,编译器就可以把对那个方法的所有调用都置入“嵌入”调用里。只要编译器发现一个final方法调用,就会(根据它自己的判断)忽略为执行方法调用机制而采取的常规代码插入方法(将自变量压入堆栈;跳至方法代码并执行它;跳回来;清除堆栈自变量;最后对返回值进行处理)。相反,它会用方法主体内实际代码的一个副本来替换方法调用。这样做可避免方法调用时的系统开销。当然,若方法体积太大,那么程序也会变得雍肿,可能受到到不到嵌入代码所带来的任何性能提升。因为任何提升都被花在方法内部的时间抵消了。Java编译器能自动侦测这些情况,并颇为“明智”地决定是否嵌入一个final方法。然而,最好还是不要完全相信编译器能正确地作出所有判断。通常,只有在方法的代码量非常少,或者想明确禁止方法被覆盖的时候,才应考虑将一个方法设为final。
"d1b N k6V @/L)k%E$L0类内所有private方法都自动成为final。由于我们不能访问一个private方法,所以它绝对不会被其他方法覆盖(若强行这样做,编译器会给出错误提示)。可为一个private方法添加final指示符,但却不能为那个方法提供任何额外的含义。

7}X3MW)w&GU051Testing软件测试网cFgA6L,Vm?/i

三 final类
he[B1Q%?(X0如果说整个类都是final(在它的定义前冠以final关键字),就表明自己不希望从这个类继承,或者不允许其他任何人采取这种操作。换言之,出于这样或那样的原因,我们的类肯定不需要进行任何改变;或者出于安全方面的理由,我们不希望进行子类化(子类处理)。
#@0p"|.Q'`` h~0u Y0除此以外,我们或许还考虑到执行效率的问题,并想确保涉及这个类各对象的所有行动都要尽可能地有效。如下所示:
51Testing软件测试网iX6@|1@dob$b:M

mfHNM4q#s8k0//: Jurassic.java51Testing软件测试网C#h`"p4}1[N0_
// Making an entire class final

h$ez2M4t5\051Testing软件测试网4{oE1HhST,v

class SmallBrain {}51Testing软件测试网.`&\ o#x[

I ?W:j0e8s#|-Af0final class Dinosaur {51Testing软件测试网I$FoVsr"x
  int i = 7;
)D:KJ.|,M-~A0  int j = 1;51Testing软件测试网h {L$F8\8[#q5n
  SmallBrain x = new SmallBrain();51Testing软件测试网)v z7V;c-g*K
  void f() {}51Testing软件测试网 givJBux
}

ZHiOD_051Testing软件测试网{$jq0[*i c

//! class Further extends Dinosaur {}
MU^$e4`(Z0// error: Cannot extend final class 'Dinosaur'

;l&jp"VW!j-`g&L!]0

~:t/~*tr7oNP0public class Jurassic {51Testing软件测试网|7H }rsz
  public static void main(String[] args) {51Testing软件测试网i6yJ;TdKcn;y
    Dinosaur n = new Dinosaur();
zM~/zFi7jF0    n.f();
%Na X,lY0    n.i = 40;51Testing软件测试网;p\"{ iA
    n.j++;51Testing软件测试网1RaJ6V,v!O
  }
Y%|T%T#[ k[0} ///:~
51Testing软件测试网$L{.\4V8A*Kx:b3b1[

51Testing软件测试网:{qECa"O#M5f

注意数据成员既可以是final,也可以不是,取决于我们具体选择。应用于final的规则同样适用于数据成员,无论类是否被定义成final。将类定义成final后,结果只是禁止进行继承——没有更多的限制。然而,由于它禁止了继承,所以一个final类中的所有方法都默认为final。因为此时再也无法覆盖它们。所以与我们将一个方法明确声明为final一样,编译器此时有相同的效率选择。
(Km,qK7F{C5u0可为final类内的一个方法添加final指示符,但这样做没有任何意义。
51Testing软件测试网?#T'f~9N

P8T+S z'~8lv0四 final的注意事项
9|:}#On z*Yb0设计一个类时,往往需要考虑是否将一个方法设为final。可能会觉得使用自己的类时执行效率非常重要,没有人想覆盖自己的方法。这种想法在某些时候是正确的。
%V)e-d/u;yh#p(I _y?0但要慎重作出自己的假定。通常,我们很难预测一个类以后会以什么样的形式再生或重复利用。常规用途的类尤其如此。若将一个方法定义成final,就可能杜绝了在其他程序员的项目中对自己的类进行继承的途径,因为我们根本没有想到它会象那样使用。51Testing软件测试网^*u\$n%[x
标准Java库是阐述这一观点的最好例子。其中特别常用的一个类是Vector。如果我们考虑代码的执行效率,就会发现只有不把任何方法设为final,才能使其发挥更大的作用。我们很容易就会想到自己应继承和覆盖如此有用的一个类,但它的设计者却否定了我们的想法。但我们至少可以用两个理由来反驳他们。首先,Stack(堆栈)是从Vector继承来的,亦即Stack“是”一个Vector,这种说法是不确切的。其次,对于Vector许多重要的方法,如addElement()以及elementAt()等,它们都变成了synchronized(同步的)。正如在第14章要讲到的那样,这会造成显著的性能开销,可能会把final提供的性能改善抵销得一干二净。因此,程序员不得不猜测到底应该在哪里进行优化。在标准库里居然采用了如此笨拙的设计,真不敢想象会在程序员里引发什么样的情绪。
9~$E}g*B,K}0另一个值得注意的是Hashtable(散列表),它是另一个重要的标准类。该类没有采用任何final方法。正如我们在本书其他地方提到的那样,显然一些类的设计人员与其他设计人员有着全然不同的素质(注意比较Hashtable极短的方法名与Vecor的方法名)。对类库的用户来说,这显然是不应该如此轻易就能看出的。一个产品的设计变得不一致后,会加大用户的工作量。这也从另一个侧面强调了代码设计与检查时需要很强的责任心。
51Testing软件测试网SI9fd1X


TAG: JAVA final 编程基础

 

评分:0

我来说两句

Open Toolbar