28-泛型--概述+擦除&补偿+在集合中的应用+泛型类+泛型方法+泛型接口

一、泛型概述

1、泛型:对要操作的数据类型进行指定。是JDK1.5出现的安全机制。泛型是给编译器使用的技术,用在编译时期,提高了编译的安全性(确保类型安全)

2、向集合中添加元素,public boolean add(E e); 任何类型都可以接收(添加的元素被提升为Object类型)。通常,会将元素取出,对元素进行特有的操作。为了避免后期出现安全隐患,在定义容器时,就需要明确容器中存储元素的类型。而迭代器是从集合获取到的,集合明确了其中的元素类型,迭代器取出的元素类型就是明确的,就没有必要再做强转动作了(编译时会检测类型)

注:迭代器的泛型和获取迭代器对象集合的泛型一致

        //定义容器时,明确容器中存储元素的类型
        ArrayList<String> list = new ArrayList<String>();
        //添加元素时,类型匹配的才可以存入,否则编译报错
        list.add("abc");
//        list.add(true);   //类型不匹配,编译报错

        //迭代器取出的元素类型明确
        for (Iterator<String> it = list.iterator(); it.hasNext();){
            //不用再做强转动作
            String value = it.next();
            System.out.println(value);
        }

3、泛型的好处

(1)将运行时期的问题ClassCastException转到了编译时期

(2)避免了强制转换的麻烦

4、何时使用<> ?

      当操作的引用数据类型不确定的时候,就使用<>,将要操作的引用数据类型传入即可。<>是一个用于接收具体引用数据类型的参数范围。在程序中,只要用到了带有<>的类或者接口,就要明确传入的具体引用数据类型

注:<>中接收的都是引用数据类型(类/接口),要么是类名,要么是接口名。只要出现<>,就要向<>中传入类型。泛型中不能传基本数据类型

二、泛型的擦除&补偿

1、泛型的擦除:运行时,会将泛型去掉。即 生成的 .class 文件是不带泛型的

2、运行时为何要擦除泛型?

      为了兼容运行时的类加载器。运行时,JVM会去启动类加载器。但以前的类加载器没有使用泛型,如果在类文件中加入泛型,以前的类加载器需要升级。希望以前的类加载器还可以使用,且编译时已经对类型进行了检查,类型已经统一,是安全的,运行时就可以把泛型去掉了。所以,只在编译器中加入了泛型机制

注:类加载器:专门用于读取类文件、解析类文件,并将类文件加载进内存的程序

3、泛型的补偿:在运行时,通过获取元素的类型进行转换动作,使用者无需再进行强制转换

4、编译器只做检查,之后<>中的类型被擦除,add()中传入的类型还是Object。取出时,为了避免强转时出现问题,在类加载器的基础上编写了一个补偿程序。先获取指定元素的类型(通过getClass()方法获取对象所属的类文件,就是<>中被擦除的类型),再对其进行类型转换

三、泛型在集合中的应用

1、API中,只要类或接口后有<>的,使用时都要在后面用<>明确元素类型

2、在定义容器时,就要明确容器中要存储的元素类型

/**
 * 实现Comparable<Person>接口,使用泛型,该Comparable接口中只能传入Person类型的对象
 */
public class Person implements Comparable<Person> {

    private int age;
    private String name;

    //......此处省略构造函数和get()、set()方法

    /**
     * 覆盖Comparable<T>接口中的compareTo(T o)方法,参数类型是Person.因为 implements Comparable<Person> 限制了类型的使用
     *
     * @param p
     * @return
     */
    @Override
    public int compareTo(Person p) {
        //不用再做强转动作,因为只有匹配的类型才能传进来
        int temp = this.age - p.age;
        return temp == 0 ? this.name.compareTo(p.name) : temp;
    }

    /**
     * 覆盖Object类中的equals(Object obj)方法,参数是Object类型(固定类型),不能修改。因为父类中没有定义过泛型
     * @param obj
     * @return
     */
    @Override
    public boolean equals(Object obj){
        //......
    }

}

3、泛型在集合框架中应用的最多

4、泛型中,当引用数据类型不确定时,用泛型表示。泛型中不能传基本数据类型

5、一般情况下,写泛型要具备的特点是:左右两边的泛型要一致

四、泛型类

1、JDK1.5后,使用泛型来接收类中要操作的引用数据类型 -- 泛型类(自定义泛型类)

2、何时使用?

      当类中操作的引用数据类型不确定时,就使用泛型来表示

3、没泛型用Object,但泛型这种替代方式比Object安全得多,只是书写比Object麻烦一点,要写<>

/**
 * 存入时,用Object类型接收(向上转型),什么类型都可以存入 -- 提高扩展性
 * 取出时,先强转再使用,注意报错:ClassCastException。所以,取出时需要做健壮性判断
 */
public class Tool {
    private Object object;

    public Object getObject() {
        return object;
    }

    public void setObject(Object object) {
        this.object = object;
    }
}

/**
 * 在JDK1.5后,使用泛型来接收类中要操作的引用数据类型 -- 泛型类
 * 何时使用?
 * 当类中操作的引用数据类型不确定时,就使用泛型来表示
 */
public class Tool<QQ> {
    private QQ q;

    public QQ getObject() {
        return q;
    }

    public void setObject(QQ object) {
        this.q = object;
    }
}

注:泛型类取出时不用做强转动作,进一步验证了:(1)泛型将运行时期的问题转移到了编译时期(2)避免了强转的麻烦

五、泛型方法

1、在不明确类型的情况下,Object其实是一种具体类型。只不过它是所有类的父类,导致它可以有多态方式来接收

2、如果不明确类型,用户往里传什么类型,就操作什么类型。这时可以定义泛型,叫做把泛型定义在方法上

eg:public <W> void show(W xxx){ ... }

public class Tool<QQ> {
    private QQ q;

    public QQ getObject() {
        return q;
    }

    public void setObject(QQ object) {
        this.q = object;
    }

    /**
     * 工具类中指定什么类型(Tool<QQ>),方法中就操作什么类型(QQ) -- show()方法的参数的泛型跟着对象走
     * 方法的参数用的也是泛型,使用的是类上定义的泛型
     */
    public void show(QQ object) {
        System.out.println("show: " + object);
    }

    /**
     * 将泛型定义在方法上:自动找类型匹配,什么都能传入 -- 泛型是在方法上自定义的
     * <W>是定义参数,W是使用参数
     */
    public <W> void show1(W obj) {
        System.out.println("泛型:show: " + obj);  //等同与obj.toString()方法
    }

}

3、当方法静态时,不能访问类上定义的泛型。如果静态方法需要使用泛型,只能将泛型定义在方法上

注:自定义的泛型一定要写在返回值类型前,修饰符后

public class Tool<QQ> {
    private QQ q;

    public QQ getObject() {
        return q;
    }

    public void setObject(QQ object) {
        this.q = object;
    }

    /**
     * 静态是不需要对象的,而泛型得需要对象来明确
     * 当方法静态时,不能访问类上定义的泛型。如果静态方法需要使用泛型,只能将泛型定义在方法上
     */
//    public static void method(QQ xxx) {
//        System.out.println("method: " + xxx);
//    }
    public static <W> void method(W xxx) {
        System.out.println("method: " + xxx);
    }

}

4、一旦使用了泛型,变量的类型不确定,就不能使用具体对象的方法。Object中的方法可以使用,因为无论传入什么类型,都是Object的子类对象,都具备着Object中的方法(Object中的方法是所有对象的通用方法)

    public void print(QQ str) {
//        System.out.println("print: " + str.length()); //报错。使用了泛型,变量str的类型不确定,不能使用具体对象的方法
    }

六、泛型接口

1、泛型接口:将泛型定义在接口上(将泛型定义在类上就叫泛型类)

2、定义泛型接口时,一般不会明确具体类型(无意义)

/**
 * 泛型接口:定义时,一般不会明确具体类型
 */
interface Inter<T> {
    public void show(T t);
}

3、泛型接口一般有两种实现方式。使用哪一种,要看何时能明确类型

(1)实现接口时明确类型

/**
 * 实现接口时,明确类型:Inter<T> --> Inter<String>
 */
class InterImpl implements Inter<String> {
    @Override
    public void show(String str){
        System.out.println(str);
    }
}

public class Test {
    public static void main(String[] args) {
        InterImpl in = new InterImpl();
        in.show("abc");
    }
}

(2)实现接口时类型尚不明确,继续使用泛型。直到使用子类对象时,才明确类型

/**
 * 实现接口时,类型尚不明确,继续使用泛型:Inter<T> --> Inter<Q>
 */
class InterImpl<Q> implements Inter<Q> {
    @Override
    public void show(Q q){
        System.out.println(q);
    }
}

public class Test {
    public static void main(String[] args) {
        //使用子类对象时,才明确类型
        InterImpl<Integer> in1 = new InterImpl<Integer>();
        in1.show(1);

        InterImpl<String> in2 = new InterImpl<String>();
        in2.show("abc");
    }
}

猜你喜欢

转载自blog.csdn.net/ruyu00/article/details/82288904