
2007年12月20日
方法調(diào)用的綁定:
將方法的調(diào)用連到方法本身被稱為“綁定”。當綁定發(fā)生在程序運行之前時,被稱作“前綁定”。
后綁定也稱為“動態(tài)綁定”或“運行時綁定”,指程序運行的時候,根據(jù)對象的類型來決定該綁定哪個方法。如果語言實現(xiàn)了后綁定,那它就必須要有能在運行時判斷對象類型,并且調(diào)用其合適方法的機制。也就是說編譯器還是不知道對象的類型,但是方法的調(diào)用機制會找出,并且調(diào)用正確的方法。
除了static和final方法(private方法隱含有final的意思),java的所有的方法都采用后綁定。也就是說,通常情況下你不必考慮是不是應該采用后綁定--它是自動的。 為什么要聲明final方法,上一章指出,這樣可以禁止別人覆寫這個方法,不過更重要的可能還是要“關(guān)閉”它的動態(tài)綁定,或者理確切的說,告訴編譯器這里不需要使用后綁定。
shape為circle的基類,下面這句就是在“上傳”:
Shape s = new Circle();
這里先創(chuàng)建了一個Circle對象,接著馬上把它的引用賦給了Shape,看上去這像是一個錯誤(一種類型怎么能賦給另一種);但是由于Circle是由Shape派生出來的,Circle就是一種Shape,因此這種做法是非常正確的。假設(shè)你調(diào)用了一個基類的方法:s.draw();這里派生類里已經(jīng)覆寫了此方法,那么可能你會認為,這次應該總調(diào)用Shape的draw()了吧,因為畢竟這是Shape的引用,但是由于實現(xiàn)了后綁定(多態(tài)性),實際上它會調(diào)用Circle.draw().
posted @
2008-01-05 19:18 仰望者 閱讀(220) |
評論 (0) |
編輯 收藏
1、 float f=1.3;
是不對的,編譯時會出錯,java認為1.3是double型的,所以定義時應寫成:float f=1.3f,或float f= (float)1.3;
2、 byte b = 3; b=b*3;
是不對的,原因是在*運算過程中,java會做類型的提升,將b提升為int型,所以應改為:b=(byte)(b*3);
3、 while(1),if(1)
是不對的,原因是java中布爾型只有true 和false兩個值,這里與C語言不同,只能用while(true)..
4、 數(shù)組聲明:int num[3];
這是不對的,java中聲明數(shù)組時不應對空間限定,正確的語法應是:
int[] num = new int[3];
或
int[] num;
num = new int[3];
5、數(shù)組初始化:int[] num;
num {1,3,4,4};
是不對的,應在定義的時候初始化。如:int[] num={1,3,4,4};
6、int[] num3 =new int[]{1,2,3};
int[] num5 =new int[3]{1,2,3};
int[] num3 =new int[]{1,2,3};是對的。
int[] num5 =new int[3]{1,2,3};是錯的。已經(jīng)初始化的數(shù)組,不應再列明:[3]
posted @
2007-12-23 23:01 仰望者 閱讀(162) |
評論 (0) |
編輯 收藏
合成與繼承
繼承:
super關(guān)鍵字的使用:super使用在派生類中,如果派生類中重寫了基類的方法,但在這個被重寫的方法中仍然要調(diào)用基類的同名的方法,這就要用到super關(guān)鍵字,特別是在創(chuàng)建對象時,在帶參數(shù)構(gòu)造函數(shù)中調(diào)用基類構(gòu)造函數(shù)的情況。
如:
class Cleanser {
private String s = new String("Cleanser");
public void append(String a) { s += a; }
public void dilute() { append(" dilute()"); }
public void apply() { append(" apply()"); }
public void scrub() { append(" scrub()"); }
public void print() { System.out.println(s); }
public static void main(String[] args) {
Cleanser x = new Cleanser();
x.dilute(); x.apply(); x.scrub();
x.print();
}
}
public class Detergent extends Cleanser {
// Change a method:
public void scrub() {
append(" Detergent.scrub()");
super.scrub(); // Call base-class version
}
// Add methods to the interface:
public void foam() { append(" foam()"); }
// Test the new class:
public static void main(String[] args) {
Detergent x = new Detergent();
x.dilute();
x.apply();
x.scrub();
x.foam();
x.print();
System.out.println("Testing base class:");
Cleanser.main(args);
}
} ///:~
可以看到基類Cleanser 中定義了scrub方法,但派生類Detergent 中對scrub方法進行了修改,并用在派生類Detergent 的scrub方法中,要調(diào)用基本的scrub方法,那么用super.scrub();
基類的初始化:
當你創(chuàng)建一個派生類的對象的時候,這個對象里面還有一個基類的子對象,這個子對象同基類自己創(chuàng)建的對象沒什么兩樣,只是從外面看來,這個子對象被包裹在派生類的對象里面。
基類子對象的正確初始化是非常重要的,而且只有一個辦法能保證這一點:調(diào)用基類的構(gòu)造函數(shù)來進行初始化,因為只有它才能掌握怎么樣才能正確地進行初始化的信息和權(quán)限。java會讓派生類的構(gòu)造函數(shù)自動地調(diào)用基類的構(gòu)造函數(shù)。
示例:
class Art {
Art() {
System.out.println("Art constructor");
}
}
class Drawing extends Art {
Drawing() {
System.out.println("Drawing constructor");
}
}
public class Cartoon extends Drawing {
Cartoon() {
System.out.println("Cartoon constructor");
}
public static void main(String[] args) {
Cartoon x = new Cartoon();
}
} ///:~
輸出結(jié)果為:
Art constructor
Drawing constructor
Cartoon constructor
一看結(jié)果便一目了然了。
上面的示例是不帶任何參數(shù)的情況,
如果構(gòu)造函數(shù)中帶有參數(shù)的話,那這里又要用到super的特性了。與上面super的使用涵意一樣,super在這里用作:派生的帶參數(shù)構(gòu)造函數(shù)中調(diào)用基類的帶參構(gòu)造函數(shù),只是這里不象上面那樣super.scrub();這里只使用super(i);即可。
class Game {
Game(int i) {
System.out.println("Game constructor");
}
}
class BoardGame extends Game {
BoardGame(int i) {
super(i);
System.out.println("BoardGame constructor");
}
}
public class Chess extends BoardGame {
Chess() {
super(11);
System.out.println("Chess constructor");
}
public static void main(String[] args) {
Chess x = new Chess();
}
} ///:~
輸出結(jié)果是:
Game constructor
BoardGame constructor
Chess constructor
合成和繼承一起使用,實現(xiàn)類的復用:
class Plate {
Plate(int i) {
System.out.println("Plate constructor");
}
}
class DinnerPlate extends Plate {
DinnerPlate(int i) {
super(i);
System.out.println(
"DinnerPlate constructor");
}
}
class Utensil {
Utensil(int i) {
System.out.println("Utensil constructor");
}
}
class Spoon extends Utensil {
Spoon(int i) {
super(i);
System.out.println("Spoon constructor");
}
}
class Fork extends Utensil {
Fork(int i) {
super(i);
System.out.println("Fork constructor");
}
}
class Knife extends Utensil {
Knife(int i) {
super(i);
System.out.println("Knife constructor");
}
}
// A cultural way of doing something:
class Custom {
Custom(int i) {
System.out.println("Custom constructor");
}
}
public class PlaceSetting extends Custom {
Spoon sp;
Fork frk;
Knife kn;
DinnerPlate pl;
PlaceSetting(int i) {//把初始化工作都放在構(gòu)造函數(shù)中
super(i + 1);
sp = new Spoon(i + 2);
frk = new Fork(i + 3);
kn = new Knife(i + 4);
pl = new DinnerPlate(i + 5);
System.out.println(
"PlaceSetting constructor");
}
public static void main(String[] args) {
PlaceSetting x = new PlaceSetting(9);
}
} ///:~
盡管編譯器會強迫我們對基礎(chǔ)類進行初始化,并要求我們在構(gòu)建器最開頭做這一工作,但它并不會監(jiān)視我們是否正確初始化了成員對象。所以對此必須特別加以留意。
FINAL關(guān)鍵字:
FINAL關(guān)鍵字指“那樣東西是不允許改動”,你可能會出于兩點考慮不想讓別人作改動:設(shè)計和效率。由于這兩個原因差別很大,所以很可能會誤用final關(guān)鍵字。
final的三種用途:數(shù)據(jù)(Data)、方法(method)、類(class)。
很多語言通知編譯器:“這段常量(constant)數(shù)據(jù)”的手段。常量能用下列兩種情況出現(xiàn):
1、可以是“編譯時的常量”,這樣以后就不能改了;
2、也可以是運行時初始化的值,這個值以后就不想再改了。
如果是編譯時的常量,編譯器會把常量放到算式里面;這樣編譯的時候就能進行計算,因此也就降低了運行時的開銷。在Java 中這種常量必須是primitive 型的,而且要用final 關(guān)鍵詞表示。這種常量的賦值必須在定義的時候進行。
一個既是static 又是final 的數(shù)據(jù)成員會只占據(jù)一段內(nèi)存,并且不可修改。
當final 不是指primitive,而是用于對象的reference 的時候,意思就有點不一樣了。對primitive 來說,final 會將這個值定義成常量,但是對于對象的reference 而言,final 的意思則是這個reference 是常量。初始化的時候,一旦將reference 連到了某個對象,那么它就再也不能指別的對象了。但是這個對象本身是可以修改的;Java 沒有提供將某個對象作成常量的方法。
(但是你可以自己寫一個類,這樣就能把類當做常量了)
這種局限性也體現(xiàn)在數(shù)組上,因為它也是一個對象。
注意,通常約定,被初始化為常量值的final static 的primitive 的名字全都用大寫,詞與詞之間用下
劃線分開,如VAL_ONE
Final 方法
使用final 方法的目的有二:
第一,為方法上“鎖”,禁止派生類進行修改。這是出于設(shè)計考慮。當你希望某個方法的功能,能在繼承過程中被保留下來,并且不被覆寫,就可以使用這個方法。
第二個原因就是效率。如果方法是final 的,那么編譯器就會把調(diào)用轉(zhuǎn)換成“內(nèi)聯(lián)的(inline)”。它會用方法本身的拷貝來代替方法的調(diào)用
final 和private
private 方法都隱含有final 的意思。由于你不能訪問private 的方法,因此你也不能覆寫它。你可以給private 方法加一個final 修飾符,但是這樣做什么意義也沒有。
這個問題有可能會造成混亂,因為即使你覆寫了一個private 方法(它隱含有final 的意思),看上去它還是可以運行的,而且編譯器也不會報錯:
class WithFinals {
// Identical to "private" alone:
private final void f() {
System.out.println("WithFinals.f()");
}
/ / Also automatically "final":
private void g() {
System.out.println("WithFinals.g()");
}
}
class OverridingPrivate extends WithFinals {
private final void f() {
System.out.println("OverridingPrivate.f()");
}
private void g() {
System.out.println("OverridingPrivate.g()");
}
}
只有是基類接口里的東西才能被“覆寫”,如果方法是private 的,那它就不屬于基類的接口。它只能算是被類隱藏起來的,正好有著相同的名字的代碼。如果你在派生類里創(chuàng)建了同名的public 或protected,或package 權(quán)限的方法,那么它們同基類中可能同名的方法,沒有任何聯(lián)系。你并沒有覆寫那個方法,你只是創(chuàng)建了一個新的方法。由于private 方法是無法訪問的,實際上是看不見的,因此這么作除了會影響類的代碼結(jié)構(gòu),其它什么意義都沒有。
Final 類
把整個類都定義成final 的(把final 關(guān)鍵詞放到類的定義部分的前面)就等于在宣布,你不會去繼承這個類,你也不允許別人去繼承這個類。換言之,出于類的設(shè)計考慮,它再也不需要作修改了,或者從安全角度出發(fā),你不希望它再生出子類。
final class Dinosaur{}
注意,final 類的數(shù)據(jù)可以是final 的,也可以不是final 的,這要由你來決定。無論類是不是final 的,這一條都適用于“將final 用于數(shù)據(jù)的”場合。但是,由于final 類禁止了繼承,覆寫方法已經(jīng)不可能了,因
此所有的方法都隱含地變成final 了。你可以為final 類的方法加一個final 修飾符,但是這一樣沒什么意義。
posted @
2007-12-20 17:33 仰望者 閱讀(238) |
評論 (0) |
編輯 收藏
訪問控制符:public 、private、protected、friendly
public包內(nèi)包外均可訪問。
private只有本類可訪問。
protected針對繼承而使用的:1、包內(nèi)繼承,因為在包內(nèi),聲明為protected不影響它本來的friendly權(quán)限。
2、包外繼承,必須聲明為protected。
派生類可以訪問基類的protected成員。
注意不可將類設(shè)成private(那樣會使除類之外的其他東西都不能訪問它),也不能設(shè)成protected。因此,我們現(xiàn)在對于類的訪問只有兩個選擇:“友好的”或者public。若不愿其他任何人訪問那個類,可將所有構(gòu)建器設(shè)為private,這樣除你之外,沒有可以用類創(chuàng)建的了。而你可以使用static成員創(chuàng)建對象。
package com.access.external;
class Soup{
private Soup(){//構(gòu)造函數(shù)聲明為private,其它類不能用此構(gòu)造函數(shù)創(chuàng)建對象;
System.out.println("sffewe");
}
public static Soup makSoup(){//其它類可通過makSoup來創(chuàng)建對象;
return new Soup();
}
private static Soup ps1 = new Soup();//自己創(chuàng)建對象;
public static Soup access(){//返回對象的引用。
return ps1;
}
public void f(){}
}
class Sandwich{
void f(){
new Lunch();
}
}
public class Lunch {
void test(){
//Soup priv1 = new Soup();
Soup priv2 = Soup.makSoup();
Sandwich f1 = new Sandwich();
Soup.access().f();//不創(chuàng)建對象,但通過Soup中返回的對象引用調(diào)用其方法。
}
}
該方法返回一個句柄,它指向類Soup的一個對象。
Soup類向我們展示出如何通過將所有構(gòu)建器都設(shè)為private,從而防止直接創(chuàng)建一個類。請記住,假若不明確地至少創(chuàng)建一個構(gòu)建器,就會自動創(chuàng)建默認構(gòu)建器(沒有自變量)。若自己編寫默認構(gòu)建器,它就不會自動創(chuàng)建。把它變成private后,就沒人能為那個類創(chuàng)建一個對象。但別人怎樣使用這個類呢?上面的例子為我們揭示出了兩個選擇。第一個選擇,我們可創(chuàng)建一個static方法,再通過它創(chuàng)建一個新的Soup,然后返回指向它的一個句柄。如果想在返回之前對Soup進行一些額外的操作,或者想了解準備創(chuàng)建多少個Soup對象(可能是為了限制它們的個數(shù)),這種方案無疑是特別有用的。
第二個選擇是采用“設(shè)計方案”(Design Pattern)技術(shù),本書后面會對此進行詳細介紹。通常方案叫作“獨子”,因為它僅允許創(chuàng)建一個對象。類Soup的對象被創(chuàng)建成Soup的一個static private成員,所以有一個而且只能有一個。除非通過public方法access(),否則根本無法訪問它。
posted @
2007-12-20 11:09 仰望者 閱讀(248) |
評論 (0) |
編輯 收藏