Java泛型

泛型的概念

  泛型,就是可以在定义类、接口时,通过一个标识来表示类中的属性类型,或者是方法的返回值及参数类型。这个标识将在使用时确定。

不使用泛型带来的安全隐患

  在JDK5之前,是没有泛型的。当我们设计一个集合时,不能确定里面装的是什么类型的对象,于是就设计成Object。当取出集合中的元素时,默认是Object的,使用时还需要强转成需要的类型,如果添加时添加了别的类型的对象,还会出现ClassCastException,带来安全隐患。

public void test(){
        ArrayList list = new ArrayList();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add("jack"); // 不小心加了个String类型的进去
        for (int i = 0; i < list.size(); i++) {
            Integer o = (Integer) list.get(i);
            System.out.print(o + "\t");
}

在这里插入图片描述
  从JDK5以后,Java引入了“参数化类型(Parameterized type)”的概念,允许我们在创建集合时再指定集合元素的类型,正如:List,这表明该List只能保存字符串类型的对象。

集合中使用泛型

  泛型在集合中使用的较多,就以集合举例。

public void test2(){
        ArrayList<String> list = new ArrayList<>();
        list.add("Tom");
        list.add("and");
        list.add("Jerry");
        // list.add(2);  编译期间就报错
        for (int i = 0; i < list.size(); i++) {
            String s = list.get(i); // 无需强转,代码更加健壮
            System.out.print(s + "\t");
        }
}

这里ArrayList的<>因为酷似菱形被称之为“菱形语法”。在JDK7之前,<>中的泛型必须补全:ArrayList list = new ArrayList();而在JDK7之后,后面的<>中的泛型类型可以不写,编译器自动推断。

自定义泛型

定义一个父类,并给父类指定多个泛型参数,它们用逗号隔开

public class Father<K,V> {
    public K print(K k){
        return k;
    }
}

定义一个子类去继承父类,此时如果不去指定父类中两个泛型的参数,那么默认是Object,可以看到,重写的方法中,返回类型K变成了Object

public class Son extends Father {
    @Override
    public Object print(Object o) {
        return o;
    }
}

如若指定父类的全部泛型类型,则子类中从父类继承过来的所有的泛型都变为指定的类型

public class Son extends Father<String, Integer> {
    @Override
    public String print(String o) {
        return o;
    }
}

如果子类保留父类的全部泛型,则可以将泛型的指定延迟到子类中去指定泛型的类型。

public class Son<K,V> extends Father<K, V> {//父类中不指定K,V类型,则子类的泛型必须有K,V类型,由父类去指定
}

如果子类只指定父类的部分泛型,则父类一定要指明子类中不指定的泛型类型。

public class Son<K> extends Father<K, Integer> {// K被子类保留,但是V没有,则父类指定为Integer类型
}

子类还可以自己额外定义泛型

public class Son<K, V, T> extends Father<K, V> { //子类添加了K这种泛型类型
}

注意:下面这段代码,K,V是子类自己的泛型,而不是父类的。虽然父类中也是K,V,但是我们没有在Father后面指定,之前也讲过,没有指定,默认就是Object类型,相当于extends Father<Object,Objcet>

public class Son<K, V > extends Father{
    @Override
    public Object print(Object o) {
        return o;
    }
}

泛型方法

  方法,也可以被泛型化,不管此时定义在其中的类是不是泛型类。在泛型方法中可以定义泛型参数,此时,参数的类型就是传入数据的类型。
语法格式:[访问权限] <泛型> 返回类型 方法名 ([ 泛型标识 参数名称] ) 抛出的异常
泛型方法中的泛型并非在类上定义的泛型,是独立出来的,且泛型方法与所在类是否为泛型类没有任何关系。在泛型方法中,无法使用类的泛型。(静态不能引用非静态)
举例:

import java.util.List;

public class GenericMethod<T> {

    private T param;

    public T getParam() { // 不是泛型方法,使用的是类的泛型
        return this.param;
    }

    public void setParam(T param) { // 不是泛型方法,使用的是类的泛型
        this.param = param;
    }

   /**
     * 将集合中的内容拷贝到数组并返回
     * 泛型方法中前面一定要加上 <泛型字母>,告诉编译器这是泛型方法,否则编译器会以为E是一个类
     * 泛型方法中的泛型在调用是确定,并非在初始化对象时确定,所以可以是static的
     * @param list 入参list
     * @param <E>  泛型
     * @return E数组
     */
    public static <E> E[] printList(List<E> list) {
        E[] arr = (E[]) new Object[list.size()];
        for (int i = 0; i < list.size(); i++) {
            arr[i] = list.get(i);
        }
        return arr;
    }
}

泛型中的继承问题

如果类B是类A的子类,而G是具有泛型的类或者接口,那么G< B >并不是G< A >的子类型。

public static void main(String[] args) {
        Object obj = new Object();
        String str = new String();
        obj = str; // 编译不报错
        ArrayList<Object> objectList = new ArrayList<>();
        ArrayList<String> stringList = new ArrayList<>();
        objectList = stringList; // 编译报错
}

可以看到,即使String是Object的子类,但是ArrayList< String >并不是ArrayList< Object >的子类,他们之间没有多态性。但是这样就十分的麻烦,如果要接收不同泛型类型的集合,还需要重载多个方法。那么有没有一个泛型是所有泛型的父类,就向Object一样呢?答案是肯定的,就要用到通配符

通配符的使用

  泛型的类型通配符是英文输入法下的 ? ,比如 List<?> , Map<?,?>,Comparable<?>等。List<?>即是List< String >、List< Object >等所有泛型List的父类 。
使用的注意点,拿集合举例:

  • 如果是?类型的泛型,那么则代表此类或接口的泛型不确定,但是我们可以通过集合的get()方法来获取集合中的元素,或者迭代器输出元素,即使类型不确定的类型,但是我们可以知道,它总是Object或者Object子类。
public void printList(List<?> list) {
       Iterator<?> iterator = list.iterator();
       while(iterator.hasNext()){
           Object obj= iterator.next();//遍历取出,使用Object接收
           System.out.println(obj);
       }
}
  • 对于不确定的泛型类型 ? ,我们是无法向集合中写入元素的。因为我们无法确切的知道元素的类型。 除了null,因为泛型必须是引用类型,而所有引用类型的默认值都是null
public void test(){

       ArrayList<?> arrayList = new ArrayList<>();
//        arrayList.add(new Object()); // 编译不通过
       arrayList.add(null);
       System.out.println(arrayList.get(0));
}

通配符的使用

public void test2(){
        ArrayList<Integer> integerList = new ArrayList<>();
        ArrayList<String> stringList = new ArrayList<>();
        integerList.add(1);
        integerList.add(2);
        integerList.add(3);
        stringList.add("Jack");
        stringList.add("And");
        stringList.add("Rose");
        printList(stringList);
        System.out.println();
        printList(integerList);
    }
    public static void printList(List<?> list) { // 使用?通配符,是所有泛型的父类
        Iterator<?> iterator = list.iterator();
        while(iterator.hasNext()){
            Object obj= iterator.next();//遍历取出,使用Object接收
            System.out.print(obj + "\t");
        }
}

通配符的上下限

在看源码时,经常看到 <? extends T > 或者 <? super T>这种泛型形式,这就是指定泛型的上下限。以集合为例。

  • List<? extends Father> : 表示 <= Father ,即泛型类型必须是Father的子类或Father类型
  • List<? super Father> :表示 >= Father,即泛型类型必须是Father的父类或Father类型
  • List<? extends Comparable> : 表示泛型类型必须是实现了Comparable的子类
    举例 ? extends Father:
public void test() {
        Son<String, Integer> son = new Son<>();
        List<Son> list = new ArrayList<>();
        print(list); // 编译通过
        List<Father> list1 = new ArrayList<>();
        print(list1); // 编译通过
        List<Object> list2 = new ArrayList<>();
        print(list2); //编译不通过
}


public void print(Collection<? extends Father> list) { //
        Iterator<? extends Father> iterator = list.iterator();
        while (iterator.hasNext()) {
            Father<String, Integer> f = iterator.next();
            System.out.println(f);
        }
}

举例 ? super Father

public void test() {
        Son<String, Integer> son = new Son<>();
        List<Son> list = new ArrayList<>();
        print(list); // 编译不通过,要求起步是Father类型
        List<Father> list1 = new ArrayList<>();
        print(list1); // 编译通过
        List<Object> list2 = new ArrayList<>();
        print(list2); //编译通过
}
    
public void print(Collection<? super Father> list) {
        Iterator<? super Father> iterator = list.iterator();
        while (iterator.hasNext()) {
            Object f = iterator.next();
            System.out.println(f);
        }
}

通配符上下限的添加问题

看一个例子

public void addTest(){
        List<? extends Father> list1 = new ArrayList<>();
        List<? super Father>  list2 = new ArrayList<>();
        list1.add(new Son()); // 编译不通过
        list1.add(new Father()); // 编译不通过
        list1.add(new String()); // 编译不通过

        list2.add(new Father()); // 编译通过
        list2.add(new Son()); // 编译通过
        list2.add(new Object()); // 编译不通过
}

当我们向集合中添加元素时,如果指定泛型类型,则只能添加指定类型的元素。如果使用了通配符 ? ,则表示允许所有泛型的引用调用。如果集合中限制了通配符的上下限,那么看下限即可
List<? extends Father> list1 = new ArrayList<>(); 集合中可以存放Father及Father的子类,相当于它的范围是(-∞,Father),相当于是无下限的。如果向集合中存放Father类型,那么万一?是比Father还小的类型就无法存放,子类可以赋值给父类,但是父类不能赋值给子类。就好比List< Son >,我们无法add(Father)。这里是没有下限的,理论上无论你添加什么值,总有小于你添加的类型。
List<? super Father> list2 = new ArrayList<>();集合中可以存放Father和Father的父类,相当于它的范围是(Father,+∞),相当于下限是Father。也就是说,该集合中能存放Father已经Father的子类类型,因为无论如何,我的起步点都是从Father开始,子类赋值给父类,是可以的。但是添加Object又不行了,因为在(Father,Object)之间,总能找到比你小的子类,父类无法赋值给子类,所以无法添加。

总结

  • 泛型类可能有多个参数,此时应将多个参数一起放在尖括号内。比如:<E1,E2,E3>
  • 泛型类的构造器是:public GenericClass(){},而非public GenericClass< E >(){};
  • 实例化后,操作原来泛型位置的结构必须与指定的泛型类型一致。
  • 泛型不同的引用不能相互赋值。
  • 泛型如果不指定,将被擦除,泛型对应的类型均按照Object处理,但不等价于Object。经验:泛型要使用一路都用。要不用,一路都不要用。
  • 如果泛型结构是一个接口或抽象类,则不可创建泛型类的对象。
  • jdk7,泛型的简化操作:ArrayList flist = new ArrayList<>();
  • 泛型的指定中不能使用基本数据类型,可以使用包装类替换。
  • 在类/接口上声明的泛型,在本类或本接口中即代表某种类型,可以作为非静态属性的类型、非静态方法的参数类型、非静态方法的返回值类型。但在静态方法中不能使用类的泛型
  • 异常类不能是泛型的
  • 不能使用new E[]。但是可以:E[] arr= (E[])new Object[capacity];
  • 父类有泛型,子类可以选择保留泛型也可以选择指定泛型类型。
  • 如果B是A的一个子类型(子类或者子接口),而G是具有泛型声明的类或接口,则G< B >并不是G< A >的子类型。
  • 无法将通配符 ? 声明在泛型方法中。
public static <?> ?[] printList(List<?> list) {} // 错的离谱
  • 无法将通配符 ?声明在类上面
public class GenericMethod<?> {} // 也是不允许的
  • 无法将通配符 ? 用在创建对象上
ArrayList<?> list= new ArrayList<?>(); //右边的尖括号中无法使用 ?
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值