Java學習筆記(二一)——Java 泛型

【前面的話】java

     最近臉好乾,掉皮,須要買點化妝品了。數組

     Java泛型好好學習一下。安全

【定義】學習

1、泛型的定義主要有如下兩種:測試

  1. 在程序編碼中一些包含類型參數的類型,也就是說泛型的參數只能夠表明類,不能表明個別對象。(這是當今較常見的定義)
  2. 在程序編碼中一些包含參數的。其參數能夠表明類或對象等等。(如今人們大多把這稱做模板)

        不論使用那個定義,泛型的參數在真正使用泛型時都必須做出指明。this

2、使用泛型的目的:編碼

  1. 一些強類型程序語言支持泛型,其主要目的是增強類型安全及減小類轉換的次數,但一些支持泛型的程序語言只能達到部份目的。
  2. 泛型程序設計(Generic programming)意味着編寫的代碼能夠被不少不一樣類型的對象所重用。
  3. 是對java語言的類型系統的一種擴展,以支持建立能夠按類型進行參數化的類。能夠把類型參數看做是使用參數化類型時指定的類型的一個佔位符,就像方法的形式參數是運行時傳遞的值得佔位符同樣。

Java泛型的幾種類型代碼】spa

1、不使用泛型的代碼設計

     咱們定義一個Person類,包含三個屬性x,y,z。在咱們開始定義地時候,咱們也不知道這三個屬性是用來幹什麼的,因此咱們定義爲Object類型。可是在使用的時候,咱們分別對x,y,z賦予了int,double,String類型,因此在取出的時候,咱們須要把這三個類型值進行強制轉換。以下代碼:code

    1. Person.java

 1 public class Person {
 2     private Object x;
 3     private Object y;
 4     private Object z;
 5     //使用Object類型。能夠轉化爲任何類型
 6     public Object getX() {
 7         return x;
 8     }
 9     public void setX(Object x) {
10         this.x = x;
11     }
12     public Object getY() {
13         return y;
14     }
15     public void setY(Object y) {
16         this.y = y;
17     }
18     public Object getZ() {
19         return z;
20     }
21     public void setZ(Object z) {
22         this.z = z;
23     }
24 }

    2. NoGenericTest.java

 1 public class NoGenericTest {
 2     public static void main(String[]args){
 3         Person boy=new Person();
 4         boy.setX(20);
 5         boy.setY(22.2);
 6         boy.setZ("帥哥TT");
 7         //這裏根據設置的不一樣類型的值,咱們須要進行強制類型轉化。
 8         int x=(Integer)boy.getX();
 9         double y=(double)boy.getY();
10         String z=(String)boy.getZ();
11         
12         System.out.println(x);
13         System.out.println(y);
14         System.out.println(z);
15     }
16 }

    3. 運行結果

1 20
2 22.2
3 帥哥TT

2、使用一個類型變量泛型的代碼

      咱們定義一個泛型類Person,定義三個屬性x,y,z,在測試類中,咱們設置屬性的值,並打印。

    1. Person.java

 1 public class Person<T> {
 2     private T x;
 3     private T y;
 4     private T z;
 5     public T getX() {
 6         return x;
 7     }
 8     public void setX(T x) {
 9         this.x = x;
10     }
11     public T getY() {
12         return y;
13     }
14     public void setY(T y) {
15         this.y = y;
16     }
17     public T getZ() {
18         return z;
19     }
20     public void setZ(T z) {
21         this.z = z;
22     }
23 }

    2. GenericTest.java

 1 public class GenericTest {
 2     public static void main(String[]args){
 3         Person boy=new Person();
 4         boy.setX(20);
 5         boy.setY(22.2);
 6         boy.setZ("帥哥TT");
 7         //不用進行類型轉化
 8         System.out.println(boy.getX());
 9         System.out.println(boy.getY());
10         System.out.println(boy.getZ());
11     }
12 }

    3. 運行結果

1 20
2 22.2
3 帥哥TT

3、使用兩個類型變量泛型的代碼

     咱們定義一個泛型類Person,定義兩個屬性x,y,使用了兩種不一樣的類型變量,在測試類中,咱們設置屬性的值,並打印。

    1. Person.java

 1 public class Person<T1,T2> {
 2     private T1 x;
 3     private T2 y;
 4     public T1 getX() {
 5         return x;
 6     }
 7     public void setX(T1 x) {
 8         this.x = x;
 9     }
10     public T2 getY() {
11         return y;
12     }
13     public void setY(T2 y) {
14         this.y = y;
15     }
16 }

    2. GenericTest.java

 1 public class GenerricTest {
 2     public static void main(String[] args){
 3         Person<String,Integer> boy=new Person<String,Integer>();
 4         boy.setX("帥哥TT");
 5         boy.setY(20);
 6         System.out.println(boy.getX());
 7         System.out.println(boy.getY());
 8     }
 9 
10 }

    3. 運行結果

1 帥哥TT
2 20

4、使用泛型的繼承

     咱們定義一個泛型類Person,定義兩個屬性x,y,而後定義另外一個泛型類Boy,定義屬性z,Boy繼承Person類,在測試類中,咱們設置屬性的值,並打印。

    1. Person.java

 1 public class Person<T1,T2> {
 2     private T1 x;
 3     private T2 y;
 4     public T1 getX() {
 5         return x;
 6     }
 7     public void setX(T1 x) {
 8         this.x = x;
 9     }
10     public T2 getY() {
11         return y;
12     }
13     public void setY(T2 y) {
14         this.y = y;
15     }
16 }

    2. Boy

1 public class Boy<T1,T2,T3>extends Person<T1,T2> {
2     private T3 z;
3     public T3 getZ() {
4         return z;
5     }
6     public void setZ(T3 z) {
7         this.z = z;
8     }
9 }

    3. GenericTest.java

 1 public class GenericTest {
 2     public static void main(String[] args){
 3         Boy<String,Integer,Double> boy=new Boy<String,Integer,Double>();
 4         boy.setX("帥哥TT");
 5         boy.setY(20);
 6         boy.setZ(200000.22);
 7         
 8         System.out.println(boy.getX());
 9         System.out.println(boy.getY());
10         System.out.println(boy.getZ());
11     }
12 }

   4. 運行結果

1 帥哥TT
2 20
3 200000.22

5、使用泛型的接口

     咱們定義一個泛型接口Person,定義兩個方法,而後定義另外一個泛型類Boy,實現泛型接口Person,定義屬性x,y,z,在測試類中,咱們設置屬性的值,並打印。

    1. Person.java

1 public interface Person<T1,T2> {
2     public T1 getX();
3     public T2 getY();
4 }

    2. Boy

 1 public class Boy<T1,T2,T3>implements Person<T1,T2> {
 2     private T1 x;
 3     private T2 y;
 4     private T3 z;
 5     public T1 getX() {
 6         return x;
 7     }
 8     public void setX(T1 x) {
 9         this.x = x;
10     }
11     public T2 getY() {
12         return y;
13     }
14     public void setY(T2 y) {
15         this.y = y;
16     }
17     public T3 getZ() {
18         return z;
19     }
20     public void setZ(T3 z) {
21         this.z = z;
22     }
23 
24 }

    3. GenericTest.java

 1 public class GenericTest {
 2     public static void main(String[] args){
 3         Boy<String,Integer,Double> boy=new Boy<String,Integer,Double>();
 4         boy.setX("帥哥TT");
 5         boy.setY(20);
 6         boy.setZ(200000.22);
 7         System.out.println(boy.getX());
 8         System.out.println(boy.getY());
 9         System.out.println(boy.getZ());
10     }
11 }

    4. 運行結果

1 帥哥TT
2 20
3 200000.22

6、使用泛型方法

     說明一下,定義泛型方法時,必須在返回值前邊加一個<T>,來聲明這是一個泛型方法,持有一個泛型T,而後才能夠用泛型T做爲方法的返回值。

      定義一個普通類Person,定義一個泛型方法,以下代碼:

     1. Person.java

 1 public class Person{
 2     public static<T>T getMiddle(T[]a){
 3         return a[a.length/2];
 4     }
 5     public static void main(String [] args){
 6         String[]name={"帥哥TT","帥哥TT1","帥哥TT2"};
 7         String middle=Person.<String>getMiddle(name);
 8         System.out.println(middle);
 9         
10         Integer[]num={20,22,25};
11         Integer middle1=Person.<Integer>getMiddle(num);
12         System.out.println(middle1);
13         
14         Double[]num1={20.0,22.2,25.5};
15         Double middle2=Person.<Double>getMiddle(num1);
16         System.out.println(middle2);
17     }
18 }

    2. 運行結果

1 帥哥TT1
2 22
3 22.2

7、類型變量的限定

     以下代碼,咱們在方法min中定義了一個變量smallest類型爲T,這說明了smallest能夠是任何一個類的對象,咱們在下面的代碼中須要使用compareTo方法, 可是咱們沒有辦法肯定咱們的T中含有CompareTo方法,因此咱們須要對T進行限定,在代碼中咱們讓T繼承Comparable類。以下:

1 public static<T extends Comparable>T min(T[]a)

    1. Person.java

 1 public class Person{
 2     public static<T extends Comparable>T min(T[]a){
 3         if(a==null||a.length==0){
 4             return null;
 5         }
 6         T smallest=a[0];
 7         for(int i=1;i<a.length;i++){
 8             if(smallest.compareTo(a[i])>0){
 9                 smallest=a[i];
10             }
11         }
12         return smallest;
13     }
14     public static void main(String [] args){
15         Integer[]num={20,25,30,10};
16         Integer middle=Person.<Integer>min(num);
17         System.out.println(middle);
18     }
19 }

    2. 運行結果

1 10

Java泛型理解】

1、類型擦除

     正確理解泛型概念的首要前提是理解類型擦除(type erasure)。 Java中的泛型基本上都是在編譯器這個層次來實現的。在生成的Java字節代碼中是不包含泛型中的類型信息的。使用泛型的時候加上的類型參數,會被編譯器在編譯的時候去掉。這個過程就稱爲類型擦除。如在代碼中定義的List<Object>和List<String>等類型,在編譯以後都會變成List。JVM看到的只是List,而由泛型附加的類型信息對JVM來講是不可見的。Java編譯器會在編譯時儘量的發現可能出錯的地方,可是仍然沒法避免在運行時刻出現類型轉換異常的狀況。

     不少泛型的奇怪特性都與這個類型擦除的存在有關,包括:

  1. 泛型類並無本身獨有的Class類對象。好比並不存在List<String>.class或是List<Integer>.class,而只有List.class。
  2. 靜態變量是被泛型類的全部實例所共享的。對於聲明爲MyClass<T>的類,訪問其中的靜態變量的方法仍然是 MyClass.myStaticVar。不論是經過new MyClass<String>仍是new MyClass<Integer>建立的對象,都是共享一個靜態變量。
  3. 泛型的類型參數不能用在Java異常處理的catch語句中。由於異常處理是由JVM在運行時刻來進行的。因爲類型信息被擦除,JVM是沒法區分兩個異常類型MyException<String>和MyException<Integer>的。對於JVM來講,它們都是 MyException類型的。也就沒法執行與異常對應的catch語句。

2、最佳實踐

在使用泛型的時候能夠遵循一些基本的原則,從而避免一些常見的問題。

  1. 在代碼中避免泛型類和原始類型的混用。好比List<String>和List不該該共同使用。這樣會產生一些編譯器警告和潛在的運行時異常。當須要利用JDK 5以前開發的遺留代碼,而不得不這麼作時,也儘量的隔離相關的代碼。
  2. 在使用帶通配符的泛型類的時候,須要明確通配符所表明的一組類型的概念。因爲具體的類型是未知的,不少操做是不容許的。
  3. 泛型類最好不要同數組一塊使用。你只能建立new List<?>[10]這樣的數組,沒法建立new List<String>[10]這樣的。這限制了數組的使用能力,並且會帶來不少費解的問題。所以,當須要相似數組的功能時候,使用集合類便可。
  4. 不要忽視編譯器給出的警告信息。

【參考資料】

  1. Java深度歷險(五)——Java泛型
  2. java核心技術

【後面的話】

     好好學習。

——TT

相關文章
相關標籤/搜索