Java泛型理解与总结

刚学java那会对泛型总是觉得有点云里雾里,工作一段时间后,再回顾泛型算是能搞明白了。
1.泛型的种类
类型泛型 -为某个具体类型E
- 为E及其子类型
- 为E及其父类型
- 为任何类型
2.简单的使用说明
针对最常用的集合来讲。假设有一颗继承树 Object <- Food <- Apple & Cookie
method1(List list)
method2(List list)
method3(List list)
method4(List list)

2.1
  method1(List list)只能接收List,不能接受List,也不能接受List,list参数在方法体中可以get/set Food类型元素。
  这很好理解,在编写method1的代码时,使用的list中都是Food类型元素,list.add(aCookie)是合法的,如果能传入一个List,那么代码将可以把Cookie放入这个Apple List。如果能传入一个List, 那么对list中的元素调用Food的方法肯定是错误的。
2.2
  method2(List list)能接受List、List,不能接受List,list参数在方法体中能get Food类型元素,不能set任何元素,除了null。
  list不能set这是因为method2能接收Food及其子类集合,并且在代码块中,list被当作List使用(这是因为在写代码的时候并不知道传入的到底是List还是List)。如果传入的参数是List,那么向其中存放Cookie显然是错误的。
2.3
  method3(List list)能接收List、List,不能接受List,list参数在方法体中能set Food及其子类和null,不能set 父类元素,get只能返回Object类型。
  在方法调用之前是不知道会传入List还是List,如果传入的是List,那么向其中存放Apple、Cookie、Food显然都是可以的,但存放Object却是不行的。同样,如果传入的是List,那么就只能取出Object元素。为了保证运行期间的安全,泛型在编译期做了很多限制,取两种情况下安全性的交集,以保证运行的绝对安全。
2.4
  method4(List list)能接受所有泛型集合,但不能set任何非null元素,只能get Object类型对象。
  这个也很好理解,在编写方法的时候(编译期)并不知道传入的集合泛型,自然不能存放元素。例如:

List foods = new ArrayList<>();
method4(foods);
Food food = foods.get(0);

如果method4把一个Object元素存入foods,那么在get时就会出现强转异常。
2.5
  可以这样理解,原生态类型(List)是为了兼容旧的代码作出的保留,在新的代码中,原生态类型应该被禁用。不允许使用原生态类型,而参数化类型(List)限制又很多,就需要通配符?来应对不同的使用情况。例如计算List的size,ListUtil.size(list),要能接收所有list对象,这时候就使用List
  之所以要使用泛型,更多的是保证代码的健壮性。即保证编写的方法在任何情况下不会出现非预期的异常,以及将更多的异常发现在早期(编译期)。例如你是用设计一个方法时,能保证调用的人传入的参数是符合预期的,而实现方法的人也知道不能在这个list中存放元素。如果用原生态类型,那就需要复杂的校验,多余的注解来告诉调用和编写代码的人,并且这多数情况下还是低效和没用的。
  这有点像Kotlin的NullPointException处理,增加了代码的编写规则,但让异常能暴露在早期。
3.泛型和数组
【泛型不能new数组】记住这条就好,除了,new ArrayList[]可以,但是这种不能正常set、get所以没什么意义。
  数组是协变得,而泛型不是协变得。意味着Object[]是Food[]的父类,而List则不是List的父类。数组存在这种情况:

E[] es = new E[1];
Object[] os = es;
os[0] = "error";
E e = es[0];

泛型设计用来避免强转异常,出现这种操作显然是违背设计初衷的,所以就直接禁止了,以保证泛型的安全性。但是泛型数组变量是可以声明的:

E[] es = (E[]) (new Object[2])

但这会有warning,最好还是用集合代替数组。有一种隐藏的情况是使用可变参数方法,因为可变参数底层也是创建一个数组来接收参数,所以不能在可变参数方法中使用泛型。
4.泛型和Class
4.1
  泛型不能使用.class,不能使用instanceof(例外,等同于原生类型,所以也没什么必要)。
String[].class √
List.class √
List.class ×
List.class ×
List.class ×
T.class ×
  
4.2
  参数化的类型不能用于强转,而形式参数类型可以用于强转,但是会有warning。
(List) myList ×
(List) myList √
(T) myObj √
4.3
  通过反射获得子类指定的泛型。

public class HelloS {
    public static void main(String[] args) {
        HelloS.Person person = new HelloS().getPerson();
        person.sayType();
    }

    public Person getPerson() {
        Person person = new Person();
        return person;
    }

    class Base {

        public void sayType() {
            Class c = this.getClass();   //这里是Person.class
            Type t = c.getGenericSuperclass();
            if (t instanceof ParameterizedType) {
                Type[] p = ((ParameterizedType) t).getActualTypeArguments();
                Class typeClass = (Class) p[0]; //因为只有一个泛型,所以是0
                System.out.println(typeClass.getName());
            }
        }
    }

    class Person extends Base {

    }
}

注意这里在Person类实现的时候就指定了Base,所以能获得。而ArrayList那种,在运行时新建的时候才指定的类型参数不能这样获得new ArrayList()。因为泛型在运行时已经擦除了。这里获得的泛型,是因为Person这个类的文件中有extends Base的信息,所以才能通过Person的Class对象获得。
4.4
  Class#asSubclass(List.class)
将某个class对象的泛型类型转换成另一种泛型类型的形式。如果转换失败则报cast异常。

List list = new ArrayList<>();  
Class listclass = list.getClass();
Class arrlistclass = listclass.asSubclass(ArrayList.class);

5.泛型和类
见4.3

6.泛型和方法
6.1 泛型声明位于方法的修饰符和返回值类型之间

public static  void method1(List list);

6.2 通配符类型不能作为方法声明泛型

 public static  E method1(List list); //正确
 public static  void method2(List list); //正确
 public static  void method3(List list); //错误

6.3 类型推导与显示指定
方法的类型参数很多时候都通过类型推导直接获得,但是在类型推导不好用的时候,也可以通过显示指定的方式指定方法的泛型。

public static  E method1();
String result = method1();  //这里调用method1()的时候,并没有指定method1()的,而是通过前面的引用推导出来

特殊情况:

public static  Set union(Set s1, Set s2);
Set numbers = union(new HashSet(), new HashSet()); //报错
Set numbers = union(~);//正确

6.4 PECS设计准则
【如果参数化类型表示一个T的生产者,就使用; 如果参数化类型表示一个T的消费者,就使用; 如果既是生产者又是消费者,就使用】。
例如List:

method(List supplier, Funcation func);
method(List consumer, Funcation func);

生产者要提供T,List 只能获得Object类型元素;
消费者要接收T,List 只能存放null。
这里仅是List,PECS适用于任何消费者/生产者的泛型设计。

你可能感兴趣的:(Java泛型理解与总结)