2013年2月15日 星期五

[Java]3-物件導向大補丸 Interface、Abstract、建構子


第一節-Java Interface

一、前言:
繼承是物件導向語言中重要的特性。在目前主要的物件導向語言中,C++使用多重繼承(multiple inheritance)的機制,Java 則採用單一繼承(single inheritance),但是多重繼承的關係比較接近真實世界的法則,因此Java 採用了介面(除特別註明外,本文中介面一詞泛指Java 中的interface )來達成多重繼承的需求,避免多重繼承的缺點,並維持Java一貫簡單易懂的風格。單一繼承和多重繼承各有好壞,以下將就Java 介面與一般多重繼承的語言之間的差異作簡單的介紹。
二、Java 介面及多重繼承:
在物件的繼承關係裡,我們習慣用有向圖(Direct Acyclic Graphic)來表示繼承的關係,在C++中多重繼承可能會引起語意的模糊(ambiguity)。如恐龍基因和青蛙的基因都是一種動物基因,而科學家取青蛙基因補足恐龍基因裡不足的部分,形成新的恐龍基因,其程式虛擬碼如下:

abstract class Animal { abstract void talk();}

class Frog extends Animal { void talk() { System.out.println("Ribit,ribit.");}

class Dinosaur extends Animal { void talk() { System.out.println("I'm a dinosaur"); }}

class Frogosaur extends Frog, Dinosaur { Animal animal = new Frogosaur(); animal.talk();}
當上列程式碼執行時,動態載入程式不清楚要呼叫Dinosaurtalk 或是Frog talk 

Java
 的單一繼承避免了語意模糊的問題,在介面的機制裡定義的都是抽象的(abstract)類別操作方法(Method),抽象的操作方法是概念上(conceptual)的而不是一個實體的方法(concrete existence)。在介面中只是定義一個類別的抽象方法,而直到實作(本文中實作泛指Java 中的implements )這個介面時才完成這個類別的實體方法。考慮以下Java 程式片段:

interface Talkative { void talk();}

abstract class Animal implements Talkative { abstract public void talk();}

class Dog extends Animal { public void talk() { System.out.println("Woof!"); }}

class Cat extends Animal { public void talk() { System.out.println("Meow."); }}

class Interrogator { Static void makeItTalk(Talkative subject) { Subject.talk(); }}
上面的程式先宣告了Talkative 的介面,一直到dog cat 的類別中才完成talk()這個方法,我們可以在規劃階段先定義共通介面的行為,讓實作上可以更有彈性,更多型(polymorphism ),接下來將介紹介面如何讓程式更有多型的效果。
多型讓程式設計師可透過父類別來呼叫子類別的方法,而讓程式更多型可獲得釵h好處。例如在上面的程式中,若日後在Animal 家族裡有新的類別繼承自Animal ,可以直接加入新的類別而不需修改Interrogator 。我們可以加入下列程式,然後透過相同的方式呼叫新類別Bird 

class Bird extends Animal { void talk() { System.out.println("Tweet, tweet!"); }}
另一方面我們的程式可能會有具相同的行為但是卻完全不同的類別家族,例如一個clock 類別,它會在預定的時間吵醒你,這時它也具有Talkative 的行為,考慮以下程式:

class CuckooClock implements Talkative { public void talk() { System.out.println("Cuckoo, cuckoo!"); }}

CuckooClock
 實做了Talkative 介面,因此我們可以傳遞CuckooClock 物件給makeItTalk 這個方法。


class Example4 { public static void main(String[] args) { CuckooClock cc = new CuckooClock(); Interrogator.makeItTalk(cc); }}
因此藉著實作Talkative 的介面,可以讓這些有共同行為的類別,透過相同的方式來呼叫。接下來我們探討Java中如何讓程式更多型,主要有兩種方式:

1.
 繼承:透過繼承的關係可以很容易加入新的子類別,這些類別都有父類別的特徵,當父類別中有部分程式改變,子類別可直接繼承,而不需要去改變每個子類別的內容。2. 合成(composition):是指讓類別裡使用參考(reference)來使用的其他的物件,在Apple 類別裡使用fruit直接參考Fruit 的方法,一個簡單的程式如下:

class Fruit {}

class Apple {private Friut fruit = new Fruit();}
利用上述兩種方式來達成多型的效果各有其優缺點:透過繼承的方式加入新的方法變的很簡單,只消在父類別中加入所需的方法,但是想像當你使用繼承的方式修改一個父類別,所有子類別都影響到了,不論那是不是你所想的,而且經由繼承的方式,當子類別生成時,父類別將一併生成,直到子類別被回收(garbage collection )。透過合成則可讓參考到的類別的生成(creation )時間延後到需要使用時再生成,當修改類別時也較容易控制影響的範圍,但是如果想對父類別作修改,而子類別要在不更動的情形下使用父類別的的新弁遄A在合成的方式中需要透過介面才能達到這個弁遄A考慮以下程式碼:

interface Peelable { int peel();}

class Fruit { // Return int number of pieces of peel that // resulted from the peeling activity. public int peel() { System.out.println("Peeling is appealing."); return 1; }}

class Apple implements Peelable { private Fruit fruit = new Fruit(); public int peel() { return fruit.peel(); }}

class FoodProcessor { static void peelAnItem(Peelable item) { item.peel(); }}

class Example5 { public static void main(String[] args) { Apple apple = new Apple(); FoodProcessor.peelAnItem(apple); }}
後來我們可以定義一個Banana 類別如下:

class Banana implements Peelable { private Fruit fruit = new Fruit(); public int peel() { return fruit.peel(); }}
Banana 類別有著和Apple 一樣的合成關係,他們都使用了Fruit 裡的peer(),但是跟一般構成關係不同的地方是Banana 實作Peelable 介面,這使得Banana能跟Apple 一樣在peelAnItem 中被傳遞,因此透過介面的機制,我們可以兼得繼承和合成兩種方式所帶來的彈性。在Java 的領域裡,也就是因為介面的機制讓Java能如此的有彈性,雖然Java 語言目前僅支援單一繼承,但透過實作介面是可以達到某種程度的多重繼承。在介面中只是宣告一些公用的行為或靜態變數,而無任何實做,這些行為都是抽象方法,一個類別雖然只能繼承一個父類別,但卻可以實做多個介面。因此介面的好處是:讓程式在單一繼承的類別下更具多型的特性,免除多重繼承所帶來的負擔。



---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
INTERFACE
Interface中只有資料和方法的聲明,它只是提供一個框架。所以其中的方法不存在存取介面中定義的靜態常量的問題。
Interfaces cannot have constructors介面沒有構造函數
介面中的所有資料成員都是static final,即靜態常量(這兩個關鍵字可以不寫)但必須給常量賦初值;
介面中的所有方法都只有定義沒有實現細節,並且全部為public(可以不寫出來),所以介面中的方法全部是抽象方法
介面比抽象更抽象
抽象類中可以有構造方法!
介面中不能定義構造方法!
--------------------------------------------------------------------------------------------------------------------------
當您定義類別時,可以僅宣告方法名稱而不實作當中的邏輯,這樣的方法稱之為「抽象方法」(Abstract method),如果一個類別中包括了抽象方法,則該類別稱之為「抽象類別」(Abstract class),抽象類別是個未定義完全的類別,所以它不能被用來生成物件,它只能被擴充,並於擴充後完成未完成的抽象方法定義。

在Java中要宣告抽象方法與抽象類別,您使用"abstract"關鍵字,直接來看個應用的例子,下面定義一個簡單的比大小遊戲抽象類別:
  • AbstractGuessGame.java
public abstract class AbstractGuessGame {
    private int number;

public void setNumber(int number) {
        this.number = number;
    }

public void start() {
        showMessage("Welcome");

int guess;
        do {
            guess = getUserInput();
            if(guess > number) {
                showMessage("bigger than the goal number");
            }
            else if(guess < number) {
                showMessage("smaller than the goal number");
            }
            else
                showMessage("you win");
        } while(guess != number);
    }

protected abstract void showMessage(String message);
    protected abstract int getUserInput();
} 

在宣告類別時使用"abstract"關鍵字,表示這是一個抽象類別,在這個類別中,您定義了start()方法,當中先實作比大小遊戲的基本規則,然而 您不實作與使用者互動及訊息是如何顯示的,這您分別定義為抽象方法showMessage()與 getUserInput(),在方法上使用"abstract"關鍵字,可以僅定義方法而不實作其內容。

使用這個類別的辦法是擴充它,並完成當中未定義完全的抽象方法showMessage()與getUserInput(),例如實作一個簡單的文字介面遊戲類別:
  • ConcreteGuessGame.java
import java.util.Scanner;

public class ConcreteGuessGame extends AbstractGuessGame {
    private Scanner scanner;

public ConcreteGuessGame() {
        scanner = new Scanner(System.in);
    }

protected void showMessage(String message) {
        System.out.println(message + "!");
    }

protected int getUserInput() {
        System.out.print("input a number: ");
        return scanner.nextInt();
    }
} 

接下來寫個簡單的測試程式,看看這個文字介面比大小遊戲類別是不是可以運作:
  • Test.java
public class Test {
    public static void main(String[] args) {
        AbstractGuessGame guessGame = 
                    new ConcreteGuessGame();
        guessGame.setNumber(50);
        guessGame.start();
    }
}

這邊必須知道,一個基底類別的物件參考名稱,可以用來指向其衍生類別的物件而不會發生錯誤,所以上面的這個指定是可以接受的:
AbstractGuessGame guessGame = 
new ConcreteGuessGame();

由於guessGame仍是AbstractGuessGame類型的參考名稱,它可以操作子類別 ConcreteGuessGame的實例中名稱相同的公開操作介面(方法),簡單的說,透過guessGame參考名稱,您可以操作 ConcreteGuessGame的實例之setNumber()與start()方法,這是多型(Polymorphism)操作的一個實際例子。

執行結果:
Welcome!
input a number: 10
smaller than the goal number!
input a number: 60
bigger than the goal number!
input a number: 50
you win!
今天如果您想要實作一個有視窗介面的比大小遊戲,則您可以擴充AbstractGuessGame並實作您的抽象方法showMessage()與 getUserInput(),事實上,上面的例子是 Template Method 模式 的一個實際例子,使用抽象類別與方法來實作Template Method 模式,在很多應用場合都可以見到。

--------------------------------------------------------------------------------------------------------------------------

建構子

基本概念:http://miakila.pixnet.net/blog/post/12049466
1、包括抽象類別都需要有建構子
2、 若沒有寫建構子,則系統會自動加入
3、 建構子的名稱與類別名稱一模一樣
4、 new時,建構子會自動被執行
5、 建構子不能有任何的回傳值,包括void,因為它只回傳物件的雜湊碼
6、 建構子可有任何存取修飾子
 constructor可以為private,不過要注意把它設為private所帶來的效果
 Methods with the same name as the constructor(s). (這種題常有)
與Constructor(s)有相同名字的方法。
每種基本資料型別都有一個相對應的型別轉換類別(type-wrapper class).這些類別是Character, Byte, Integer, Long, Float, Double, Boolean.它可以把基本資料型別轉成物件.然後我們就能用多型的方法去處裡這些資料.因為我們要處裡的對象是物件,而非基本資料型別的物件,所以我們德將變數轉成物件,因為每個物件都是object類別.
範例:
1、包括抽象類別都需要有建構子
Class A{
A( ){……}
}
main A a1=new A( );
2、若沒有寫建構子,則系統會自動加入
A( ){ } ---à稱為預設建構子
publicclass no07 {
publicstaticvoid main(String[] args) {
c c1=new c();
c c2=new c();
c c3=new c();
}
}
class c{
staticintcount=0;
c(){
count++;
System.out.println(""+count+"物件產生");
}
}
3、建構子的名稱與類別名稱一模一樣
4、當new時,建構子會自動被執行
5、建構子不能有任何的回傳值,包括void,因為它只回傳物件的雜湊碼
6、建構子可有任何存取修飾子
7、執行建構子,回一層一層往上走
8、建構子可覆蓋
Ex:
Private c( ){ }
此時無法new出新物件
 c( ){ }為預設存取權限,則在不同package就無法new出物件
故若要讓使用者new出一個物件,最好是public c( ){ }








package p1;
import p2.*;
publicclass no08 {
publicstaticvoid main(String[] args) {
x x1=new x();
Y y1=new Y();->error
}

}
package p2;
publicclass x {
public x(){super();}
//空白程式系統會自己加
}
class Y{
Y(){super();}//系統自己加的
}
class Z{
}

Y y1=new Y( );
class Y 為預設存取權,在不同packageprivate
故無法使用
若要被其他package使用,需:
a、在class前加public
b、並且要獨立成一個新檔

7、執行建構子,回一層一層往上走
package p1;
publicclass no08 {
publicstaticvoid main(String[] args) {
Z z1=new Z();
}
}
class x extends Object{
x(){
super();//沒有寫,系統會自動加
System.out.println("x class建構子");
}
}
class Y extends x{
Y(){
super();//沒有寫,系統會自動加
System.out.println("Y class建構子");
}
}
class Z extends Y{
Z(){
super();//沒有寫,系統會自動加
System.out.println("Z class建構子");
}
}一層一層返回,會到最頂端Object然後再一層一層印出 即便空白沒有寫,系統也會自己加上
8、建構子可覆蓋
package p1;
publicclass no08 {
publicstaticvoid main(String[] args) {
Z z1=new Z();
Z z2=new Z("abcd");
}
}
class x{
x(){
System.out.println("x class建構子");
}
}
class Y extends x{
Y(){
System.out.println("Y class建構子");
}
}
class Z extends Y{
Z(){
System.out.println("Z class建構子");


使用建構子的主要目的是將成員初始化為我們希望的值,這裡有幾個要注意的地方:
1. 在多層繼承時,子類別的建構子會先呼叫「父類別」的建構子,換言之就是從最上面的類別開始初始化成員。
2.因為class name一定是不同,所以「父類別的建構子」不可能作為「子類別的建構子」,所以也不可能有Override的情況發生
3.如果class內沒有定義建構子,系統會自已建立一個「預設建構子 className()子類別的預設建構子會自動呼叫父類別的預設建構子,用super(參數值)呼叫父類別建構子。
======
 super(參數值)只能放在建構子的第一行,一個建構子只能有一個。(this亦同)
 如果沒有使用super(參數值)指定,則因為「繼承」,會自動呼叫父類別。
 super只能呼叫自已的上一層而已。
※※ 父類別如果定義了一個以上的「非」預設建構子,系統便不會自動產生「預設建構子」,故如果子類別沒有用super(參數值)指定建構子,或是用super()呼叫父類別的預設建構子,就會出現錯誤。
 使用super的情況:
1.在建構子內,指定呼叫的父類別的某個建構子。
2.指定使用繼承來的某個成員,而不是用本類別改寫的成員。
======
 this是用來參考物件實體本身的。如果在class內用this(),代表呼叫此class的建構子。
 this = 此類別
 使用this的情況:
1. 取得此物件實體本身之「參考值」
2. 用來分辨成員函數與同名的「區域變數」
ex:
int a,b;
public method(int a,int b)
{
this.a = a;
this.b = b;
}
3. 在建構子內,呼叫同一class內其他的建構子
ex:
class class
{
public class getThis()
{
return this;
}
public class()
{
System.out.println(this.getThis());
}
}
===================
範例以下程式會出現錯誤,無法complie
class A
{
int i;
public A(int i)
{
this.i = i;
}
}
class B extends A
{
public static void main(String para[])
{
A a = new A(10);
System.out.println(a.i);
}
}

Ans: 因為class A已建立一個建構子,故系統並不會自動幫class A建立預設建構子,但class B沒有建立任何建構子,故系統在compile時會自動在class B內建立一個預設建構子 B(),但根據繼承原則,class B會去找class A的預設建構子,但class A沒有,所以會出現錯誤。
Solution:
1. class A內加上預設建構子 A()
2. class B內建立B(i)之建構子,使其符合class A的需求



[Java 筆記] 建構子

由於對「建構子」這個部分還不是很清楚,偏偏這個是很重要的一部分,所以特別針對「建構子」「super」「this」做了一些筆記,主要以防將來自已又忘記有關的一些重要原則可以回來看。 有錯請指導一下囉~ ^ ^
使用建構子的主要目的是將成員初始化為我們希望的值,這裡有幾個要注意的地方:
1. 在多層繼承時,子類別的建構子會先呼叫「父類別」的建構子,換言之就是從最上面的類別開始初始化成員。
2.因為class name一定是不同,所以「父類別的建構子」不可能作為「子類別的建構子」,所以也不可能有Override的情況發生
3.如果class內沒有定義建構子,系統會自已建立一個「預設建構子 className()」- 子類別的預設建構子會自動呼叫父類別的預設建構子,用super(參數值)呼叫父類別建構子。
======
※ super(參數值)只能放在建構子的第一行,一個建構子只能有一個。(this亦同)
※ 如果沒有使用super(參數值)指定,則因為「繼承」,會自動呼叫父類別。
※ super只能呼叫自已的上一層而已。
※※ 父類別如果定義了一個以上的「非」預設建構子,系統便不會自動產生「預設建構子」,故如果子類別沒有用super(參數值)指定建構子,或是用super()呼叫父類別的預設建構子,就會出現錯誤。
※ 使用super的情況:
1.在建構子內,指定呼叫的父類別的某個建構子。
2.指定使用繼承來的某個成員,而不是用本類別改寫的成員。
======
※ this是用來參考物件實體本身的。如果在class內用this(),代表呼叫此class的建構子。
※ this = 此類別
※ 使用this的情況:
1. 取得此物件實體本身之「參考值」
2. 用來分辨成員函數與同名的「區域變數」
ex: 
int a,b;
public method(int a,int b)
{
this.a = a;
this.b = b;
}
3. 在建構子內,呼叫同一class內其他的建構子
ex:
class class
{
public class getThis()
{
return this;
}
public class()
{
System.out.println(this.getThis());
}
}
===================
範例: 以下程式會出現錯誤,無法complie
class A
{
int i;
public A(int i)
{
this.i = i;
}
}
class B extends A
{
public static void main(String para[])
{
A a = new A(10);
System.out.println(a.i);
}
}
Ans: 因為class A已建立一個建構子,故系統並不會自動幫class A建立預設建構子,但class B沒有建立任何建構子,故系統在compile時會自動在class B內建立一個預設建構子 B(),但根據繼承原則,class B會去找class A的預設建構子,但class A沒有,所以會出現錯誤。
Solution:
1. 在class A內加上預設建構子 A()
2. 在class B內建立B(i)之建構子,使其符合class A的需求

沒有留言:

張貼留言