java泛型 T 和 通配符 ?

一 泛型的基本用法

从java5开始,java开始引入泛型。在没有泛型之前,从集合中读取到的每一个对象都必须进行转换,如果有人不小心插入了类型错误的对象,在运行时的转换处理就会出错。
在这里插入图片描述在这里插入图片描述
有了泛型之后,你可以告诉编译器每个集合接受哪些对象类型。编译器自动为你的插入进行转换,饼子啊编译时告知是否插入了类型错误的对象。

定义

· 1 定义在类中,紧跟在类名后面

  public class TestClassDefine<T, S extends T>{}

定义泛型 T, S, 且S 继承 T

· 2 定义在方法装饰符后面,紧跟修饰符后面(public)

  public <T, S extends T> T testGenericMethodDefine(T t, S s){}

定义泛型 T, S, 且S 继承 T

运行原理

java泛型是通过擦拭来运行的,即在代码运行过程中不会生效,只是编译期生效。

List<String> stringArrayList = new ArrayList<String>();
List<Integer> integerArrayList = new ArrayList<Integer>();

Class classStringArrayList = stringArrayList.getClass();
Class classIntegerArrayList = integerArrayList.getClass();

if(classStringArrayList.equals(classIntegerArrayList)){
    Log.d("泛型测试","类型相同");
}

输出结果:D/泛型测试: 类型相同。
对此总结成一句话:泛型类型在逻辑上看以看成是多个不同的类型,实际上都是相同的基本类型。

二 PECS法则

参数化类型是不变的。
对于任何两个截然不同的类型Type1和Type2而言,List < Type1 > 既不是List < Type2>的子类型,也不是它的超类型。即虽然String是Object类的子类型,但是List < String>从来不是List < Object>的子类型

我们定义一个Stack类:

public  class Stack<E> {
    public Stack();
    public void push(E e);
    public E pop();
    public boolean isEmpty();
}

如果我们尝试定义一个pushAll方法,让它按顺序将一系列元素放到stack中

    public void pushAll(Iterable<E> src) {
        for (E e : src) {
            push(e);
        }
    }

在编译期这个方法正确无误,假设定义一个Stack < Number>,并且调用pushAll,传递的参数是Integer类型,按照逻辑说这是可以的因为Integer是Number的一个子类。

 Stack<Number> numberStack = new Stack<>();
        Iterable<Integer> integerIterable = new ArrayList<Integer>();
        numberStack.pushAll(integerIterable);

但是这个无法编译通过
在这里插入图片描述
这个时候我们的通配符 ? 就派上用场了

    public void pushAll(Iterable<? extends E> src) {
        for (E e : src) {
            push(e);
        }
    }

pushAll的输入参数类型不应该是E的Iterable接口,而应该是E的某个子类型的Iterable接口。通配符类型Iterable<? extends E>正式这个意思。

在这里插入图片描述
调用已经可以正常进行。

假设现在要编写一个popAll方法与pushAll对应。使元素从stack中弹出,并且收集到指定集合中:

    public void popAll(Collection<E> dst) {
        while (!isEmpty()) {
            dst.add(pop());
        }
    }

同样,如果目标集合的元素类型与堆栈完全匹配,则编译通过,并且运行良好。
现在假设有一个Stack < Number>和object类型的变量,从堆栈中弹出一个元素保存在object中,按说不应该报错
在这里插入图片描述
可以看到还是拿到了类似于第一次用pushAll时的错误:Collection < Number>不是Collection< Object>的子类型。
我们可以修改如下:

    public void popAll(Collection<? super E> dst) {
        while (!isEmpty()) {
            dst.add(pop());
        }
    }

这时我们上面的代码就不会报错了。popAll的输入参数类型不应该是E的集合,而应该是E的某种超类的集合。

结论很明显:为了获得最大限度的灵活性,要在表示生产者或者消费者的输入参数上使用通配符类型。

PECS 表示 producer-extends , consumer-super
如果参数化类型表示一个生产者T,就使用<? extends T>;如果他表示一个消费者T,就使用<? super T>。

还要记住:Comparable和Comparator都是消费者

三 T 和 ? 的更多场景

1,要注意的是我们没有办法向List<?>中添加任何元素,除了null,即使有上下边界也不行。
2,尽量不要在返回值中使用通配符,因为这样会强制用户在客户端代码中使用通配符,如果用户必须考虑通配符类型,api出错的可能性大大增加
3,始终是Comparable<? super T>优先于Comparable< T>,始终是Comparator<? super T>优先于Comparator< T>
4,有个问题值得讨论

    public static <E> void swap(List<E> list, int i, int j);
    public static  void swap(List<?> list, int i, int j);

在javaAPI中,第二种更好一些,因为它并不需要考虑类型参数,而是可以随意传list的类型值。
一般来说,如果类型参数只在方法生命中出现一次,就可以用通配符替代它。

发布了6 篇原创文章 · 获赞 10 · 访问量 2715

猜你喜欢

转载自blog.csdn.net/weixin_40292704/article/details/103988501