1. 程式人生 > >java泛型通俗講解

java泛型通俗講解

1.為什麼需要泛型

我們知道java是屬於強型別程式語言。變數在使用之前,需要先進行定義,而定義個變數時必須要指定其資料型別,這樣編譯器在編譯階段就能將很多型別錯誤消滅在萌芽狀態。

如果我們有這樣一個需求:定義一個座標類。但是該座標類的資料型別可能是整數,也可以能是小數,還有可能是字串。舉個例子

x=10; y=15;
x="東經116",y="北緯39"
x=10.01; y=15

座標有可能是整型,也有可能是字串型別,還有可能是double型別。如果沒有泛型,我們可能會這麼做

package edu.bit.test;

public class ObjectType {

    public
static void main(String[] args) { Point p = new Point(); p.setX(10); p.setY(15); //向下轉型,此時沒有問題,程式碼能正常執行 int x = (Integer)p.getX(); int y = (Integer)p.getY(); System.out.println(p); p.setX("東經116"); p.setY("北緯39"); //此時向下轉型會有問題
double x1 = (Double)p.getX(); double y1 = (Double)p.getY(); System.out.println(p); } } class Point { Object x = 0; Object y = 0; public Object getX() { return x; } public void setX(Object x) { this.x = x; } public Object getY
() { return y; } public void setY(Object y) { this.y = y; } @Override public String toString() { return "Point{" + "x=" + x + ", y=" + y + '}'; } }

將程式碼run起來

Point{x=10, y=15}
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Double
    at edu.bit.test.ObjectType.main(ObjectType.java:22)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)

Process finished with exit code 1

在上面的示例程式碼中,用的是Object型別。這樣,在呼叫set方法的時候是沒有問題的。但是在使用get方法時,因為需要將Object類向下轉型成其他類,這樣容易帶來問題。並且,這種問題在jdk編譯階段還不容易被發現,往往只有在執行的時候才會丟擲異常!像我們上面的程式碼,在IDE中是不會報錯的,但是程式碼run起來以後,會丟擲一個ClassCastException的異常!

2.泛型的使用方式

將上面的程式碼改用泛型的方式實現

package edu.bit.test;

public class GenericType {

    public static void main(String[] args) {
        Point1<Integer,Integer> p1 = new Point1<Integer, Integer>();
        p1.setX(10);
        p1.setY(15);
        System.out.println(p1);

        Point1<String,String> p2 = new Point1<String,String>();
        p2.setX("東經116");
        p2.setY("北緯39");
        System.out.println(p2);
    }

}

class Point1<T1,T2> {
    T1 x;
    T2 y;

    public T1 getX() {
        return x;
    }

    public void setX(T1 x) {
        this.x = x;
    }

    public T2 getY() {
        return y;
    }

    public void setY(T2 y) {
        this.y = y;
    }

    @Override
    public String toString() {
        return "Point1{" +
                "x=" + x +
                ", y=" + y +
                '}';
    }
}

將程式碼run起來

Point1{x=10, y=15}
Point1{x=東經116, y=北緯39}

在上面的例子裡,直接將Point1類定義為泛型類。泛型類的定義方法為類名後面用尖括號將

3.限制泛型的使用型別

在我們上面的程式碼中,沒有對引數型別做任何限制,可以使用任何型別的引數。然後在很多實際場景中,還是需要對引數型別做一定限制的,只能傳遞部分引數型別,傳遞其他型別則會引發錯誤。例如在前面的Point類中,我們希望使用者只能傳遞數字型別,而不能傳遞字串等其他型別:

package edu.bit.test;

public class GenericSmallType {

    public static void main(String[] args) {
        Point2<Integer,Integer> p1 = new Point2<Integer,Integer>();
        p1.setX(10);
        p1.setY(15);
        System.out.println(p1);

        Point2<Double,Double> p2 = new Point2<Double,Double>();
        p2.setX(10.0);
        p2.setY(15.01);
        System.out.println(p2);
    }
}

class Point2<T1 extends Number,T2 extends Number> {
    public T1 x;
    public T2 y;

    public T1 getX() {
        return x;
    }

    public void setX(T1 x) {
        this.x = x;
    }

    public T2 getY() {
        return y;
    }

    public void setY(T2 y) {
        this.y = y;
    }

    @Override
    public String toString() {
        return "Point2{" +
                "x=" + x +
                ", y=" + y +
                '}';
    }
}

將程式碼run起來:

Point2{x=10, y=15}
Point2{x=10.0, y=15.01}

4.型別擦除(type erasure)

java位元組碼中不包含有泛型的型別資訊。編譯器在編譯階段去掉泛型資訊,在執行的時候加上型別資訊,這個過程被稱為型別擦除。
我們在使用泛型的過程中,如果沒有指定資料型別,那麼將會擦除泛型型別。

package edu.bit.test;

public class TypeErasure {

    public static void main(String[] args) {
        Point3 p1 = new Point3();

        p1.setX("東經116");
        p1.setY("北緯39");

        //向下轉型
        String longitude = (String)p1.getX();
        String latitude = (String)p1.getY();

        System.out.println(p1);
    }
}

class Point3<T1,T2> {
    public T1 x;
    public T2 y;

    public T1 getX() {
        return x;
    }

    public void setX(T1 x) {
        this.x = x;
    }

    public T2 getY() {
        return y;
    }

    public void setY(T2 y) {
        this.y = y;
    }

    @Override
    public String toString() {
        return "Point3{" +
                "x=" + x +
                ", y=" + y +
                '}';
    }
}

本例中,建立Point3物件的時候沒有指定資料型別。編譯器在處理的時候,會將所有資料向上變為Object型別。然而這樣處理完以後,使用get方法的時候,又需要向下轉型。這樣的話,跟第一部分沒有使用泛型就一樣了!