+ Y" P( B1 a& A% ?, Lfinal使得被修饰的变量"不变",但是由于对象型变量的本质是“引用”,使得“不变”也有了两种含义:引用本身的不变,和引用指向的对象不变。 ; x( `- e) _: V; }/ u; Q& p9 l) R" }5 O/ r" H
引用本身的不变: . U) z9 u& l8 ], {, w* E ' q9 ?; z3 R, I/ |final StringBuffer a=new StringBuffer("immutable"); $ i9 q# z: z1 z: |2 m/ L( B
final StringBuffer b=new StringBuffer("not immutable"); * i: [" t$ S8 U, q, Q5 Ta=b;//编译期错误 8 y" p7 f3 C' k/ B& j引用指向的对象不变: 1 H' Q! W' b, Y0 \ ! R8 D" W8 y: L t8 qfinal StringBuffer a=new StringBuffer("immutable"); $ v8 d6 L- m: ^0 y, Z8 y
a.append(" broken!"); //编译通过 * ~7 |' E1 U4 d( m! u) w5 y
可见,final只对引用的“值”(也即它所指向的那个对象的内存地址)有效,它迫使引用只能指向初始指向的那个对象,改变它的指向会导致编译期错误。至于它所指向的对象的变化,final是不负责的。这很类似==操作符:==操作符只负责引用的“值”相等,至于这个地址所指向的对象内容是否相等,==操作符是不管的。 理解final问题有很重要的含义。许多程序漏洞都基于此----final只能保证引用永远指向固定对象,不能保证那个对象的状态不变。在多线程的操作中,一个对象会被多个线程共享或修改,一个线程对对象无意识的修改可能会导致另一个使用此对象的线程崩溃。一个错误的解决方法就是在此对象新建的时候把它声明为final,意图使得它“永远不变”。其实那是徒劳的。 : _8 E! A3 o/ n/ s5 r. j/ m; n7 z3 I J3 e
问题五:到底要怎么样初始化! 3 _# V; {( Y" g1 C% ~) U4 b$ R7 }7 K1 @% @* Q1 x2 N, C9 v8 x
本问题讨论变量的初始化,所以先来看一下Java中有哪些种类的变量。 " F" f2 M, ? c( g K" }5 E 0 w3 B6 j# _ p! X2 h; E' \7 s1. 类的属性,或者叫值域 4 |6 ^ q2 j, w; i4 j; }9 o ' U K& q" w$ ^) t3 J2. 方法里的局部变量 I& u: j$ V: R* K5 w4 } 7 s; ^( i7 t$ ^( O' d4 |) y3. 方法的参数7 Y/ y1 ?0 [& d6 {% w
- M0 [: z+ z/ S. l( H对于第一种变量,Java虚拟机会自动进行初始化。如果给出了初始值,则初始化为该初始值。如果没有给出,则把它初始化为该类型变量的默认初始值。 ) W; Z2 I1 I3 R ; T3 @0 r2 y; eint类型变量默认初始值为0 ) o3 c6 M, l! x, p, X! A4 e8 V N+ v& T3 l F
float类型变量默认初始值为0.0f$ H, |3 u9 |1 q/ h
$ O# R$ @! I6 n. x% A
double类型变量默认初始值为0.0" e4 k( {: V4 F& y9 y E
H. S, M1 b! dboolean类型变量默认初始值为false8 v ~" E* F, \* n/ o5 ]
! W! K5 ]4 _3 q# ~1 Ochar类型变量默认初始值为0(ASCII码)+ |5 `9 w0 o6 @/ y
) x' J+ ?! q, u: G
long类型变量默认初始值为0" M) B" ?- I g
* I( b: c- W; X {9 l0 `& q9 h8 c7 V
所有对象引用类型变量默认初始值为null,即不指向任何对象。注意数组本身也是对象,所以没有初始化的数组引用在自动初始化后其值也是null。 ( W: T+ d1 C8 A2 `& L4 j: i" ]7 X7 X$ X! d
对于两种不同的类属性,static属性与instance属性,初始化的时机是不同的。instance属性在创建实例的时候初始化,static属性在类加载,也就是第一次用到这个类的时候初始化,对于后来的实例的创建,不再次进行初始化。这个问题会在以后的系列中进行详细讨论。6 }9 F, T. b6 T, E' m# q Z* m% X
" x# A8 q; c r9 u; E' x对于第二种变量,必须明确地进行初始化。如果再没有初始化之前就试图使用它,编译器会抗议。如果初始化的语句在try块中或if块中,也必须要让它在第一次使用前一定能够得到赋值。也就是说,把初始化语句放在只有if块的条件判断语句中编译器也会抗议,因为执行的时候可能不符合if后面的判断条件,如此一来初始化语句就不会被执行了,这就违反了局部变量使用前必须初始化的规定。但如果在else块中也有初始化语句,就可以通过编译,因为无论如何,总有至少一条初始化语句会被执行,不会发生使用前未被初始化的事情。对于try-catch也是一样,如果只有在try块里才有初始化语句,编译部通过。如果在catch或finally里也有,则可以通过编译。总之,要保证局部变量在使用之前一定被初始化了。所以,一个好的做法是在声明他们的时候就初始化他们,如果不知道要出事化成什么值好,就用上面的默认值吧! 6 L9 J3 w v5 |$ [: w" \" o) x& v9 b4 I" u; o' p
其实第三种变量和第二种本质上是一样的,都是方法中的局部变量。只不过作为参数,肯定是被初始化过的,传入的值就是初始值,所以不需要初始化。 ( {! z _6 t& D: G0 P s' u 1 C+ h8 _ _6 @- V问题六:instanceof是什么东东? ' ]" F) c$ x9 N: M. e9 K% f4 }, W. y, Z( \3 V
instanceof是Java的一个二元操作符,和==,> , <是同一类东东。由于它是由字母组成的,所以也是Java的保留关键字。它的作用是测试它左边的对象是否是它右边的类的实例,返回boolean类型的数据。举个例子:! _( M1 O) S! W" _9 f( ^/ `* c* z
/ f3 J( y; l7 z8 ?
String s = "I AM an Object!"; , Q4 y# |4 [! U; J+ M, Zboolean isObject = s instanceof Object; 0 D# L) Z# z% Q4 F' E6 x
我们声明了一个String对象引用,指向一个String对象,然后用instancof来测试它所指向的对象是否是Object类的一个实例,显然,这是真的,所以返回true,也就是isObject的值为True。 3 {4 q5 s8 M! b/ t$ l Z5 m, A; z6 w5 q* |
instanceof有一些用处。比如我们写了一个处理账单的系统,其中有这样三个类:7 ^9 N1 ]. K3 o2 Y0 l# Z6 v2 X
7 A% e; X' c3 C3 @, Wpublic class Bill {//省略细节} ( ^) |: C' A: t Y" R5 T v7 I
0 Z1 K; I: \/ U; spublic class PhoneBill extends Bill {//省略细节} $ D5 c: u" T7 ^( W9 R' n 9 X( }' D. V$ w; ? _3 fpublic class GasBill extends Bill {//省略细节} 9 j* q9 h) ^- ~9 ~# j! F, d
在处理程序里有一个方法,接受一个Bill类型的对象,计算金额。假设两种账单计算方法不同,而传入的Bill对象可能是两种中的任何一种,所以要用instanceof来判断:: b: c3 U6 t% F# D/ a( A' {* r! P
( K5 [& i/ c% |& R( C3 f; [
public double calculate(Bill bill) { 5 `" c8 R% X1 J: Hif (bill instanceof PhoneBill) { 1 Q& l' Y. l6 G! c9 ^+ w* W//计算电话账单 0 o* Q8 S8 w _5 ?
} ! M3 Y. r H% w0 J8 b
if (bill instanceof GasBill) { - D7 ~* ]" @, O2 J; Z//计算燃气账单 2 `4 @7 M/ e1 r4 ~
} 4 l: q: q0 j; w2 u8 i& ?... 6 c* Z. k6 Z/ t# ~. O
} , ~* K& B( ]* ^6 |4 W这样就可以用一个方法处理两种子类。 然而,这种做法通常被认为是没有好好利用面向对象中的多态性。其实上面的功能要求用方法重载完全可以实现,这是面向对象变成应有的做法,避免回到结构化编程模式。只要提供两个名字和返回值都相同,接受参数类型不同的方法就可以了:. R! _& Q7 ~' ]3 v, H& f5 y3 v8 D. f0 |
: U) S, K: H2 _6 ~) l$ D4 V6 b& m% L
public double calculate(PhoneBill bill) { - `* H2 L5 |3 E1 A: p. d2 s//计算电话账单 * V+ T# v# T3 I g} # M! C( x- s# V' b- V% qpublic double calculate(GasBill bill) { 6 C4 E' ^, }* D5 I) K8 ^
//计算燃气账单 1 k/ `, T& Z, L
} # b' V& b8 i% q& y$ A+ ^4 f
7 j$ ~+ t+ }8 q4 d