1. 聲明是什么?
String s = "Hello world!";
許多人都做過(guò)這樣的事情,但是,我們到底聲明了什么?回答通常是:一個(gè)String,內(nèi)容是“Hello world!”。這樣模糊的回答通常是概念不清的根源。如果要準(zhǔn)確的回答,一半的人大概會(huì)回答錯(cuò)誤。
這個(gè)語(yǔ)句聲明的是一個(gè)指向?qū)ο蟮囊茫麨椤皊”,可以指向類(lèi)型為String的任何對(duì)象,目前指
1. 聲明是什么?
String s = "Hello world!";
許多人都做過(guò)這樣的事情,但是,我們到底聲明了什么?回答通常是:一個(gè)String,內(nèi)容是“Hello world!”。這樣模糊的回答通常是概念不清的根源。如果要準(zhǔn)確的回答,一半的人大概會(huì)回答錯(cuò)誤。
這個(gè)語(yǔ)句聲明的是一個(gè)指向?qū)ο蟮囊茫麨椤皊”,可以指向類(lèi)型為String的任何對(duì)象,目前指向"Hello world!"這個(gè)String類(lèi)型的對(duì)象。這就是真正發(fā)生的事情。我們并沒(méi)有聲明一個(gè)String對(duì)象,我們只是聲明了一個(gè)只能指向String對(duì)象的引用變量。所以,如果在剛才那句語(yǔ)句后面,如果再運(yùn)行一句:
String string = s;
我們是聲明了另外一個(gè)只能指向String對(duì)象的引用,名為string,并沒(méi)有第二個(gè)對(duì)象產(chǎn)生,string還是指向原來(lái)那個(gè)對(duì)象,也就是,和s指向同一個(gè)對(duì)象。
2. String類(lèi)的特殊性
1) String s1 = “Hello”; //產(chǎn)生一個(gè)String ”Hello”對(duì)象,并產(chǎn)生該對(duì)象的一個(gè)別名s1來(lái)引用該對(duì)象
String s2 = “Hello”; //又產(chǎn)生一個(gè)別名s2來(lái)引用上面的”Hello”對(duì)象
s1 == s2 = true; //由于是同一個(gè)對(duì)象所以“==”返回為true
s1 = “World”; //產(chǎn)生一個(gè)String ”World”對(duì)象, s1的引用不再指向“Hello”而是指向?qū)ο蟆盬orld”
s1 == s2 = false; //由于不是同一個(gè)對(duì)象所以“==”返回為false
s1 = “Hello”; //同上面的String s2 = “Hello”; 現(xiàn)在s1又指向?qū)ο蟆盚ello”, 因?yàn)镴VM會(huì)自動(dòng)根據(jù)棧中數(shù)據(jù)的實(shí)際情況來(lái)決定是否有必要?jiǎng)?chuàng)建新對(duì)象。
s1 == s2 = true; //由于是同一個(gè)對(duì)象所以“==”又返回為true了
s1 = s1 + “World”; //這時(shí)又產(chǎn)生一個(gè)對(duì)象”HelloWord”,s1不再指向”Hello”而是指向”HelloWord”
s1 == s2 = false; //不是一個(gè)對(duì)象當(dāng)然是false拉
s1 = s1+ "a"+"b"+"c"+…; // String不停的創(chuàng)建對(duì)象,影響性能,這種易變的String用StringBuffer會(huì)得到更好的性能
StringBuffer s3 = new StringBuffer(“Hello”);
s3.append(“a”); //沒(méi)有生成新的對(duì)象,而是將s3引用的對(duì)象內(nèi)容改為”Helloa”
//說(shuō)明: String類(lèi)用來(lái)表示那些創(chuàng)建后就不會(huì)再改變的字符串,它是immutable的。而StringBuffer類(lèi)用來(lái)表示內(nèi)容可變的字符串,并提供了修改底層字符串的方法。
StingBuffer是一個(gè)可變的字符串,它可以被更改。同時(shí)StringBuffer是Thread safe的, 你可以放心的使用.
因?yàn)镾tring被設(shè)計(jì)成一種安全的字符串, 避免了C/C++中的尷尬。因此在內(nèi)部操作的時(shí)候會(huì)頻繁的進(jìn)行對(duì)象的交換, 因此它的效率不如StringBuffer。 如果需要頻繁的進(jìn)行字符串的增刪操作的話最好用StringBuffer。 比如拼SQL文, 寫(xiě)共函。 另: 編繹器對(duì)String的+操作進(jìn)行了一定的優(yōu)化。
x = "a" + 4 + "c"
會(huì)被編繹成
x = new StringBuffer().append("a").append(4).append("c").toString()
但:
x = “a”;
x = x + 4;
x = x + “c”;
則不會(huì)被優(yōu)化。 可以看出如果在一個(gè)表達(dá)式里面進(jìn)行String的多次+操作會(huì)被優(yōu)化, 而多個(gè)表達(dá)式的+操作不會(huì)被優(yōu)化。
摘自:《Java API Using, Tips And Performance Tuning》
2) Integer、Boolean等wrapper類(lèi)以及BigInteger、BigDecimal是immutable的,所以也有與String類(lèi)似的地方,不過(guò)沒(méi)有IntegerBuffer之類(lèi)的東西。不過(guò)Float, Double比較特殊。如
T a1 = 10; //T代指Byte,Integer,Short,Long,Boolean。 注:應(yīng)用了JDK5的AUTOBOXING
T a2 = 10;
if (a1 == a2)
System.out.println(true);
else
System.out.println(false);
這時(shí)總是true,和String有點(diǎn)類(lèi)似
//Float時(shí)
Float i1 = (float)10.0;
Float i2 = (float)10.0;
if (i1==i2)
System.out.println(true);
else
System.out.println(false);
這時(shí)總是false
//Double時(shí)
Double i1 = 10.0;
Double i2 = 10.0;
if (i1==i2)
System.out.println(true);
else
System.out.println(false);
這時(shí)總是false
總之如果比較兩個(gè)Wrapper類(lèi)的值用equals,以免不必要的麻煩
3) 再看
String s1 = new String(“Hello”);
String s2 = new String(“Hello”);
s1 == s2 = false;
//因?yàn)閚ew的時(shí)候JVM不管heap中有沒(méi)有”Hello”對(duì)象都會(huì)產(chǎn)生一個(gè)新的”Hello”對(duì)象
String s3 = “Hello”; //重新創(chuàng)建對(duì)象”Hello”, 并令s3指向?qū)ο蟆盚ello”
s3 == s1 = false; //不同對(duì)象當(dāng)然false
String s4 = “Hello”;
s3 == s4 = true; //故伎重演,jvm清楚的知道哪些用了new,哪些沒(méi)用new
3. 方法的參數(shù)傳遞中都是以reference傳遞,而primitive傳遞的是副本,但如果傳遞的是Integer、Boolean等wrapper類(lèi)和String類(lèi)的Object則是以immutable方式傳遞。示例:
import java.awt.Point;
class HelloWorld
{
public static void modifyPoint(Point pt, String j, int k, Integer m, Boolean b)
{
pt.setLocation(5,5);
j = "15";
k = 25;
m = 35;
b = true;
System.out.println("During modifyPoint " + "pt = " + pt +
" and j = " + j+ " and k = "+ k+
" and m = "+ m+ " and b = "+ b);
}
public static void main(String args[])
{
Point p = new Point(0,0);
String i = "10";
int k = 20;
Integer m = 30;
Boolean b = false;
System.out.println("Before modifyPoint " + "p = " + p +
" and i = " + i+ " and k = "+ k+
" and m = "+ m+ " and b = "+ b);
modifyPoint(p, i, k, m, b);
System.out.println("After modifyPoint " + "p = " + p +
" and i = " + i+ " and k = "+ k+
" and m = "+ m+ " and b = "+ b);
}
}
輸出結(jié)果:
Before modifyPoint p = java.awt.Point[x=0,y=0] and i = 10 and k = 20 and m = 30 and b = false
During modifyPoint pt = java.awt.Point[x=5,y=5] and j = 15 and k = 25 and m = 35 and b = true
After modifyPoint p = java.awt.Point[x=5,y=5] and i = 10 and k = 20 and m = 30 and b = false
4. final作用于基本類(lèi)型變量則該變量為恒常量;final作用于對(duì)象類(lèi)型變量則該對(duì)象reference為恒量;final作用于方法則該方法不能被覆蓋;final作用于class則該class不能被繼承。
final使得被修飾的變量"不變",但是由于對(duì)象型變量的本質(zhì)是“引用”,使得“不變”也有了兩種含義:引用本身的不變,和引用指向的對(duì)象不變。
引用本身的不變:
final StringBuffer a=new StringBuffer("immutable");
final StringBuffer b=new StringBuffer("not immutable");
a=b;//編譯期錯(cuò)誤
引用指向的對(duì)象不變:
final StringBuffer a=new StringBuffer("immutable");
a.append(" broken!"); //編譯通過(guò)
可見(jiàn),final只對(duì)引用的“值”(也即它所指向的那個(gè)對(duì)象的內(nèi)存地址)有效,它迫使引用只能指向初始指向的那個(gè)對(duì)象,改變它的指向會(huì)導(dǎo)致編譯期錯(cuò)誤。至于它所指向的對(duì)象的變化,final是不負(fù)責(zé)的。這很類(lèi)似==操作符:==操作符只負(fù)責(zé)引用的“值”相等,至于這個(gè)地址所指向的對(duì)象內(nèi)容是否相等,==操作符是不管的。
理解final問(wèn)題有很重要的含義。許多程序漏洞都基于此----final只能保證引用永遠(yuǎn)指向固定對(duì)象,不能保證那個(gè)對(duì)象的狀態(tài)不變。在多線程的操作中,一個(gè)對(duì)象會(huì)被多個(gè)線程共享或修改,一個(gè)線程對(duì)對(duì)象無(wú)意識(shí)的修改可能會(huì)導(dǎo)致另一個(gè)使用此對(duì)象的線程崩潰。一個(gè)錯(cuò)誤的解決方法就是在此對(duì)象新建的時(shí)候把它聲明為final,意圖使得它“永遠(yuǎn)不變”。其實(shí)那是徒勞的。
5. 怎樣初始化
本問(wèn)題討論變量的初始化,所以先來(lái)看一下Java中有哪些種類(lèi)的變量。
1). 類(lèi)的屬性,或者叫值域
2). 方法里的局部變量
3). 方法的參數(shù)
對(duì)于第一種變量,Java虛擬機(jī)會(huì)自動(dòng)進(jìn)行初始化。如果給出了初始值,則初始化為該初始值。如果沒(méi)有給出,則把它初始化為該類(lèi)型變量的默認(rèn)初始值。
primitive類(lèi)型默認(rèn)值
boolean: false
char: '\u0000' 對(duì)于未初始化的char c, c == ‘\u0000’ = true
byte: 0
short: 0
int: 0
long: 0
float: 0.0
double: 0.0
object reference: null
array: null
注意數(shù)組本身也是對(duì)象,所以沒(méi)有初始化的數(shù)組引用在自動(dòng)初始化后其值也是null。
對(duì)于兩種不同的類(lèi)屬性,static屬性與instance屬性,初始化的時(shí)機(jī)是不同的。instance屬性在創(chuàng)建實(shí)例的時(shí)候初始化,static屬性在類(lèi)加載,也就是第一次用到這個(gè)類(lèi)的時(shí)候初始化,對(duì)于后來(lái)的實(shí)例的創(chuàng)建,不再次進(jìn)行初始化。
對(duì)于第二種變量,必須明確地進(jìn)行初始化。如果再?zèng)]有初始化之前就試圖使用它,編譯器會(huì)抗議。如果初始化的語(yǔ)句在try塊中或if塊中,也必須要讓它在第一次使用前一定能夠得到賦值。也就是說(shuō),把初始化語(yǔ)句放在只有if塊的條件判斷語(yǔ)句中編譯器也會(huì)抗議,因?yàn)閳?zhí)行的時(shí)候可能不符合if后面的判斷條件,如此一來(lái)初始化語(yǔ)句就不會(huì)被執(zhí)行了,這就違反了局部變量使用前必須初始化的規(guī)定。但如果在else塊中也有初始化語(yǔ)句,就可以通過(guò)編譯,因?yàn)闊o(wú)論如何,總有至少一條初始化語(yǔ)句會(huì)被執(zhí)行,不會(huì)發(fā)生使用前未被初始化的事情。對(duì)于try-catch也是一樣,如果只有在try塊里才有初始化語(yǔ)句,編譯部通過(guò)。如果在 catch或finally里也有,則可以通過(guò)編譯。總之,要保證局部變量在使用之前一定被初始化了。所以,一個(gè)好的做法是在聲明他們的時(shí)候就初始化他們,如果不知道要出事化成什么值好,就用上面的默認(rèn)值吧!
其實(shí)第三種變量和第二種本質(zhì)上是一樣的,都是方法中的局部變量。只不過(guò)作為參數(shù),肯定是被初始化過(guò)的,傳入的值就是初始值,所以不需要初始化。
6. 盡量使用多態(tài)(polymorphism)特性而不是instanceof
7. 一旦不需要對(duì)象,盡量顯式的使之為null
8. 對(duì)象之間的”=”賦值操作乃是賦值的reference, 即左邊的對(duì)象也指向右邊的對(duì)象,只是該reference多了一個(gè)別名而已。
9. “==”和equals()的區(qū)別
==操作符專(zhuān)門(mén)用來(lái)比較變量的值是否相等。比較好理解的一點(diǎn)是:
int a=10;
int b=10;
則a==b將是true。
但不好理解的地方是:
String a=new String("foo");
String b=new String("foo");
則a==b將返回false。
根據(jù)前一帖說(shuō)過(guò),對(duì)象變量其實(shí)是一個(gè)引用,它們的值是指向?qū)ο笏诘膬?nèi)存地址,而不是對(duì)象本身。a和b都使用了new操作符,意味著將在內(nèi)存中產(chǎn)生兩個(gè)內(nèi)容為"foo"的字符串,既然是“兩個(gè)”,它們自然位于不同的內(nèi)存地址。a和b的值其實(shí)是兩個(gè)不同的內(nèi)存地址的值,所以使用"=="操作符,結(jié)果會(huì)是 false。誠(chéng)然,a和b所指的對(duì)象,它們的內(nèi)容都是"foo",應(yīng)該是“相等”,但是==操作符并不涉及到對(duì)象內(nèi)容的比較。
對(duì)象內(nèi)容的比較,正是equals方法做的事。
看一下Object對(duì)象的equals方法是如何實(shí)現(xiàn)的:
boolean equals(Object o){
return this==o;
}
Object 對(duì)象默認(rèn)使用了==操作符。所以如果你自創(chuàng)的類(lèi)沒(méi)有覆蓋equals方法,那你的類(lèi)使用equals和使用==會(huì)得到同樣的結(jié)果。同樣也可以看出, Object的equals方法沒(méi)有達(dá)到equals方法應(yīng)該達(dá)到的目標(biāo):比較兩個(gè)對(duì)象內(nèi)容是否相等。因?yàn)榇鸢笐?yīng)該由類(lèi)的創(chuàng)建者決定,所以O(shè)bject把這個(gè)任務(wù)留給了類(lèi)的創(chuàng)建者。
看一下一個(gè)極端的類(lèi):
Class Monster{
private String content;
...
boolean equals(Object another){ return true;}
}
我覆蓋了equals方法。這個(gè)實(shí)現(xiàn)會(huì)導(dǎo)致無(wú)論Monster實(shí)例內(nèi)容如何,它們之間的比較永遠(yuǎn)返回true。
所以當(dāng)你是用equals方法判斷對(duì)象的內(nèi)容是否相等,請(qǐng)不要想當(dāng)然。因?yàn)榭赡苣阏J(rèn)為相等,而這個(gè)類(lèi)的作者不這樣認(rèn)為,而類(lèi)的equals方法的實(shí)現(xiàn)是由他掌握的。如果你需要使用equals方法,或者使用任何基于散列碼的集合(HashSet,HashMap,HashTable),請(qǐng)察看一下java doc以確認(rèn)這個(gè)類(lèi)的equals邏輯是如何實(shí)現(xiàn)的。
10. 不要依賴(lài)equals()的缺省實(shí)現(xiàn)
11. 一個(gè)equals()的實(shí)現(xiàn)模版
class Golfball
{
private String brand;
private String make;
private int compression;
public boolean equals(Object obj)
{
if (this == obj)
return true;
if (obj != null && getClass() == obj.getClass())
{
Golfball gb = (Golfball)obj; //Classes are equal, downcast.
if (brand.equals(gb.brand()) && //Compare attributes.
make.equals(gb.make()) &&
compression == gb.compression())
{
return true;
}
}
return false;
}
}
注意getClass() == obj.getClass()的限制,如果判斷必須相等則無(wú)法比較基類(lèi)和子類(lèi)是否相等,完全不同的類(lèi)不用考慮,完全沒(méi)有可比性,除了特殊需要或很糟糕的程序。
12. 實(shí)現(xiàn)equals()應(yīng)優(yōu)先考慮使用getClass()
13. 如果某個(gè)基類(lèi)我們自己實(shí)現(xiàn)了equals(),在它的子類(lèi)中要覆蓋此方法,最好調(diào)用super.equals()喚起base class的相關(guān)行為,然后再實(shí)現(xiàn)子類(lèi)域的比較。
Example:
public boolean equals(Object obj)
{
if (this == obj) //1
return true;
if (obj != null && getClass() == obj.getClass() && //2
super.equals(obj)) //3
{
MyGolfball gb = (MyGolfball)obj; //Classes equal, downcast.
if (ballConstruction == gb.construction()) //Compare attrs.
return true;
}
return false;
}
14. 如果要在base class與derived class之間應(yīng)運(yùn)equals(),可以考慮instanceof來(lái)代替getClass()。對(duì)此論題的詳細(xì)討論參見(jiàn):Practical Java, Practice 14
15. instanceof什么東西?
instanceof是Java的一個(gè)二元操作符,和==,>,<是同一類(lèi)東東。由于它是由字母組成的,所以也是Java的保留關(guān)鍵字。它的作用是測(cè)試它左邊的對(duì)象是否是它右邊的類(lèi)的實(shí)例,返回boolean類(lèi)型的數(shù)據(jù)。舉個(gè)例子:
String s = "I AM an Object!";
boolean isObject = s instanceof Object;
我們聲明了一個(gè)String對(duì)象引用,指向一個(gè)String對(duì)象,然后用instancof來(lái)測(cè)試它所指向的對(duì)象是否是Object類(lèi)的一個(gè)實(shí)例,顯然,這是真的,所以返回true,也就是isObject的值為T(mén)rue。
instanceof有一些用處。比如我們寫(xiě)了一個(gè)處理賬單的系統(tǒng),其中有這樣三個(gè)類(lèi):
public class Bill {//省略細(xì)節(jié)}
public class PhoneBill extends Bill {//省略細(xì)節(jié)}
public class GasBill extends Bill {//省略細(xì)節(jié)}
在處理程序里有一個(gè)方法,接受一個(gè)Bill類(lèi)型的對(duì)象,計(jì)算金額。假設(shè)兩種賬單計(jì)算方法不同,而傳入的Bill對(duì)象可能是兩種中的任何一種,所以要用instanceof來(lái)判斷:
public double calculate(Bill bill) {
if (bill instanceof PhoneBill) {
//計(jì)算電話賬單
}
if (bill instanceof GasBill) {
//計(jì)算燃?xì)赓~單
}
...
}
這樣就可以用一個(gè)方法處理兩種子類(lèi)。
然而,這種做法通常被認(rèn)為是沒(méi)有好好利用面向?qū)ο笾械亩鄳B(tài)性。其實(shí)上面的功能要求用方法重載完全可以實(shí)現(xiàn),這是面向?qū)ο笞兂蓱?yīng)有的做法,避免回到結(jié)構(gòu)化編程模式。只要提供兩個(gè)名字和返回值都相同,接受參數(shù)類(lèi)型不同的方法就可以了:
public double calculate(PhoneBill bill) {
//計(jì)算電話賬單
}
public double calculate(GasBill bill) {
//計(jì)算燃?xì)赓~單
}
所以,使用instanceof在絕大多數(shù)情況下并不是推薦的做法,應(yīng)當(dāng)好好利用多態(tài)。
16. 認(rèn)真對(duì)待異常。
1).在方法體用throws子句拋出異常時(shí)盡量包括所有出現(xiàn)的異常,而不是僅僅拋出base exception.
2).在super class中定義的方法拋出某個(gè)異常,如果在deriver class中要override該方法,那么overriding method必須:
a. 不拋出任何異常
b. 拋出和super class 中同樣的異常
c. 拋出和super class 中異常的deriver class
如果super class中定義的方法沒(méi)有拋出異常,但deriver class中的override的方法會(huì)產(chǎn)生異常,必須自己內(nèi)部解決
3).好好利用finally功能。一般只要有finally,它總是會(huì)被執(zhí)行,除非在try中用System.exit(0)或者在try塊執(zhí)行期間強(qiáng)行拔掉電源。finally被執(zhí)行有三種情況:
a. 拋出異常
b. try正常結(jié)束
c. 在try中執(zhí)行了return, break, continue而引起離開(kāi)try的操作
尤其注意c.如果方法中在try塊return 1,而在finally塊return 2,則最終永遠(yuǎn)是2,因此盡量避免在try中使用return, break, continue,要么確保在finally中不會(huì)改變返回值
4).不要在循環(huán)體中使用try,因?yàn)樵跓o(wú)JIT的JVM中將大大降低性能,而且這也是良好的編程習(xí)慣
5).不要將異常用于控制流程,而是僅僅用于會(huì)發(fā)生錯(cuò)誤的地方
6).不要每逢出錯(cuò)就使用異常,盡量使用傳統(tǒng)的方法判斷變量的有效性
17. 關(guān)于不可變類(lèi)(Immutable class),如String、Byte、Integer、Short、Long、Float、Double、BigInteger、BigDecimal等,它們之所以能將同一值自動(dòng)地指向同一引用,實(shí)際上是它們實(shí)現(xiàn)了靜態(tài)工廠方法。