java 泛型

来源:互联网 发布:大阪 知乎 编辑:程序博客网 时间:2024/06/15 04:37

1. 泛型解决的问题:

解决了由于类型强制转换产生的类型安全问题。避免了类膨胀。

如果没有泛型一个List(不一定要限定为list可以是任何有泛型参数的类)。只能装一个中类型比如要装A需要定义一个类ListA,要装B需要定义一个listB。这样会造成类膨胀的问题,还有一种方法是将所有要装入List的对象都向下转型为Object(其实java泛型就是这样实现泛型只是一种语法糖),但这样读取list中的对象来用的时候就需要强制转型,不但编码的时候麻烦还有可能引起类型安全问题。泛型可以完美的解决以上两个问题。

个人认为泛型的本质就是做了一个合适颗粒度的抽象。类型参数,把一个类型作为另一个类型的参数这样类就不再是一成不变的可以根据类型参数来用做不同的类型,泛型类跟普通类的关系有点像变量和变量值的关系,比如int x,x可以是-2的32次方和2的32次方之间任何整数,就说x可以表示任何该范围内的值,等于是一个合适的范围限制。

2. 一些注意的点,

未分配类型的泛型成为原始类型new ArrayList()相当于new ArrayList<Object>();

无法将原始类型(int,long,double,char)用于泛型。

3. 有界类型(这里只是拿list来举例任何容器泛型类都一样)

List<T extends UpperBoundType>(上界)

List<T super LowerBoundType>(下界)

如果声明List<T extends Number> ln ,则ln可以指向List<Integer>的对象li,可以指向List<Double> 的对象ld;所以tn只能读不能写,如果add给ln一个Double如果ln是指向List<Integer>的,就会发生类型安全问题。但是可以读读出来的对象肯定是Number的子类对象。

如果声明一个list<T super Integer>  lsi ,则lsi可以指向List<Number>的对象li,也可以指向List<Object>的对象lo;所以从lsi读出的值只能用Object类型来解释,不能用Number因为,有可能是Object的对象用Number类型来解释会有线程安全问题。 下面一段为

The wildcard declaration of List<? extends Number> foo3 means that any of these are legal assignments:

List<? extends Number> foo3 = new ArrayList<Number>();  // Number "extends" Number (in this context)List<? extends Number> foo3 = new ArrayList<Integer>(); // Integer extends NumberList<? extends Number> foo3 = new ArrayList<Double>();  // Double extends Number
  1. Reading - Given the above possible assignments, what type of object are you guarenteed to read from List foo3:

    • You can read a Number because any of the lists that could be assigned to foo3contain a Number or a subclass of Number.
    • You can't read an Integer because foo3 could be pointing at a List<Double>.
    • You can't read a Double because foo3 could be pointing at a List<Integer>.
  2. Writing - Given the above possible assignments, what type of object could you add to List foo3 that would be legal for all the above possible ArrayList assignments:

    • You can't add an Integer because foo3 could be pointing at a List<Double>.
    • You can't add a Double because foo3 could be pointing at a List<Integer>.
    • You can't add a Number because foo3 could be pointing at a List<Integer>.

You can't add any object to List<? extends T> because you can't guarantee what kind of List it is really pointing to, so you can't guarantee that the object is allowed in that List. The only "guarantee" is that you can only read from it and you'll get a T or subclass of T.

super

Now consider List <? super T>.

The wildcard declaration of List<? super Integer> foo3 means that any of these are legal assignments:

List<? super Integer> foo3 = new ArrayList<Integer>();  // Integer is a "superclass" of Integer (in this context)List<? super Integer> foo3 = new ArrayList<Number>();   // Number is a superclass of IntegerList<? super Integer> foo3 = new ArrayList<Object>();   // Object is a superclass of Integer
  1. Reading - Given the above possible assignments, what type of object are you guaranteed to receive when you read from List foo3:

    • You aren't guaranteed an Integer because foo3 could be pointing at a List<Number> or List<Object>.
    • You aren't guaranteed an Number because foo3 could be pointing at a List<Object>.
    • The only guarantee is that you will get an instance of an Object or subclass of Object (but you don't know what subclass).
  2. Writing - Given the above possible assignments, what type of object could you add to List foo3 that would be legal for all the above possible ArrayList assignments:

    • You can add an Integer because an Integer is allowed in any of above lists.
    • You can add an instance of a subclass of Integer because an instance of a subclass of Integer is allowed in any of the above lists.
    • You can't add a Double because foo3 could be pointing at a ArrayList<Integer>.
    • You can't add a Number because foo3 could be pointing at a ArrayList<Integer>.
    • You can't add a Object because foo3 could be pointing at a ArrayList<Integer>.

PECS

Remember PECS"Producer Extends, Consumer Super".

  • "Producer Extends" - If you need a List to produce T values (you want to read Ts from the list), you need to declare it with ? extends T, e.g. List<? extends Integer>. But you cannot add to this list.

  • "Consumer Super" - If you need a List to consume T values (you want to write Ts into the list), you need to declare it with ? super T, e.g. List<? super Integer>. But there are no guarantees what type of object you may read from this list.

  • If you need to both read from and write to a list, you need to declare it exactly with no wildcards, e.g. List<Integer>.

Example

Note this example from the Java Generics FAQ. Note how the source list src (the producing list) uses extends, and the destination list dest (the consuming list) uses super:

public class Collections {   public static <T> void copy(List<? super T> dest, List<? extends T> src) {      for (int i = 0; i < src.size(); i++)         dest.set(i, src.get(i));   } }

4. 协变

List<Number> 不能解释(不能指向)List<Integer>的对象。

但是数组是协变的 Number[]能解释Integer[]的对象。但是会引起只能读不能写的问题,比如Number[] ns = new Intege[](); 如果 Number n= ns[0]没问题,但是ns[0] = new Integer(1)可能会报错,如果ns指向的Integer[]的对象就不会报错但是如果指向的Double[]的对象就会报错。有一些人认为数据设置成协变的是一种设计缺陷。

测试代码

package com.lww.test.generics;import com.google.common.collect.Lists;import org.junit.Test;import java.beans.IntrospectionException;import java.math.BigDecimal;import java.util.ArrayList;import java.util.Arrays;import java.util.List;/** * User: yian * Date: 2017/5/7 *///public class test<T extends BigDecimal> {//有界类型的正确声定义方法,声明变量的时候可以用?代替T但是//public class test<? extends T> {//报错//public class test<? extends BigDecimal> {//报错能用问号public class test<T> {//也可以声明为test<? extends BigDecimal> q12//public class test<?> {//类上不能声明通配符变量    /**     * 该方法只能返回null因为N不能被写,只能读     */    public static <N extends Integer> N add(N  b){        return b;    }    public   void  doTest(List<T>  list) {//        System.out.println(list.get(0).getClass());    }    public   void  doTest2(List<?>  list) {//编译错误泛型必须是类型参数即,实例化类型的时候传进来的    }//    public static void test11(List<T>) {//静态方法不能用类上声明的////    }    public  <N extends Number> double add(N a, N b){        double sum = 0;        sum = a.doubleValue() + b.doubleValue();//参数声明为N类型的,此处是读所以不违反PECS原则。        return sum;    }    public  <N extends T> void dotest11(N a, N b){    }//    public   void  doTest2(List<M>  list) {//编译错误泛型必须是类型参数,类上声明的或者方法上声明的////    }    public static <M>  void  doTest23(List<M>  list) {//23和24说明不知道类型参数的时也可以不用?        System.out.println(list.get(0).getClass());    }    public static   void  doTest24(List<?>  list) {        System.out.println(list.get(0).getClass());    }    public   static   void  main(String[] args) {        List< Parent > parentList  =   new  ArrayList < Parent > ();        parentList.add(new Parent());        List < Child >  childList  =   new  ArrayList < Child > ();        childList.add(new Child());        test q = new test();//即是new的        q.doTest(parentList);        q.doTest(childList);        doTest23(parentList);        doTest23(childList);        doTest24(parentList);        doTest24(childList);        // 注意这里编译错误//        doTest(childList);        ArrayList arrayList = new ArrayList();        arrayList.add(new Integer(1));//        arrayList.add()//如果不注释这里ide会提示object,说明实例化类的时候        test<? extends BigDecimal> q12 = new test<>();////        test<T extends BigDecimal> q123 = new test<>();//不能这么声明只能用?        test q1 = new test<Parent>();//        q1.doTest(parentList);        q1.doTest(childList);//q1的类型参数其实object 所以object可以协变而其他类型不可以协变        q1.doTest(Arrays.asList(1,2));//        test<Parent> q3 = new test<Child>();//不可以协变//        test<Parent> q2 = new test<>();//即是new的//        q2.doTest(parentList);//        q2.doTest(childList);//        q2.doTest(Arrays.asList(1,2));    }}


java.util.Collection.removeIf(Predicate<?superE>filter) 用 <? super E> 都行因为Collection中的原始E可以向上转型为其父类的对象。


<?>为无界通配符


原创粉丝点击