IT教程 ·

Java范型学习条记

小cookie,大智慧

关于范型的运用或者说印象只要鸠合,其他处所纵然运用过也不晓得,横竖就是只停留在List<E> Map<K, V>,近来恰好闲来无事,就找找材料进修一下;下列为个人进修总结,迎接进修交换;

1. 什么是java泛型

范型:参数化范例,所操纵的数据范例被指定为一个参数。这类参数范例能够用在类、接口和要领的创建中,离别称为泛型类、泛型接口、泛型要领;

List<Integer> list = new ArrayList<>();

上述代码申清楚明了一个鸠合,操纵的数据范例被指定为Integer(此处Integer为范例参数);

2. 为何须要泛型

引入泛型的优点是能够将运转时毛病提前到编译时毛病

List list = new ArrayList();
list.add(100);
list.add("zhangsan");

for(int i = 0; i< list.size();i++){
    int num = (int)list.get(i);
}

上面的代码在编译时没有任何问题,然则在运转时会报错:

java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer

假如没有引入范型,鸠合内的操纵数据范例能够是任何范例,假如在操纵数据时举行范例推断然后在强转也是没有问题的,然则很明显不切合现实,所以假如引入范型对操纵数据范例做肯定的束缚的话,将会对后续的操纵供应太多的轻易也能削减毛病的涌现;

List<Integer> list = new arrayList<>();
//list.add("zhangsan");//在编译期就会报错

3. 范型的运用

泛型有三种运用体式格局,离别为:泛型类、泛型接口、泛型要领

3.1. 范型类

泛型类中的范例参数险些能够用于任何能够运用接口名、类名的处所

/**
 * 范型标识能够是任何标识标记,如罕见的E, T, K, V ...
 */
class 类名<范型标识>{
}

class Stu<T>{
}

class People<E>{
}

注重:

  • 泛型的范例参数只能是类范例,不能是基础范例;
List<int> list;//基础范例不能算作范例参数    
  • 不能对确实的泛型范例运用instanceof操纵。以下面的操纵是不法的,编译时会失足。
if(item instanceof List<Integer>) // Illegal generic type of instanceof

3.2. 范型接口

泛型接口与泛型类的定义及运用基础雷同

public interface Iterable<T> {
    Iterator<T> iterator();
}
  1. 完成范型接口,未明白范型时:完成类后的范型标识不能省略
class Iter<T> implements Iterable<T> {
    @Override
    public Iterator<T> iterator() {
        return null;
    }
}
  1. 完成范型接口,明白范型时:完成类后的范型标识省略
class Iter1 implements Iterable<String> {
    @Override
    public Iterator<String> iterator() {
        return null;
    }
}

3.3. 范型要领

public 与 返回值 中心 非常重要,声明此要领为泛型要领;

/**
 * 泛型要领的基础引见
 * @param tClass 传入的泛型实参
 * @return T 返回值为T范例
 * 申明:
 *     1)public 与 返回值 中心<T>非常重要,声明此要领为泛型要领;
 *     2)只要声清楚明了<T>的要领才是泛型要领,若没有<T>泛型类中纵然运用了泛型的成员要领也不是泛型要领;
 *     3)<T>表明该要领将运用泛型范例T,此时才能够在要领中运用泛型范例T;
 *     4)与泛型类的定义一样,此处T能够随意写为恣意标识,罕见的如T、E、K、V等情势的参数常用于示意泛型;
 */
public <T> T genInstance(Class<T> tClass) throws IllegalAccessException, InstantiationException {
    T instance = tClass.newInstance();
    return instance;
}  

光看上面的例子大概依旧会非常含糊,我们再经由过程一个例子

public class ArrayList<E> implements List<E> {

    /**
     * 虽然在要领中运用了泛型,然则这并非一个泛型要领。
     * 这只是类中一个平常的成员要领,只不过他的返回值是在声明泛型类已声明过的泛型。
     * 所以在这个要领中才能够继承运用 T 这个泛型。
     */
    public E get(int index) {
        //...
        return elementData(index);
    }

    /**
     * 将E改成T后,要领报错,"cannot reslove symbol T"
     * 由于在类的声明中并未声明泛型T,所以在运用E做形参和返回值范例时,编译器会没法辨认
     */
    public T set(int index, T element) {
        //...
        return oldValue;
    }

    /**
     * 这才是一个真正的泛型要领。
     * 首先在public与返回值之间的<T>必不可少,这表明这是一个泛型要领,而且声清楚明了一个泛型T
     * 这个T能够涌现在这个泛型要领的恣意位置.
     */
    public <T> T[] toArray(T[] a) {
        //...
        return a;
    }
}    

4. 范型通配符

运用?替代范型标识,标识操纵数据范例能够为任何数据范例

java中我们都晓得父类能够涌现的处所,子类都是能够涌现的,然则:

public static void method(List<Number> list){
}

public static void main(String[] args) {
    List<Integer> list = new ArrayList<>();
    method(list); // 此处报错:List<java.lang.Number> cannot be applied to List<java.lang.Integer>
}

经由过程提醒信息我们能够看到List<Integer>不能被看作为List<Number>的子类。
由此能够看出:同一种泛型能够对应多个版本(由于参数范例是不确定的),差别版本的泛型类实例是不兼容的。
我们能够将上面的要领改一下:

public static void method(List<?> list){
}

范例通配符平常是运用?替代详细的范例实参,此处的和Number、String、Integer一样都是一种现实的范例,能够把算作一切范例的父类,是一种实在的范例;
能够处理当详细范例不确定的时刻,这个通配符就是?;当不须要运用范例的详细功用只运用Object类中的功用,那末能够用 ? 通配符来表未知范例。

5. 泛型高低边境

在运用泛型的时刻,我们还能够为传入的泛型范例实参举行高低边境的限制,如:范例实参只准传入某种范例的父类或某种范例的子类。

public static void method(List<? extends Number> list){
}

public static void main(String[] args) {
    List<Integer> intList = new ArrayList<>();
    method(intList); // Integer 是Number的子类

    List<Double> doubleList = new ArrayList<>();
    method(doubleList); // Double是Number的子类

    List<String> strList = new ArrayList<>();
    method(strList); // 此处报错,String不是Number的子类
}

6. 范型擦除

Java在编译时期,一切的泛型信息都邑被擦掉;
如在代码中定义List<Object>List<String>等范例,在编译后都邑变成List,JVM看到的只是List,而由泛型附加的范例信息对JVM是看不到的。Java编译器会在编译时尽量的发明大概失足的处所,然则依然没法预知在运转时刻涌现的范例转换非常的状况;

擦除范型后只保存原始范例

原始范例 就是擦除去了泛型信息,末了在字节码中的范例变量的真正范例,不管什么时候定义一个泛型,响应的原始范例都邑被自动供应,范例变量擦除,并运用其限制范例(无限制的变量用Object)替代。

List<String> list1 = new ArrayList<>();
list1.add("abc");

List<Integer> list2 = new ArrayList<>();
list2.add(123);

System.out.println(list1.getClass() == list2.getClass()); // true
System.out.println(list1.getClass()); // class java.util.ArrayList

申明泛型范例String和Integer都被擦除掉了,只剩下原始范例List;

ArrayList<Integer> list = new ArrayList<Integer>();

list.add(1);  //如许挪用 add 要领只能存储整形,由于泛型范例的实例为 Integer
list.add("asd"); // 此处报错

list.getClass().getMethod("add", Object.class).invoke(list, "asd"); // 经由过程反射猎取实例后能够增加胜利

for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}

在程序中定义了一个ArrayList泛型范例实例化为Integer对象,假如直接挪用add()要领,那末只能存储整数数据,不过当我们应用反射挪用add()要领的时刻,却能够存储字符串,这说清楚明了Integer泛型实例在编译以后被擦除掉了,只保存了原始范例。

参考材料:

【JavaScript】进制转换&位运算,了解一下?

参与评论