1. 程式人生 > ><資料結構與演算法分析>讀書筆記--實現泛型構件pre-Java5

<資料結構與演算法分析>讀書筆記--實現泛型構件pre-Java5

 

面向物件的一個重要目標是對程式碼重用的支援。支援這個目標的一個重要的機制就是泛型機制:如果除去物件的基本型別外,實現的方法是相同的,那麼我們就可以用泛型實現來描述這種基本的功能。

 

 

1.使用Object表示泛型

Java中的基本思想就是可以通過使用像Object這樣超類來實現泛型類。

示例一:

(1)編寫MemoryCell.java

package cn.pre.example;

public class MemoryCell {

    private Object storedValue;
    
    
    public
Object rend() { return storedValue; } public void write(Object x) { storedValue=x; } }

當我們使用這種策略時,有兩個細節必須要考慮。第一個細節(2)中闡釋,它描述一個main方法,該方法把“37”寫到MemoryCell物件中,然後由從MemoryCell物件讀出。為了訪問這種物件的一個特定方法,必須要強制轉換成正確的型別(當然了,在這個例子中可以不必強制轉換,因為在程式中可以呼叫toString()方法。這種呼叫對任何物件都是能夠做到的)。

 

(2)編寫TestMemoryCell並執行該類

package cn.pre.example;

import java.lang.management.MemoryNotificationInfo;

import javax.swing.event.MenuDragMouseEvent;
/**
 * 使用泛型MemoryCell類
 * @author youcong
 * @date 2018年12月25日20點19分
 */
public class TestMemoryCell {
  
    public static void main(String[] args) {
        
        MemoryCell m 
= new MemoryCell(); m.write("37"); String val = (String) m.rend(); System.out.println("Content are:"+val);
// System.out.println("Content are:"+m.rend().toString()); } }

 

 

第二個重要細節是不能使用基本型別。只有引用型別能夠與Object相容。

 

說到這,回顧一下Java的基礎知識,基本型別有哪些?引用型別有哪些?基本型別和引用型別的區別是?

基本型別:byte、short、int、long、double、float、boolean、char。

引用型別:Object、陣列、String和列舉等。

基本型別和引用型別的區別是:基本資料型別和引用型別的區別主要在於基本資料型別是分配在棧上的,而引用型別是分配在堆上的

 

那麼什麼是堆?什麼是棧?

堆(heap):是一個可動態申請的記憶體空間(其記錄空閒記憶體空間的連結串列由作業系統維護).
在java中,所有使用new xxx()構造出來的物件都在堆中儲存,當垃圾回收器檢測到某物件未被引用,則自動銷燬該物件.所以,理論上說java中物件的生存空間是沒有限制的,只要有引用型別指向它,則它就可以在任意地方被使用.

棧(stack):是一個先進後出的資料結構,通常用於儲存方法(函式)中的引數,區域性變數.
在java中,所有基本型別和引用型別都在棧中儲存.棧中資料的生存空間一般在當前scopes內(就是由{...}括起來的區域).

 

下面的示例二(可以很好的說明這一點):

 

package cn.pre.example;

import java.awt.HeadlessException;

public class HeapStackExample {

    
    public static void stackTest() {
        String s1 = "abc";
        String s2 = "abc";
        System.out.println(s1==s2);
    }
    
    public static void heapTest() {
        
        String s1 = new String("abc");
        String s2 = new String("abc");
        System.out.println(s1==s2);
    }

   public static void main(String[] args) {
    
      HeapStackExample.stackTest();
      HeapStackExample.heapTest();
}

}

 

 

在Java中有六個不同的地方可供存資料:

(1) 暫存器(register)。這是最快的儲存區,因為它位於不同於其他儲存區的地方——處理器內部。但是暫存器的數量極其有限,所以暫存器由編譯器根據需求進行分配。你不能直接控制,也不能在程式中感覺到暫存器存在的任何跡象。


(2)堆疊(stack)。位於通用RAM中,但通過它的“堆疊指標”可以從處理器哪裡獲得支援。堆疊指標若向下移動,則分配新的記憶體;若向上移動,則釋放那些 記憶體。這是一種快速有效的分配儲存方法,僅次於暫存器。建立程式時候,JAVA編譯器必須知道儲存在堆疊內所有資料的確切大小和生命週期,因為它必須生成 相應的程式碼,以便上下移動堆疊指標。這一約束限制了程式的靈活性,所以雖然某些JAVA資料儲存在堆疊中——特別是物件引用,但是JAVA物件不儲存其 中。

(3)堆(heap)。一種通用性的記憶體池(也存在於RAM中),用於存放所以的JAVA物件。堆不同於堆疊的好處是:編譯器不需要知道要從堆裡分配多少儲存區 域,也不必知道儲存的資料在堆裡存活多長時間。因此,在堆裡分配儲存有很大的靈活性。當你需要建立一個物件的時候,只需要new寫一行簡單的程式碼,當執行 這行程式碼時,會自動在堆裡進行儲存分配。當然,為這種靈活性必須要付出相應的程式碼。用堆進行儲存分配比用堆疊進行儲存儲存需要更多的時間。

(4)靜態儲存(static storage)。這裡的“靜態”是指“在固定的位置”。靜態儲存裡存放程式執行時一直存在的資料。你可用關鍵字static來標識一個物件的特定元素是靜態的,但JAVA物件本身從來不會存放在靜態儲存空間裡。


(5)常量儲存(constant storage)。常量值通常直接存放在程式程式碼內部,這樣做是安全的,因為它們永遠不會被改變。有時,在嵌入式系統中,常量本身會和其他部分分割離開,所以在這種情況下,可以選擇將其放在ROM中 。


(6)非RAM儲存。如果資料完全存活於程式之外,那麼它可以不受程式的任何控制,在程式沒有執行時也可以存在。

 

 

 

2.基本型別的包裝

當我們實現演算法的時候,常常遇到語言定型問題:我們已有一種型別的物件,可是語言的語法卻需要一種不同型別的物件。

這種技巧闡釋了包裝類的基本主題。一種典型的用法是儲存一個基本的型別,並新增一些這種基本型別不支援或不能正確支援的操作。

在Java中我們已經看到,雖然每一個引用型別都和Object相容,但是,8種基本型別卻不能。於是,Java為這8種基本型別中的每一種都提供了一個包裝類。

例如:int型別的包裝是Integer。每一個包裝物件都是不可變的(就是說它的狀態絕不能改變),它儲存一種當該物件被構建時所設定的原值,並提供一種方法以重新得到該值。包裝類也包含不少的靜態使用方法。

 

示例三(如何能夠使用MemoryCell來儲存整數):

package cn.pre.example;
/**
 * 基本型別包裝類
 * @author youcong
 * @date 2018年12月25日20點19分
 */
public class WrapperDemo {

    
    public static void main(String[] args) {
        
        MemoryCell m = new MemoryCell();
        m.write(new Integer(37));
        Integer wrapperVal = (Integer) m.rend();
        int val = wrapperVal.intValue();
        System.out.println("Contents are:"+val);
    }
    
}

 

基本資料對應的包裝類:

byte -> Byte

short ->Short

int -> Integer

long ->Long

double ->Double

float ->Float

char ->Character

boolean -> Boolean

 

3.使用介面型別表示泛型

只有在使用Object類中已有的那些方法能夠表示所執行的操作的時候,才能使用Object作為泛型型別來工作。

例如,考慮在由一些項組成的陣列中找出最大項的問題。基本的程式碼是型別無關的,但是它的確需要一種能力來比較任意兩個物件,並確定哪個是大的,哪個是小的。因此,我們不能直接找出Object的陣列中的最大元素。最簡單的想法是找出Comparable的陣列中最大元。要確定順序,可以使用compareTo()方法,我們知道,它對所有的Comparable都必然是現成可用的。

現在,提出幾個忠告很重要。首先,只有實現Comparable介面的那些物件才能夠作為Comparable陣列的元素被傳遞。僅有compareTo()方法但並未宣稱實現Comparable介面的物件不是Comparable的,它不具有必需的IS-A關係。因為我們也許會比較兩個Shape的面積,因此假設Shape實現Comparable介面。

第二,如果Comparable陣列有兩個不相容的物件(例如一個String和一個Shape),那麼Comparable方法將丟擲異常ClassCastException(型別轉換異常)。

第三,如前所述,基本型別不能作為Comparable傳遞,但是包裝類則可以,因為它們實現了Comparable介面。

第四,介面究竟是不是標準的庫介面倒不是必需的。

最後,這個方案不是總能夠行得通,因為有時宣稱一個類實現所需的介面是不可能的。例如,一個類可能是庫中的類,而介面卻是使用者定義的介面。如果一個類是final類,那麼我們就不可能擴充套件它以建立一個新的類。

4.陣列型別的相容性

語言設計中的困難之一是如何處理集合型別的繼承問題。設Employee IS-A Person。那麼,這是不是意味著陣列Employee[] IS -A Person[]呢?換句話說,如果一個例程接受Person[]作為引數,那麼我們能不能把Employee[]作為引數來傳遞呢?

咋看,該問題不值得一問,似乎Employee[]就應該是和Person[]型別相容的。然而這個問題比想象中要複雜。假設除Employee外,我們還有Studeng IS-A Person,並設Employee[]是和Person[]型別相容的。此時考慮下面兩條賦值語句:

Person[] arr = new Employee["5];//編譯:arrays are compatible

arr[0]= new  Student(...);//編譯: Studeng IS-A Person

兩句都編譯,而arr[0]實際上是引用一個Employee,可是Student IS-NOT-A Employee。這樣就產生了型別混亂。執行時系統(runtime system)不能丟擲ClassCastException異常,因為不存在型別轉換。

 

避免這種問題的最容易的方法是指定這些陣列不是型別相容的。可是,在Java中陣列卻是相容的。這叫做協變陣列型別。每個陣列都明瞭它所允許儲存的物件的型別。

如果將一個不相容的型別插入到陣列中,那麼虛擬機器將丟擲ArrayStoreException異常(陣列儲存異常)。

 

示例程式碼我已經提交到我的Github,有需要可克隆下載。

程式碼地址為:https://github.com/youcong1996/The-Data-structures-and-algorithms/tree/master/Introduction