1. 程式人生 > >Java程式設計思想(2)

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 所有造器的呼叫順序

  1. 呼叫基類構造器
  2. 按宣告順序初始化成員變數
  3. 呼叫自己的構造器主體
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()覆蓋。初始化的實際過程如下

  1. 在其他任何事物發生之前,將分配給物件的儲存空間初始化為二進位制的零
  2. 呼叫基類的構造器,呼叫被覆蓋後的draw()函式,但radius值為0
  3. 按照宣告的順序初始化成員變數
  4. 呼叫自身的構造器
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());

	}
}