Java泛型总结

1、使用泛型的好处

当没有泛型的时候,Java List的做法是

public interface List extends Collection{
   boolean add(Object e);
   Object remove(int index);
}

使用泛型之后

public interface List<E> extends Collection<E> {
   boolean add(E e);
   E remove(int index);
}

由于Java是强类型转换,如果不使用泛型,在运行时可能出现类型转换错误,该错误在编译时无法发现。但是使用了泛型,在编译时会做强类型检查,提高了类型转换的正确率。并且编译时类型擦除,即Java编译源码时,会删除所有的类型参数,替换类型为上下界,如果没有界限,则替换成Object。例如:

public class Zoo<T> {
    public boolean putIn(T t) {
        return true;
    }
}
/************编译之后****************/
public class Zoo {
    public boolean putIn(Object t) {
        return true;
    }
}

带有上下界的编译对比:

public class Zoo<T extends Animal> {
    public boolean putIn(T t) {
        return true;
    }
}

/*****************编译之后*******************/
public class Zoo {
    public boolean putIn(Animal t) {
        return true;
    }
}

2、PECS原则

PECS原则即producer-extends,consumer-super,生产者使用extends,消费者使用super,简单粗暴的例子是Collections.copy

public static <T> void copy(List<? super T> dest, List<? extends T> src) {
    int srcSize = src.size();
    if (srcSize > dest.size())
        throw new IndexOutOfBoundsException("Source does not fit in dest");
    if (srcSize < COPY_THRESHOLD ||
        (src instanceof RandomAccess && dest instanceof RandomAccess)) {
        for (int i=0; i<srcSize; i++)
            dest.set(i, src.get(i));
    } else {

        //di需要拷贝src的数据给自己,对于他来说,他消费了src的数据,所以消费者角色使用super
        ListIterator<? super T> di=dest.listIterator();
        //si 需要把自己的数据传递给di,对于他来说,他产生了数据,所以生产者角色使用extends
        ListIterator<? extends T> si=src.listIterator();
        for (int i=0; i<srcSize; i++) {
            di.next();
            di.set(si.next());
        }
    }
}

使用其他例子来说明extends & super的用法

private class DataManager<E>{
        //以该对象的视角来看,getAll即自己被别人拿走(消费),putAll即自己放入的新的对象进来,自己产生了新的元素
        private List<E> list = new ArrayList<>();

        public void getAll(List<? super E> out){
            //out需要使用list消费,站在out视角,它写入的其他元素,所以适用于写场景
            out.addAll(list);
        }
        public void addAll(List<? extends E> in){

            //extends 适用于读场景,因为站在in的视角,它被list读取了
            list.addAll(in);
        }
}

那么如何正确的使用上下界通配符呢

package com;

import java.util.ArrayList;
import java.util.List;

import javax.swing.plaf.basic.ComboPopup;

import lombok.Getter;
import lombok.ToString;

/**
 * @author xiude
 * @date 2018/4/1
 */
public class PESCTest {
    private static class Group{

    }

    @ToString
    @Getter
    private static class Company extends Group{
        private String name;

        public Company(String name) {
            this.name = name;
        }
    }

    public static void main(String[] args) {

        //消费者
        List<? super Group> listB = new ArrayList<>();

        //生产者
        //List<? extends Company> listC = new ArrayList<Group>是编译不过的
        List<? extends Company> listC = new ArrayList<Company>(){
            {
                add(new Company("Tmall"));
                add(new Company("Taobao"));
            }
        };
        //编译不通过,因为listC被插入了新的元素,属于消费者,消费者只能使用super,但是listC声明成了extends,注定只能读,不能写
        //listC.add(new Company("Alipay"));

        listB.add(listC.get(0));
        listB.add(listC.get(1));

        //listB作为消费者,get到的元素z还能赋值给Object
        //Company tmall = listB.get(0);
        Object tmallObj = listB.get(0);

        if(tmallObj instanceof Company){
            Company tmall = (Company)tmallObj;
            System.out.println("I am "+tmall.getName());
        }

        listB.stream().forEach(System.out::println);
    }

}

使用上界通配符 <? extends Company>

上界通配符,一般用于读取的场景(Company是该类型的上限,如果写是不知道究竟是什么子类)
1、为泛型指定的类型只能是Company类型或者其子类。
2、只能为其列表添加null。
3、get方法获取的值只能赋值给Company类或者其超类。

使用下界通配符 <? super Company>

下界通配符,一般用于写入的场景。(Company是该类型的下限,有下限才能写)
1、为泛型指定的类型必须为Company,或者其超类。
2、可以为其列表添加任意Company类型,或者其子类。
3、get方法获取的类型,只能赋值给Object类型。

这里最后补充一个extends Company vs T extends Company 的区别

public static void println(List<? extends Company> list){
        
    //list.add(new Dept("A")); 编译不通过

    /*如果list 传进 List<? extends Company> listD = new ArrayList<Dept>();打印classcom.PESCTest$Dept
      如果是  List<? extends Company> listC = new ArrayList<Company>();打印classcom.PESCTest$Company*/
    System.out.println(list.get(0).getClass());

}
public static <T extends Company> void print(List<T> list){
        list.add((T)new Company("Cainiao"));
        System.out.println(list.get(0).getClass()); //结果class com.PESCTest$Company

}
  • 通配符(? extends Company):可以进行获取操作,但是不能写入(set(? extends Number)不支持);
  • 类型参数(T extends Company): 获取和写入都支持,并且可以声明引用和作为返回值;例如

猜你喜欢

转载自my.oschina.net/u/2302503/blog/1788220