学习笔记:Java泛型

一、 为什么要使用泛型?
Java核心技术卷中写到:“使用泛型机制编写的程序代码要比那些杂乱的使用Object变量,然后在进行强制类型转换的代码具有更好的安全性和可读性。泛型(Generic)意味着编写的代码可以被很多的不同类型的对象重用”。
比如未使用泛型的ArrayList集合,只是维护一个Object类型数组。在实现中没有类型推断,只能在运行时检测错误,在获取值时必须进行强制类型转换,可能会出现ClassCastExcepyion异常。

@Test
    public void test2(){
        ArrayList a  = new ArrayList();
        a.add(132);
        a.add(456);//没有进行类型检查,可以添加任意Object对象
        a.add("Hello");

        for (Object o :a) {
            String str = (String) o;//进行强制类型转换,编译时不会报错,运行时会出现java.lang.ClassCastException异常
            System.out.println(str);
        }

Java SE 5.0增加了泛型机制,使用泛型的Arrsylist类,有一个类型参数来指示元素的类型。

    @Test
    public void test1(){
        Collection<Integer> c = new ArrayList<Integer>();//类型推断
        c.add(12);
        c.add(22);//能在编译时而不是在运行时检测错误
        c.add(52);
        //c.add("good"); 只能添加Integer类的对象

        for (Integer o :c) { //不用进行强制类型转换,编译器已经知道o的数据类型,且运行时不会报错
            System.out.println(o);
        }
    }

二、 定义泛型类
“一个泛型类就是具有一个或多个类型变量的类”。如何实现?引入一个或多个类型变量,用< >括起来,并放在类名的后面。
作用是什么?类定义的类形变量可以指定类的内部结构的数据类型(比如属性的数据类型,方法的返回类型,方法的形参类型,成员变量的数据类型)。

//开发一个泛型Apple类,要求有一个重量属性weight在测试类中实例化不同的泛型对象,
public class Apple<T> {
    private T weight;//指定属性的数据类型
    public void setWeight(T weight){//指定方法的形参类型
        this.weight = weight;
    }
    public T getWeight(){//指定方法的返回值类型
        return weight;
    }
    //泛型类定义构造器方式和一般类一样
    public Apple(){
    }
    //泛型类定义构造器方式和一般类一样
    public Apple(T weight){
        this.weight = weight;
    }
} 

“用具体的类型替换类型变量就可以实例化泛型类型。”不能用基本数据类型指定类型变量,而要用其对应的包装类。

    @Test
    public void test1(){
        Apple<String> a1 = new Apple<>("500克");
        Apple<Integer> a2 = new Apple<>(500);
        Apple<Double>  a3 = new Apple<>(500.0);
        System.out.println(a1.getWeight());//返回值类型是String类型
        System.out.println(a2.getWeight());//返回值类型是Integer类型
        System.out.println(a3.getWeight());//返回值类型是Double类型
    }
}

三、 怎么定义泛型方法
什么是泛型方法?“带有类型参数的方法”。泛型方法可以定义在普通类中,也可以定义在泛型类中。
类型变量放在权限修饰符的后面,返回值类型的前面。

public class Arrayex {
    public static <E> void  toArray(E[] e, Collection<E> c){//类型变量修饰形参
        for (E ex: e){//类型变量修饰局部变量
            c.add(ex);
        }
    }
}

可以看出,泛型方法定义的类型变量可以定义方法的内部结构(形参,局部变量)以及方法的返回值类型。
调用泛型方法?“调用时,在方法名的尖括号中放入具体的类型,也可以省略,编译器有足够的信息推断出所调用的方法。

    @Test
    public void test2(){
        Collection<String> c1 = new ArrayList<>();
        String[] str1 = new String[]{"C","Python","Java","C++"};
        Arrayex.<String>toArray(str1,c1);//输入第一个参数后,编译器会类型推断,指定第二个参数为容纳String类型的Collection集合
        System.out.println(c1);
    }
}

显然,在泛型类和泛型方法的定义和调用过程中,可以体验到类型变量在类或方法的定义中充当的Object类型,当调用时指定具体传入数据类型,帮助编译器检查传入数据类型、类型推断的作用。

四、类型变量的限定
“在类或方法中有时需要对类型变量加以约束”。对比Object类等类型指定的类或方法的结构,通常继承的子类也能满足代码内部的使用。
“对于类型变量,可以指定为任何一个类的对象,假如内部调用了对象所属类的compareTo方法,怎么才能确信T对象所属类有CompareTo方法呢?解决方案是将T限定为实现了Comparable接口。
<T extends Compareable>
表示T是其实现了Comparable接口或者是绑定类型的子类型。一个类型变量可以有多个限定。限定类型用“&”分隔,用逗号来分隔类型变量。
<T super xxxClass>
表示类型变量指定的范围为xxxClass及其父类。

    //返回数組元素最大值
    public static <E extends Emploee & Comparable>  E max(E[] e){
        if (e == null || e.length == 0) return null;
        E maxplayer = e[0];
        for (int i = 1; i < e.length; i++) {
            if (maxplayer.compareTo(e[i])<0){
                maxplayer = e[i];
            }
        }
        return maxplayer;
    }

在此方法中,类型变量限定为实现Comparable接口并且是Employee及其子类。
创建Person类、Employee类、Mannager类,三个类是多层继承关系,Person重写了compareTo方法对salary属性。

    @Test
    public void test1(){
        Emploee[] emploees = new Emploee[5];
        emploees[0] = new Emploee("1号员工",12,100);
        emploees[1] = new Emploee("2号员工",13,110);
        emploees[2] = new Emploee("3号员工",13,120);
        emploees[3] = new Manager("1号管理",12,100,50);
        emploees[4] = new Manager("2号管理",14,130,50);

        Object o = Emploee.max(emploees);//返回工资最高的人
        System.out.println(o);//名字 2号管理工资是 130   
    }    
}

五、泛型变量与擦除
“无论何时定义一个泛型类,都自动提供一个相应的原始类型,类名就是删除类型参数后的泛型类名”。
擦除类型变量,原始类型会替换为第一个限定类型,如果没有给定就用Object类型来替换。
“当程序调用泛型方法时,如果擦除返回类型,编译器会插入强制类型转换。”
在虚拟机中没有泛型,只有普通的类和方法。所有的类型参数都用它们的限定类型替换。桥方法被合成来保持多态(方法类型变量擦除时,调用来自父类的方法可能会变成另外一个父类方法,不在同名同参数产生重载,与多态冲突)。

六、泛型的约束与局限性
运行时类型查询只适用于原始类型。 使用instanceOf或涉及泛型类型的强制类型转换表达式都会看到一个编译器警告。
不能实例化泛型类参数化类型的数组。
不能实例化泛型类的类型变量。
在静态属性或者静态方法中不能使用泛型类的类型变量。
不能抛出或捕获泛型类的实例。

posted @ 2021-03-04 18:34  夏天的风key  阅读(68)  评论(0编辑  收藏  举报