Java程式設計思想(2)
第6章 訪問許可權控制
1 訪問許可權控制的等級,從最大許可權到最小許可權依次為:public,protected,包訪問許可權(沒有關鍵字)和private
2 如果想要使用類,可以在import語句中匯入該類或使用該類的全名來指定
// 使用ArrayList的一種方法是使用其全名java.util.ArrayList來指定 public static void main(String[] args){ java.util.ArrayList list = new java.util.ArrayList(); } // 可以使用import匯入這個類,而不需要寫全名 import java.util.ArrayList public static void main(String[] args){ ArrayList list = new ArrayList(); }
3 如果使用package語句,它必須是檔案中除註釋外的第一句程式程式碼,在檔案起始處寫 package xxxx ; 表示你宣告該編譯單元中public類名稱是xxxx類庫的一部分
4 Java直譯器的執行過程:首先,找出環境變數CLASSPATH包含一個或多個目錄,用作檢視.class檔案的根目錄。從根目錄開始,直譯器獲取包的名稱並將每個句點替換成反斜杆,以從CLASSPATH根中產生一個路徑名稱(即,package foo.bar.baz變成foo/bar/baz)。得到的路徑會與CLASSPATH中的各個不同的項相連線,直譯器就在這些目錄中查詢與你所要建立的類名稱相關的.class檔案。
5 從java2開始,包名都是小寫,如package com.baidu.tt
6 除非要使用包裡的所有類,可以使用星號匯入全部。最好還是單個匯入避免衝突。
7 如果不提供任何訪問許可權修飾詞,即沒有public,protected和private , 則意味著它是“包訪問許可權”。
8 關鍵字protected處理的是繼承的概念。子類可以訪問父類的protected成員變數或成員函式
9 程式碼風格:將public成員前面,後面跟protected,包訪問許可權和private成員
10 類的訪問許可權限制:
- 每個編譯單元(檔案)都只能有一個public類
- public的類名必須完全與該編譯單元的檔名一樣
- 編譯單元內完全不帶public類也是可以的,這種不太常見
11 類既不可以是private,也不可以是protected,只能是包訪問許可權或public
第7章 複用類
1 每個非基本型別的物件都有一個toString()方法。而且當編譯器需要一個String而你只有一個物件時,該方法就會被呼叫。
2 當建立一個類時,就是在繼承,如果未明確指出要從其他類中繼承,就是在隱式地從Java的標準根類Object進行繼承。Object是所有類的父類。
3 繼承是用extends關鍵字,語法為 class A extends B { }
4 可以為每個類都建立一個main()方法,可使每個類的單元測試變得簡單易行。
class Soap{
private String s = "Cleanser";
public void append(String a){ s += a;}
public void dilute(){ append(" dilute()");}
public void apply(){ append(" apply()");}
public void scrub(){ append(" scrub()"); }
public String toString(){
return s;
}
public static void main(String[] args){ // main()函式
Cleanser x = new Cleanser();
x.dilute();x.apply();x.scrub();
System.out.println(x);
}
}
public class Chocolate extends Soap {
public void scrub(){
append(" Chocolate.scrub()");
super.scrub();
}
public void foam(){ append(" foam()");}
public static void main(String[] args){ // main()函式
Chocolate c = new Chocolate();
c.dilute();
c.apply();
c.scrub();
c.foam();
System.out.println(c);
System.out.println("Testing base class: ");
Cleanser.main(args);
}
}
5 Java用super關鍵字表示父類,super.func()表示呼叫父類的函式func()
6 Java會自動在子類的構造器中先呼叫父類構造器,找不到會報錯。預設的構造器都不會帶引數, 子類會自動呼叫父類的預設構造器,如果父類沒有預設構造器,則需要用super顯式地呼叫父類的構造器。
class Art2{
Art2(int i){
System.out.println("Art constructor "+i);
}
}
class Drawing2 extends Art2{
Drawing2(int i){
super(i); // 呼叫構造器Art2(int i)
System.out.println("Drawing constructor "+i);
}
}
public class Chocolate extends Drawing2 {
Chocolate(){
super(11); // 呼叫構造器Drawing2(int i)
System.out.println("Chocolate constructor");
}
public static void main(String[] args){
Chocolate c = new Chocolate();
}
}
7 Java中沒有解構函式
8 子類對父類的函式的過載,Java SE5 新增了@Override註解,添加了這個註解的函式只能被覆寫,如果不小心過載這個函式,編譯器會報錯
class Homer2{
char doh(char c){
System.out.println("doh(char)");
return 'd';
}
float doh(float f){
System.out.println("doh(float)");
return 1.0f;
}
}
class Milhouse{}
class Bart2 extends Homer2{
void doh(Milhouse m){ // 對父類的函式過載
System.out.println("doh(Milhouse)");
}
}
public class Chocolate{
public static void main(String[] args){
Bart2 b = new Bart2();
b.doh(1);
b.doh('x');
b.doh(1.0f);
b.doh(new Milhouse());
}
}
9 “is - a”的關係是用繼承來表達,“has - a”的關係是用組合來表達。
10 向上轉型,tune( Intrument i )函式將Instrument的子類Chocolate引用轉換為Instrument引用,稱之為向上引用。
class Instrument{
public void play(){}
static void tune(Instrument i){
i.play();
}
}
public class Chocolate extends Instrument{
public static void main(String[] args){
Chocolate c = new Chocolate();
Instrument.tune(c);
}
}
11 在Java中,這類常量必須是基本資料型別,並且以關鍵字final表示,定義時必須進行賦值。
12 一個既是static又是final的變數只佔據一段不能改變的儲存空間,既是static又是final的變數一般用大寫表示。
public static int getInt(){
return 12;
}
private Random r = new Random();
private final static int ONE = 1;
private final static int TWO = getInt(); // 可以動態賦值,但必須是static
private final int T = 12;
private final int K = r.nextInt(12);
13 Java允許生成“空白final”,即被宣告為final但又未給定初值的成員變數,可以在使用前賦值。
class Poppet{
private int i;
Poppet(int i){
this.i = i;
}
}
public class Chocolate{
private final int i = 0; // Initialized final
// private final int k; // error
private final int j; // Blank final
private final Poppet p; // Blank final
public Chocolate(){
j = 1;
p = new Poppet(1);
}
public Chocolate(int x){
j = x;
p = new Poppet(x);
}
public static void main(String[] args){
new Chocolate();
new Chocolate(4);
}
}
注:類的final成員變數不會被賦預設值,必須要在域的定義處或所有構造器中用表示式對final成員變數進行賦值。Java確保final在使用前必須被初始化。
14 Java允許在引數列表中以宣告的方式將引數宣告為final,表示在函式中無法修改引數引用所指向的物件
class Gizmo{}
public class Chocolate{
void with(final Gizmo g){
g = new Gizmo(); // error,g為final引數,不能改變
}
public static void main(String[] args){
}
}
15 使用final函式的原因有2個。第一個是把方法鎖定,以防任何繼承類修改該函式,不會被覆蓋;第二個原因是效率,編譯器對final函式的呼叫都轉為內嵌呼叫,但只對程式碼小的函式內嵌呼叫會比較有用。
16 因為類的private函式,子類是沒有許可權呼叫,所以private函式預設都是final。
class WithFinals{
private final void f(){System.out.println("WithFinals.f()");}
private void g(){System.out.println("WithFinals.g()");}
}
class OverridingPrivate extends WithFinals{
private final void f(){ System.out.println("OverringPrivate.f()"); }
private void g(){ System.out.println("OverridingPrivate.g()");}
}
class OverridingPrivate2 extends OverridingPrivate{
public final void f(){ System.out.println("OverringPrivate.f()"); }
public void g(){ System.out.println("OverridingPrivate.g()");}
}
public class Chocolate{
public static void main(String[] args){
OverridingPrivate2 o = new OverridingPrivate2();
o.f();
o.g();
OverridingPrivate o2 = o;
o2.f(); // error
o2.g(); // error
WithFinals wf = o;
wf.f(); // error
wf.g(); // error
}
}
17 當將某個類定義為final時,表示該類不能被繼承。final class A { . . . } 。 由於final類不能被繼承,所以該類中的所有方法都隱式指定為final,無法覆蓋。
18 Java中所有事物都是物件。
第8章 多型
1 動態繫結,在執行時才會判斷物件的型別。Java中除了static函式和final函式外,其他所有函式都是後期繫結
class A2{
void play(){ System.out.println("A");}
}
class B extends A2{
void play(){ System.out.println("B");}
}
class C extends A2{
void play(){ System.out.println("C");}
}
public class Chocolate{
public static void tune(A2 a){
a.play();
}
public static void main(String[] args){
A2 a = new B();
a.play(); // 動態繫結,呼叫B的play()
tune(a); // 動態繫結,傳入B類引用
}
}
2 private函式無法被覆蓋
public class Chocolate{
private void f(){ System.out.println("private f()"); }
public static void main(String[] args){
Chocolate a = new B();
a.f(); // 呼叫Chocolate的f()函式,因為是private型別,無法被覆蓋
}
}
class B extends Chocolate{
public void f(){ System.out.println("public f()"); }
}
3 成員變數和靜態方法都不具有多型性。當Sub物件轉型為Super引用時,任何成員變數的訪問操作都將由編譯器解析,因此不會多型。Super.field和Sub.field分配了不同的儲存空間,所以Sub實際由兩個field變數。靜態函式則是跟類相關。
class Super{
public int field = 0;
public int getField(){ return field; }
}
class Sub extends Super{
public int field = 1;
public int getField(){ return field;}
public int getSuperField(){ return super.field;}
}
public class Chocolate{
private void f(){ System.out.println("private f()"); }
public static void main(String[] args){
Super sup = new Sub();
System.out.println("sup.field = "+sup.field + " , sup.getField() = "+sup.getField());
Sub sub = new Sub();
System.out.println("sub.field = "+sub.field + " , sub.getField() = "+sub.getField()+" , sub.getSuperField() = "+sub.getSuperField());
}
}
輸出
sup.field = 0 , sup.getField() = 1
sub.field = 1 , sub.getField() = 1 , sub.getSuperField() = 0
class StaticSuper{
public static String staticGet(){
return "Base staticGet()";
}
public String dynamicGet(){
return "Derived dynamicGet()";
}
}
class StaticSub extends StaticSuper{
public static String staticGet(){
return "Derived staticGet()";
}
public String dynamicGet(){
return "Derived dynamicGet()";
}
}
public class Chocolate{
public static void main(String[] args){
StaticSuper sup = new StaticSub();
System.out.println(sup.staticGet());
System.out.println(sup.dynamicGet());
}
}
輸出
Base staticGet() // 因為是靜態函式,沒有動態呼叫子類的函式
Derived dynamicGet()
4 構造器其實是隱式的static函式。
5 所有構造器的呼叫順序
- 呼叫基類構造器
- 按宣告順序初始化成員變數
- 呼叫自己的構造器主體
class Meal {
private Bread b = new Bread();
Meal(){System.out.println("Meal");}
}
class Bread {
Bread(){System.out.println("Bread");}
}
class Cheese {
Cheese(){System.out.println("Cheese");}
}
class Lettuce {
Lettuce(){System.out.println("Lettuce");}
}
class Lunch extends Meal{
private Lettuce l = new Lettuce();
Lunch(){System.out.println("Lunch");}
}
class ProtableLunch extends Lunch{
ProtableLunch(){System.out.println("ProtableLunch");}
}
public class Chocolate extends ProtableLunch{
private Bread b = new Bread();
private Cheese c = new Cheese();
private Lettuce l = new Lettuce();
private Chocolate(){ System.out.println("Chocolate");}
public static void main(String[] args){
new Chocolate();
}
}
輸出
Bread
Meal
Lettuce
Lunch
ProtableLunch
Bread
Cheese
Lettuce
Chocolate
6 記住銷燬的順序應該要跟初始化順序相反。
7 構造器內部有多型函式的問題。如下所示Glyph的draw()會被RoundGlyph的draw()覆蓋。初始化的實際過程如下
- 在其他任何事物發生之前,將分配給物件的儲存空間初始化為二進位制的零
- 呼叫基類的構造器,呼叫被覆蓋後的draw()函式,但radius值為0
- 按照宣告的順序初始化成員變數
- 呼叫自身的構造器
class Glyph{
void draw(){ System.out.println("Glyph.draw()");}
Glyph(){
System.out.println("Glyph() before draw()");
draw();
System.out.println("Glyph() after draw()");
}
}
class RoundGlyph extends Glyph{
private int radius = 1;
RoundGlyph(int r){
radius = r;
System.out.println("RoundGlyph.RoundGlyph(), radius = "+radius);
}
void draw(){
System.out.println("RoundGlyph.draw(), radius = "+radius);
}
}
public class Chocolate{
public static void main(String[] args){
new RoundGlyph(4);
}
}
輸出
Glyph() before draw()
RoundGlyph.draw(), radius = 0
Glyph() after draw()
RoundGlyph.RoundGlyph(), radius = 4
注:編寫構造器有一條有效的準則:用儘可能簡單的方法使物件進入正常狀態,儘量避免呼叫其他函式。
8 Java SE5添加了協變返回型別,表示子類的被覆蓋函式可以返回父類函式返回型別的某種子類型別。
class Grain{
public String toString(){ return "Grain"; }
}
class Wheat extends Grain{
public String toString(){ return "Wheat"; }
}
class Mill{
Grain process(){ return new Grain(); }
}
class WheatMill extends Mill{
Wheat process(){ return new Wheat();}
}
public class Chocolate{
public static void main(String[] args){
Mill m = new Mill();
Grain g = m.process();
System.out.println(g);
m = new WheatMill();
g = m.process();
System.out.println(g);
}
}
輸出
Grain
Wheat
9 向上轉型會丟失具體的型別資訊。可以採用向下轉型
class Useful{
public void f(){}
public void g(){}
}
class MoreUseful extends Useful{
public void f(){}
public void g(){}
public void u(){}
public void v(){}
public void w(){}
}
public class Chocolate{
public static void main(String[] args){
Useful[] x = {
new Useful(),
new MoreUseful()
};
x[0].f(); // ok
x[1].g(); // ok
x[1].u(); // error 因為x[1]向上轉型,丟失了具體型別資訊,無法呼叫u()函式
((MoreUseful)x[0]).u(); // error,x[0]是Useful型別,不能向下轉型為MoreUseful
((MoreUseful)x[1]).u(); // ok,x[1]向下轉型為MoreUseful
}
}
第9章 介面
1 只有宣告但沒有函式體的函式稱為抽象函式。如 abstract void f( ) 。 有抽象函式的類即為抽象類,抽象類不能建立物件,會報錯。
2 如果一個類繼承抽象類,並想建立該類物件,那麼該子類必須要為抽象類的所有抽象函式提供函式定義。如果沒有全部提供函式定義,該類還是抽象類。
3 抽象類並不要求所有的函式都是抽象函式
abstract class Instrument { // 抽象類
private int i;
public abstract void play(int n);
public String what(){ return "Instrument";}
public abstract void adjust();
}
class Wind extends Instrument{
public void play(int n){
System.out.println("ok");
}
public String what(){ return "Instrument";}
public abstract void adjust(){};
}
4 interface關鍵字定義一個完全抽象的類,即介面。所有函式只有宣告沒有函式體。介面被用來建立類與類之間的協議。
5 可以在interface關鍵字前面加public,僅限於該介面與其同名的檔案中被定義。介面也可以包含成員變數,這些成員變數自動是static和final的。成員函式也自動為public。
6 實現介面是用implements。必須要實現所有函式的函式體
interface Instrument{
int VALUE = 5; // static & final
void play(int n); // automatically public
void adjust(); // automatically public
}
class Wind implements Instrument{ // 實現介面必須要定義所有函式的函式體
public void play(int n){ System.out.println("ok");}
public void adjust(){}
}
7 只能繼承一個類,但可以實現多個介面。需要將所有的介面名都置於implements關鍵字之後,用逗號將它們一一隔開。
interface CanFight{
void fight();
}
interface CanSwim{
void swim();
}
interface CanFly{
void fly();
}
class ActionCharacter{
public void fight(){}
}
class Hero extends ActionCharacter implements CanFight,CanSwim,CanFly{
//public void fight(){};
public void swim(){};
public void fly(){};
}
public class ArrayApp {
public static void t(CanFight x){ x.fight(); }
public static void u(CanSwim x){ x.swim(); }
public static void v(CanFly x){ x.fly(); }
public static void w(ActionCharacter x){ x.fight(); }
public static void main(String[] args){
Hero h = new Hero();
t(h);
u(h);
v(h);
w(h);
}
}
Hero類繼承了ActionCharacter和實現介面CanFight,CanSwim和CanFly。Hero必須要實現所有介面的函式,但介面CanFight的fight()跟ActionCharacter的fight()一樣,所以Hero無須再實現CanFight的fight()函式。t(),u(),v(),w()函式將父類和介面作為引數,所以Hero物件可以向上轉型。
8 使用介面的核心原因:為了能夠向上轉型為多個基型別。第二個原因是防止客戶端程式設計師建立該類的物件,並確保這僅僅是建立一個介面。
9 通過繼承來擴充套件介面。類只能繼承一個類,但介面可以繼承多個介面
interface Monster{
void menace();
}
interface DangerousMonster extends Monster{
void destroy();
}
interface Lethal{
void kill();
}
interface Vampire extends DangerousMonster,Lethal{ // 介面可以繼承多個介面
void drinkBlood();
}
10 在介面中定義的成員變數不能是空final,但可以被非常量表達式初始化。
interface Monster{
Random RAND = new Random(47);
int RANDOM_INT = RAND.nextInt(12);
long RANDOM_LONG = RAND.nextLong()*10;
float D = 4.32f;
}
11 介面是實現多重繼承的途徑,而生成遵循某個介面的物件的典型方式是工廠方法設計模式。
interface Service{
void method1();
void method2();
}
interface ServiceFactory{
Service getService();
}
class Implementation1 implements Service{
Implementation1(){}
public void method1(){ System.out.println("Implementation1 method1");}
public void method2(){ System.out.println("Implementation1 method2");}
}
class Implementation1Factory implements ServiceFactory{
public Service getService(){
return new Implementation1();
}
}
class Implementation2 implements Service{
Implementation2(){}
public void method1(){ System.out.println("Implementation2 method1");}
public void method2(){ System.out.println("Implementation2 method2");}
}
class Implementation2Factory implements ServiceFactory{
public Service getService(){
return new Implementation2();
}
}
public class ArrayApp {
public static void serviceConsumer(ServiceFactory fact){
Service s = fact.getService();
s.method1();
s.method2();
}
public static void main(String[] args){
serviceConsumer(new Implementation1Factory());
serviceConsumer(new Implementation2Factory());
}
}