我的Thinking in Java學習筆記(十)

來源:互聯網
上載者:User
第六章  重複運用classes
  在面向過程的語言中重複運用代碼只是簡單的複製代碼,以達到重複運用的目的,而在物件導向的java程式中,代碼的重用主要體現在2點
  1、在新的class中使用既有的class,這中方法稱之為"組合"。但是這種重用方式只是很單純的重複運用以有的代碼 功能,而非重複運用其形式。
  2、讓新的class成為既有class的一類,並且根據需要加入新的功能,而無須更動原有class,這種方法稱之為"繼承"。
  組合文法
    其實組合我們在以前的例子中已經大量的用到了,我們只要將物件控點放置在class中就是組合!
    class compostion
    {
     private String s;
     compostion()
     {
      System.out.println("compostion()");
      s=new String("hello");
     }
     public String toString()
     {
      return s;
     }
    }
    public class test
    {
     compostion c;  //物件控點
     int i;
     public void show()
     {
      System.out.println("int = "+i);
      System.out.println("compostion = "+c);
     }
     public static void main(String args[])
     {
      test t = new test();
      t.show();
     }
    }
    其中每個非基礎資料型別 (Elementary Data Type)的對象都有一個toString()方法,該函數用於將compostion轉換為一個string,和其他string相加class中基礎資料型別 (Elementary Data Type)會被初始化為預設值,而物件控點會被初始化為null。如果你要使用該控制代碼,切記要初始化,否則會抱null 指標錯誤!
  繼承
    繼承是java語言中極其重要的一部分,使用關鍵字extends來實現,這樣變自動的讓子類獲得了父類中所有的成員資料和函數。而java中所有的類甚至包括你自己已經定義的或者將要定義的類都是繼承自object類的,在編譯器內部進行的隱式繼承
    class base
    {
     int i=10;
     public  void show()
     {
      System.out.println("base method");
     }
     public static void main(String args[]) // java允許在同一個檔案中的class擁有各自的main()
     {
      new base().show();
     }
    }
    class derived extends base //繼承
    {
     public void show() //覆蓋了base的函數
     {
      System.out.println("derived method");
      super.show(); //調用base的函數
     }
     public void newMethod() //子類中新加入的函數
     {
      System.out.println(i); //列印base中的資料
     }
     public static void main(String args[])
     {
      derived d = new derived();
      d.show();
      d.newMethod();
     }
    }
    base的初始化
     當子類被初始化的時候系統會先將被繼承的父類初始化,java編譯器會在調用子類建構函式之前調用父類的建構函式
     class base
     {
      base()
      {
       System.out.println("base method");
      }
     
     }
     class derived extends base
     {
      derived()
      {
       //super();  系統會自動加入對父類的調用
       System.out.println("derived method");
      }
      public static void main(String args[])
      {
       derived d = new derived();
      }
     }
     假如你的父類是帶有引數的class,那麼編譯器是不會自動調用建構函式的,你必須使用super來調用,否則系統會抱錯
     class base
     {
      base(String s)
      {
       System.out.println(s);
      }
     
     }
     class derived extends base
     {
      derived()
      {
       super("base method"); //必須是建構函式的第一行語句
       System.out.println("derived method");
      }
      public static void main(String args[])
      {
       derived d = new derived();
      }
     }
    相容組合和繼承
     有的時候,我們在編寫class的時候不但用到組合,還用到繼承。
  組合和繼承之間的選擇
    如果你只是希望在新class中使用到既有class的功能,並且隱藏起實現細目,那麼最好使用組合。
    如果你希望為既有的class開發一種特殊版本,那麼繼承再好不過了。
    如果你的既有類和新類是一種is a的(是一個)關係,那麼使用繼承,如果是一種has a(有一個)關係,那麼使用組合
  protected
    我們再來複習一下protexted的意義:繼承此class的子類,可以訪問該類的成員,並且對於一個包內的其他類是friendly的
  漸進式開發
    繼承的優點之一就是支援漸進式開發,在這種開發模式下,你可以在程式中加如新的程式碼,但是卻不影響父類的代碼
  向上轉型
    由於繼承的關係,在子類中可以使用父類的所有函數,並且任何發送給父類的訊息,也可以發送給子類
    class base
    {
     protected void show()
     {
      System.out.println("base method");
     }
     protected void getSomeone( base b)
     {
      b.show();
     }
    }
    class derived extends base
    {
     public static void main(String args[])
     {
      derived d = new derived();
      d.getSomeone(d);
     }
    }
    注意在getsomeone的函數定義中,我們定義的是他只能接受一個base類的控制代碼,然而他居然接受了子類的控制代碼。因為子類雖然和父類不太相同,但是他畢竟是父類的一種因此適用與父類的函數當然也適用與子類咯,我們把這種把子類轉型為父類的做法叫做向上轉型upcasting
    class base
    {
     public void methodOne()
     {
      System.out.println("base.methodOne");
     }
     public void methodTwo()
     {
      System.out.println("base.methodTwo");
     }
    }
    
    class derived  extends base
    {
     public void methodOne()
     {
      System.out.println("derived.methodOne");
     }
     public void methodTwo()
     {
      System.out.println("derived.methodTwo");
     }
     public void methodNew()
     {
      System.out.println("derived.methodNew");
     }
     public static void main(String[] args)
     {
      base d = new derived();
      d.methodOne(); //雖然已經向上轉型成為了base,但是調用的函數還是基類的函數,顯示derived.methodOne
      d.methodTwo(); //同上
      //d.methodNew(); 因為已經向上轉型,所以丟失了子類特有的函數
     }
    }
    為什麼需要向上轉型?
      子類是父類的一個超集,因此子類中至少包含父類中的函數,並且可能會更多。然而向上轉型會使子類遺失和父類不同的方法,向上轉型一定是安全的,因為這是從特殊類型改變成通用類型。
    組合vs繼承
      當你需要向上轉型的時候是使用繼承的最佳時間
  關鍵字final
    什麼是final?就是"最終"的意思,也就是不可改變的意思,我們在這裡討論3中final:data、method、class
    final data
      final的資料是固定不變的,不變的資料稱之為常量,他是很有用的,因為它
      1、可以是永不改變的編譯期常量。編譯期常量可以在編譯期執行某些計算,減少執行期的負擔,此類常量必須是基礎資料型別 (Elementary Data Type),使用final修飾,必須給定初值。
      2、可以在執行期被初始化,而你卻不想再改變他。
      如果某個資料既有static還有final,那麼他就會擁有一塊無法改變的儲存空間。當把final用於對象時,final讓控制代碼保持不變不能再重新指向其他對象,然而對象本身卻是可以改變的,這點和final的基礎資料型別 (Elementary Data Type)不能改變其值的特點有所不同。
      class Value
      {
       int i = 1;
      }
      class finalData
      {
       //編譯期常量
       final int i = 10;
       static final int II = 20;
       //典型的常量
       public static final int III= 30;
       //執行期常量
       final int iiii = (int)(Math.random()*20);
       static final int iiiii = (int)(Math.random()*20);
       
       Value v = new  Value();
       final Value vv =new Value();
       static final Value vvv =new Value();
       //數組
       final int[] a = { 1,2,3,4,5,6};
       
       public void show(String id)
       {
        System.out.println(id+" : "+"iiii ="+iiii+" , iiiii = "+ iiiii);
       }
       public static void main(String[] args)
       {
        finalData fd = new finalData();
        //fd.i++;  不能改變值
        fd.vv.i++; //final對象可以改變其對象值
        fd.v=new Value(); //非final
        for(int i = 0;i<fd.a.length;i++)
         fd.a[i]++; //final對象可以改變其值
        //fd.vv = new Value(); //final對象的控制代碼不能改變其引用的對象
        //fd.vvv =new Value(); //final對象的控制代碼不能改變其引用的對象
        //fd.a = new int[3]; //final對象的控制代碼不能改變其引用的對象
        
        fd.show("fd");
        System.out.println("creat new finalData");
        finalData nfd = new finalData();
        fd.show("fd");
        nfd.show("nfd");
       }
      }
      /*顯示fd : iiii =2 , iiiii = 3
      creat new finalData
      fd : iiii =2 , iiiii = 3
      nfd : iiii =1 , iiiii = 3*/其中因為iiii是非static的,因此每次的值不相同。static的final資料只會在裝載的時候進行初始化,不會再每次產生新對象時再被初始化一次
      空白的final
      final同時允許將資料成員聲明為final的,但是並不初始化,但是,你必須在使用該成員資料之前保證該資料成員的初始化。這樣做的好處是既保證了資料的恒久不變性,
      又可以根據對象的不同產生不同的final資料,具有更大的靈活性。
      class finalTest
      {
       final String s;
       finalTest(String s)
       {
        this.s=s;
        System.out.println(this.s);
       }
       public static void main(String[] args)
       {
        new finalTest("hello");
        new finalTest("bye");
       }
      }
      final的引數
        當final的引數是基礎資料型別 (Elementary Data Type)的時候,其值不允許改變。而當是對象時,則不允許將改變控制代碼的指向。
    final的函數
      使用final函數的意義有二:
               1、鎖住該函數,不允許子類進行改變,也就是不能覆寫
               2、讓短小的函數提高執行效率
      final和private
        class中private的函數其實就是天生的final函數,因為你無法使用private函數,當然也就無法覆寫該函數,而當你嘗試覆寫他的時候,其實是寫了一個全新的函數,把final加在private函數上,不具備任何意義!
    final的class
      當你確切的希望該class不能被繼承的時候,就需要用到final,然而無論class是否是final的,class中的資料成員既可以是final的也可以不是
  初始化以及class的裝載
    java中只有在必要的時候才會裝載相關類,一般來說是class的初次使用和繼承時裝載,所謂初次使用,不僅僅是產生對象的時候,也有可能是某個static函數或者資料成員被使用的時候。首次使用class的這個時間點,也是static初始化的時候,任何static的class成員被裝載時,會根據他們出現的次序分別初始化,並且只會進行一次初始化。
    繼承和初始化
      下面的這個程式展示了繼承和裝載的順序
      class base
      {
       int i = 10;
       int j ;
       base() //6、執行base建構函式
       {
        show(" i = "+i+", j = "+j);
        j=20;
       }
       static int m = show("static base.m init"); //2、進行靜態成員的初始化以便裝載
       public static int show(String s)
       {
        System.out.println(s);
        return 30;
       }
      }
      
      class derived  extends base
      {
       int  k =show("derived.k init"); //7、進行基礎資料型別 (Elementary Data Type)的初始化
       derived() //8、已經完成資料成員的初始化動作,開始執行函數
       {
        show(" k = "+ k);
        show(" j = "+j);
       }
       static int n =show("static derived.n init"); //3、進行靜態成員的初始化以便裝載
       public static void main(String[] args) //1、啟用derived.main()靜態方法,class derived被使用,引起裝載器啟動,發現extends,於是裝載base
       {
        show("derived constructor"); // 4、裝載完畢,開始執行程式
        derived d = new derived(); //5、開始產生對象,由於extends的關係,先產生base對象
       }
      }
      輸出/*static base.m init
        static derived.n init
        derived constructor
         i = 10, j = 0
        derived.k init
         k = 30
         j = 20*/
  總結
    繼承和組合都允許你根據已經有的類產生新類。使用組合來重複運用以有類,使其成為新類中的一部分;繼承則用於介面的複用,由於子類繼承了父類的介面,因此可以向上轉型,這對多態是極為重要的。推薦多使用組合,組合具有彈性,繼承具有靈活性。
    
  習題解答
    其實這章的習題沒什麼難度,全是知識點的鞏固習題,大家認真做,一定可以做出來的,不會的地方注意看書!
  下一章我的筆記會寫到物件導向的精髓---多態!謝謝關注和支援!
 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.