2013年9月21日 星期六

泛型(Generics)

泛型
Image(28)[4]

泛型(generics)
  • 撰寫程式時,往往有一種情況是,我們無法再編譯期間確定程式碼的撰寫方式,也因此衍生出許多技巧,來克服這個問題,比方說利用動態記憶體配置,來解決無法在編譯期間確定使用者輸入資料量的問題,或者利用晚期鏈結(late binding)的技巧,使得呼叫的方法可以依照執行期間的狀況而決定。 
  • 泛型是物件導向的三個特性之一的多型(polymorphism)的一種技巧,多型的本意是「一種介面,多種使用方法」,然而在Java中多型通常指處理未知的資料型態,對不同的型別都給予相同的操作方式,也就是一種函式(function),多種方法,或者一種指令,多種方法。
  • 而在Java中,泛型則是針對特定指定型別產生對應型態的程式碼以處理不同型別,泛型將型別抽象化、參數化,型別參數化意味著我們可以更彈性的決定類別、介面與方法的資料型別,將主要的核心演算法與邏輯獨立出來,不再牽制於資料型別的限制,比方說,堆疊結構需要Pop()、Push()等方法,但堆入的元素型別可能是String、double、float...等,與其為每個型別都撰寫一種堆疊類別來處理來實現多型,不如用另外一種方式:泛型。泛型可以讓類別的型別參數化,也就是說我們可以定義完堆疊後,再告訴編譯器我們的堆疊會使用甚麼樣的資料型別。如此一來同樣達成了「一種介面,多種方法」的技巧,省下的重複撰寫的成本。
  • 在Java中,多型是針對函式的,而泛型是針對參數的。
  • 泛型可以讓你流暢的建立、型別安全,與可重複使用的程式碼。

簡單的範例
  • 接著我們舉例一個簡單的泛型示例:
    Image(29)[6]
  • Image(30)[8]
  • 輸出結果:
    Image(31)[6]

泛型的型別取決於型別引數
  • 一個版本的泛型型別參考與另一個版本的泛型型別參考,在型別上市不相容的,例如上述的程式中:
    iOb=strOb;  //錯誤

泛型如何提升型別安全
  • 或許你會發現,只要指定Object作為資料型別,再加上適當的轉型即可實現泛型機制,但泛型可以自動地保護型別安全,在撰寫程式中,你不需要輸入型別轉型的程式碼,也不須手動檢查程式中的型別轉換是否正確。
  • 舉例以Object來取代泛型
    Image(32)[5]
  • Image(33)[5]
  • 此程式相較於泛型有兩個缺點
    • 一、你必須明確使用強制轉型取得儲存在物件的資料
    • 二、許多型別不符的錯誤都是在執行時期才發現,使用Object建立泛型機制無法確保型別安全,泛型將執行期間的錯誤變成編譯期間的錯誤,這使得我們比較容易避免潛在問題

抹除
  • 泛型新增至Java的主要限制在於必須與舊版本相容,任何語言語法的變更,或是JVM的修改,都必須避免破壞到舊的程式碼,Java透過抹除(erasure)方式以符合這個限制,藉以實作泛型
  • 當編輯你的Java原始碼後,所有的泛型型別資訊會被抹除:Java會以限制條件的型別(如果沒有明確指定限制條件,則預設是Object)取代型別參數,然後再以適當的轉型動作(由型別引數決定),以確保能與型別引述指定的型別相容。編譯器會堅持型別一定要相容。
  • 泛型這種處理方式代表在執行時期型別參數是不存在的,他們只存在於原始碼。執行時期所有的泛型型別只以原始型別的形式存在
  • 舉例來說,以下兩段程式編譯的結果會是相同的:
    • Gen<Integer>iOb=new Gen<Integer>(99);
      int x=iOb.getob();
    • Gen iOb=new Gen(99);
      in tx=(Integer)iOb.getob();

橋梁方法
  • 有時候,子類別中的覆寫方法,其型別抹除的動作部會發生在父類別中的相同方法上,此時編譯器需要新增一個橋梁方法(bridge method)至類別中,以便處理這樣的情形
  • 橋梁方法只發生在位元碼的層級,我們看不到,也無法使用此方法

意義不明的錯誤
  • 新增泛型同時也產生了新型的錯誤:意義不明(ambiguity)的錯誤。
  • 抹除動作導致的問題是,當朗各表面上是不同的泛型宣告,進行抹除後卻解析成相同的型別,此時就會產生意義不明的錯誤並導致衝突:
    Image(34)[5]
  • 此範例的其中一個錯誤提示是:
    • Method set(V) has the same erasure set(Object) as another method in type MyGenClass<T,V>
    • 大意是說:在MyGenClass<T,V>類別中,set(V)方法在抹除(erasure)後,會成為set(Object),而使得它與另一個方法(此指set(T)相同,導致意義不明的錯誤(類別中不能有兩個相同的方法)
  • 解決的方法可以將類別定義改成class MyGenClass<T,V extends String>{//...
    • 不過要注意的是當你使用這樣的敘述:
      MyGenClass<String,String> x = new MyGenClass<String,String>();
    • 同樣會產生意義不明的錯誤
  • 意義不明的錯誤通常代表你的設計概念上有錯誤,要留意處理方式

泛型的限制
  • 泛型只運用在物件上:當宣告一個泛型類別的實體時,傳入的型別參數必須是類別型別,你不能使用基本型別,像是int或char:
    Gen<int> strOb=new Gen<int>(53); //錯誤,不能使用基本型別
  • 型別參數不能實體化:
    Image(35)
    錯誤提示為:Cannot instantiate the type T
    無法將型別T實體化,在執行時期T會被其他型別取代(型別參數會被抹除),而編譯時期T也只是佔位符號而已,編譯器無法知道該建立哪種型別的物件
  • 靜態成員的限制:
    Image(36)
    錯誤提示為依序為:
    Cannot make a static reference to the non-static type T
    Cannot make a static reference to the non-static type T
    ob cannot be resolved to a variable
    ob cannot be resolved to a variable
    意思是說非靜態型別的T是無法產生靜態成員參考的
    而沒辦法產生參考的ob也不能被解析成變數提供程式其他地方存取
    雖然你不可以使用有型別參數的static成員,但是可以宣告static泛型方法
  • 泛型陣列的限制
    Image(37)
    錯誤提示為:
    Cannot create a generic array of T
    Cannot create a generic array of GenericsArray<Integer>
    一、宣告型別T的陣列是合法的,但無法實體化泛型陣列,原因是編譯器並沒辦法知道實際上要建立陣列的物件型別為何,你可以用參考指派的方式(第9行)來將物件指派給泛型陣列,因為傳入Gen的陣列是已知型別的,T會與建立陣列時的型別相同。
    二、指定泛型型別的陣列是不被允許的(第14行),因為他們可能破壞型別安全,然而,如果你使用萬用字元,就可以建立泛型型別的陣列參考(第15行),者種方式比使用原始型別的陣列要來得好,因為至少它會執行一些型別檢查
  • 泛型例外的限制
    泛型的類別不能繼承Throwable。這意味著你不能建立泛型的例外類別。

延伸探討
  • 本文希望解釋泛型的核心價值,其餘的探討讀者若有興趣可以參考其他書籍或文章,以下延伸的主題僅做簡單的摘要,往後有必要可能在做章節補充,一般包括
    • 使用兩個型別參數的泛型類別:class class-name <T,V>{//...
      • 泛型類別的一般形
      • class class-name <type-param-list>{//...
      • class class-name<type-arg-list> var-name=new class-name<type-arg-list>(cons-arg-list);
    • 受限的型別:<T extends superclass>
    • 萬用字元引數與受限的萬用字元引數
      • 上限(extend)
      • 下限(super):<? super subclass>,下限條件的類別(subclass)不包含在限制條件內
    • 泛型方法:<type-param-list> return-type method-name((param-list){//...
    • 泛型建構式
    • 泛型介面:
      • 定義:interface interface-name<type-param-list>{//...
      • 實作:class class-name<type-param-list> implements interface-name<type-arg-list>{//...
    • 原始型別與舊的程式碼:泛型是新的功能,因此Java需要一些轉變的方式將舊的、泛型之前的程式碼轉換過來。
    • 泛型類別階層體系
      • 繼承泛型父類別
      • 執行時期犯行階層內的型別比較
    • 轉型:(Gen<Integer>)iOb
    • 覆寫泛型類別中的方法



2013年9月6日 星期五

Microsoft Access與MySQL的存取


Image(15)


  • 這篇文章提供你完成以下操作的方法
    • 使用Micosoft Access編輯、新增、刪除MySQL資料庫的資料
    • 將Access 資料庫匯入至MySQL資料庫中

使用MyODBC
  • 系統環境:Windows 7 x64、MySQL database、Microsoft Access Database
  • 需求: MyODBC Driver:使用版本ODBC 5.2.5 winx64(下載鏈結)

使用MyODBC轉換資料
設定 ODBC 資料來源:
  • 設定資料來源,進入控制台\系統及安全性\系統管理工具,然後點選「資料來源 (ODBC)」
    Image(17)
  • 再來點選「系統資料來源名稱」,並點「新增」,會跳出所有 Driver 的視窗,選擇 MySQL ODBC 5.2 Unicode Driver,然後點選完成
    Image(18)
  • 點選完成後,會跳出設定資料來源組態的視窗。
    Image(19)
    Data Source Name:設定你的資料來源名稱,例如access2mysql。
    Description:對這個資料來源的描述與說明
    TCP/IP Server:可用localhost,或者你的遠端伺服器IP。
    Database Name:要連結的資料庫名稱,例如access2mysqldb。
    User:使用者名稱,例如root。
    Password:資料庫密碼,預設空白,或者輸入你先前設定的資料庫密碼。
    Port:連線使用的 port 號為「3306」。
    Test:此按鈕用來測試是否連線成功
  • 輸入完資料後,點選ok,資料來源會新增你剛剛輸入的項目
    Image(20)
  • 如果你剛輸入的資料來源設定中,還沒有指定的資料庫,那就必須建立一個,可用以下指令
    CREATE DATABASE access2mysqldb;
  • 其中access2mysqldb為你指定的資料庫名稱

匯入資料檔:
  • 使用Miscrosoft Access開啟要匯入的資料檔
    Image(21)
  • 在要匯出的資料表上按右鍵選擇匯出>ODBC資料庫
    Image(22)
  • 輸入名稱
    Image(23)
  • 跳出選擇資料來源的視窗後,按下分頁"機器資料來源",選擇剛剛建立的資料庫(示例為Access2MySQL),按下確定
    Image(24)
  • 之後利用phpMyAdmin確定你的資料是否成功建立:
    Image(25)
  • 或者使用MySQL命令
    Image(26)

2013年9月5日 星期四

資料表的正規化(Normalization)

Image[3]



正規化是為了使資料儲存與建立後,有一個統一的格式(便於設計)、利於日後使用(查詢、修改等用途)的資料表。以下藉由實作說明一個資料表如何正規劃:
訂單資料
  • 起初我們拿到客戶訂單,他的基本形式差不多是這樣:
Image(1)
第一階段正規化(First Normal Form)
  • 未正規化前,商品編號、商品名稱、商品數量欄位中有一筆以上的資料,使得我們沒辦法確定欄位要預留多少空間儲存資料。所以我們第一步是將這些欄位限制只能儲存一筆項目資料,將訂單中一個以上的項目分成數筆紀錄儲存:
    Image(2)
  • 而原本的六筆訂單資料,會成為16筆資料紀錄
  • 此時,我們去定義資料表的主要關鍵欄位,可能的方式是用訂單編號、客戶編號、商品編號三個欄位做為主要關鍵欄位,才能識別每筆資料的唯一性,找到正確的資料。
    Image(3)
  • 如上圖所示,要找到第四筆資料,我們需要T002+C003+A002

第二階段正規化
  • 我們發現資料表中有很多重複的部分,而且又具有相依性(例如客戶張曉風與客戶編號C001具有相同的意義),若表中部份欄位彼此相依,會使得管理不易,例如我們想要刪除客戶編號C001,可是卻發現,必須連客戶名稱欄位中的張曉風一併刪除,這種部份相依性,使得資料表中某欄位的修改或刪除可能影響到其他欄位。
  • 而像商品名稱這樣的欄位,他相依於商品編號,或者是客戶名稱相依於客戶編號,他們都只相依於主關鍵欄位的部分欄位,我們稱之部分相依
  • 我們希望,主要的資料表中的各個欄位要完全相依於主要關鍵,這樣才便於查詢、刪減。因此第二階段正規化就是要將部分相依的欄位獨立出來,成為另一個資料表
  • 可能的作法是將原資料表分割成訂單資料表與訂單交易明細資料表
    Image(4)Image(5)
  • 接著去除重複的部分
    Image(6)Image(7)
  • 而兩張資料表需要有相關性,因此我們新增一個訂單編號欄位在B2訂單交易資料表
    Image(8)
  • 此使我們便可以使用訂單編號欄位作為B3訂單資料表的主要關鍵欄位,而其餘的欄位都完全相依於主要關鍵
    Image(9)
  • 而B4訂單交易明細資料表仍然有部分相依的問題,我們把部分欄位獨立出來成為商品資料表:
    Image(10)Image(11)
  • 接著刪去重複的部分:
    Image(12)
    商品資料表中可以看出,只要確認商品編號,就可以確定該商品的基本資料,其主要關鍵便是商品編號。其餘欄位皆相依於主要關鍵。

第三階段正規化
  • 資料分割後,資料的關聯性與高效率已見雛型,不過還是存在一些問題:譬如訂單資料表中雖然訂單編號資料欄未是具有唯一性的主要關鍵,會影響其餘的資料欄位,但這些欄位中的客戶編號欄位又會影響客戶名稱欄位,造成了某些欄位除了依循著主要關鍵外,又依循了其他資料欄位的影響,造成了所謂的轉移性的相依(Transitive Dependency),第三階段正規化就是要解決這方面的問題。
  • 讓所有資料欄位除了相依主要關鍵欄位,不能再相依其他欄位的值來決定資料的正確性。同樣我們藉由獨立出另一個資料表的方式解決這個問題:
  • Image(13)Image(14)

總結而言
  • 第一階段:必須設定主要關鍵欄位,而欄位中只有一個單一資料值,並沒有重複的資料值
  • 第二階段:必須除去資料的部分相依性
  • 第三階段:必須除去資料的間接相依性