Java泛型详解

    |     2015年3月31日   |   Java面向对象高级特性   |     0 条评论   |    1755

所谓泛型,就是“宽泛的数据类型”:不但值可以通过参数传递,类型也可以。它解决的是:既不想为每种类型写一遍重载,又不愿依赖不安全的向下转型。

Java 1.5 起引入泛型,参照 C++ 模板,本质是参数化类型(Parameterized Type)。类型参数只能表示引用类型;传入 int、double 之所以能过,是因为会自动装箱成包装类。

一、装箱、拆箱与对象类型转换

定义变量必须先声明类型。假如坐标的 x、y 可能是整数、小数或字符串:

x = 10、y = 10

x = 12.88、y = 129.65

x = "东经180度"、y = "北纬210度"

除了方法重载,还可以走自动装箱再向上转型:int 装箱成 Integer,再升成 Object。任何类的实例都能升成 Object:

int --> Integer -->Object

    int a = 10;

    Integer a1 = a; //自动装箱     class  Teacher extends Person{}

    Object a2 = a1; //向上转型      Teacher t = new Teacher();

                                                Person p = t; //向上转型

double -->Double -->Object

String -->Object

于是一个方法就能接所有类型。用 Object 存坐标:

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;

   }

}



public class Demo {

    public static void main(String[] args){

        Point p = new Point();

        p.setX(10);  // int -> Integer ->Object

        p.setY(20);

        int x = (Integer)p.getX();  // 必须向下转型

        int y = (Integer)p.getY();

        System.out.println("This point is:" + x + ", " + y);



        p.setX(25.4);  // double -> Integer ->Object

        p.setY("东京180度");

        double m = (Double)p.getX();  // 必须向下转型

        double n = (Double)p.getY();  // 运行期间抛出异常

       System.out.println("This point is:" + m + ", " + n);

   }

}

写入时没问题,取出必须向下转型。向下转型有风险,编译期看不出来,运行期才抛 ClassCastException。上面第二组 getY() 实际是字符串,却强转成 Double,运行就会炸。

更好的办法是泛型类:任意类型都能接,又把转型风险压到最低。

二、泛型类

// 定义泛型类

class Point<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;

   }

}



public class Demo {

    public static void main(String[] args){

        // 实例化泛型类

       Point<Integer, Integer> p1 = new Point<Integer, Integer>();

       p1.setX(10);

       p1.setY(20);

        int x = p1.getX();

        int y = p1.getY();

       System.out.println("This point is:" + x + ", " + y);



       Point<Double, String> p2 = new Point<Double, String>();

       p2.setX(25.4);

       p2.setY("东京180度");

        double m = p2.getX();

        String n = p2.getY();

       System.out.println("This point is:" + m + ", " + n);

    }

}

运行结果:

This point is:10, 20

This point is:25.4, 东京180度

类名后面的 <T1, T2> 是类型参数,占位符,运行时换成真正的类型。传值参数用小括号,类型参数用尖括号,多个用逗号分隔。习惯用单个大写字母:K 键、V 值、E 异常或元素、T 一般类型。

泛型类实例化

实例化必须指出具体类型(向类型参数传值):

className variable<dataType1, dataType2> = new className<dataType1, dataType2>();

等号右边的类型可以省略,但会有警告:

className variable<dataType1, dataType2> = new className();

指明类型后,赋错类型会直接报错,既不必向下转型,也没有潜在的 ClassCast 风险,比一上来就装箱升 Object 实用。

写法 取出时 风险
Object + 装箱/向上转型 必须向下转型 运行期 ClassCastException
泛型类 Point 编译器已记住类型 赋错类型编译失败
泛型但未传类型(擦除) 仍要向下转型 退回 Object 方案

三、泛型方法

也可以只给方法加类型参数。打印坐标的泛型方法:

// 定义泛型类

class Point<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;

    }



    // 定义泛型方法

    public <T1, T2> void printPoint(T1 x, T2 y){

        T1 m = x;

        T2 n = y;

       System.out.println("This point is:" + m + ", " + n);

    }

}



public class Demo {

    public static void main(String[] args){

        // 实例化泛型类

       Point<Integer, Integer> p1 = new Point<Integer, Integer>();

       p1.setX(10);

       p1.setY(20);

       p1.printPoint(p1.getX(), p1.getY());



       Point<Double, String> p2 = new Point<Double, String>();

       p2.setX(25.4);

       p2.setY("东京180度");

       p2.printPoint(p2.getX(), p2.getY());

    }

}

运行结果:

This point is:10, 20

This point is:25.4, 东京180度

printPoint() 的类型参数放在修饰符后面、返回值前面,可在参数列表、方法体、返回类型里使用。调用时不必手写类型,编译器根据实参推断。调用方式和普通方法一样。

泛型方法与泛型类没有必然联系:普通类里也能定义泛型方法。方法里的 T1、T2 和类上的 T1、T2 不是一回事,可换成别的标识:

public static <V1, V2> void printPoint(V1 x, V2 y){

    V1 m = x;

    V2 n = y;

   System.out.println("This point is:" + m + ", " + n);

}

四、泛型接口

接口同样可以带类型参数。实现类跟上相同的 T,构造方法也可以是泛型的:

//定义泛型接口

interface Info<T>{

    public T getVar();

}

//实现接口

class InfoImp<T> implements Info<T> {

    private T var;

    // 定义泛型构造方法

    public InfoImp(T var) {

       this.setVar(var);

    }

    public void setVar(T var) {

       this.var = var;

    }

    public T getVar() {

        return this.var;

    }

}

public class Demo {

    public static void main(String arsg[]) {

       Info<String> obj = new InfoImp<String>("www.weixueyuan.net");

       System.out.println("Length Of String: " + obj.getVar().length());

    }

}



运行结果:

Length Of String: 18

示例站点仍用原文 www.weixueyuan.net,输出字符串长度 18。

五、类型擦除

使用泛型时如果不指明数据类型,编译器会擦除泛型,全部按 Object 处理,取出仍要向下转型——和不用泛型几乎一样:

class Point<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;

    }

}

public class Demo {

    public static void main(String[] args){

        Point p = new Point();  // 类型擦除

        p.setX(10);

        p.setY(20.8);

        int x = (Integer)p.getX();  // 向下转型

        double y = (Double)p.getY();

       System.out.println("This point is:" + x + ", " + y);

    }

}



运行结果:

This point is:10, 20.8

六、限制泛型的可用类型

很多时候只允许一部分类型。例如取数组最大值时要用 doubleValue(),那是 Number 的方法,不是所有类都有:

public <T> T getMax(T array[]){

    T max = null;

    for(T element : array){

        max = element.doubleValue() > max.doubleValue() ? element : max;

    }

    return max;

}

用 extends 限制 T 只能是 Number 及其子类(Integer、Double 等),传入其它类型会报错:

public <T extends Number> T getMax(T array[]){

    T max = null;

    for(T element : array){

        max = element.doubleValue() > max.doubleValue() ? element : max;

    }

    return max;

}

这里的 extends 不是“继承”那个意思,应读成:T 是 Number 的子类型,或 T 实现了某接口。日常业务里泛型用得不多,框架和类库里才密集出现;先建立印象即可。

一句话总结:泛型把类型也变成参数:编译期记住 T,避免盲目向下转型;不写类型就等于擦回 Object。

转载请注明来源:Java泛型详解
本文链接地址:https://ai.zhousir.top/?p=112
回复 取消