美文网首页
Java泛型理解与总结

Java泛型理解与总结

作者: 云海_54d4 | 来源:发表于2018-02-27 17:23 被阅读0次

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

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

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

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

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<E>.class ×
List<?>.class ×
List<String>.class ×
T.class ×
  
4.2
  参数化的类型不能用于强转,而形式参数类型可以用于强转,但是会有warning。
(List<String>) myList ×
(List<T>) 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<T> {

        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<String> {

    }
}

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

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

5.泛型和类
见4.3

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

public static <E> void method1(List<E> list);

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

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

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

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

特殊情况:

public static <E> Set<E> union(Set<? extends E> s1, Set<? extends E> s2);
Set<Number> numbers = union(new HashSet<Integer>(), new HashSet<Long>()); //报错
Set<Number> numbers = <Number>union(~);//正确

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

method(List<? extend T> supplier, Funcation<T> func);
method(List<? super T> consumer, Funcation<T> func);

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

相关文章

  • Java 泛型

    java 泛型 很多朋友对java的泛型不是很理解,很多文章写的已不是很清楚,这篇博客对java泛型进行 一个总结...

  • java 泛型

    很多朋友对Java的泛型不是很理解,很多文章写的已不是很清楚,这篇博客对java泛型进行 一个总结。 泛型的转换:...

  • 泛型琐碎之泛型上下限

    泛型的命名规范 为了更好地去理解泛型,我们也需要去理解java泛型的命名规范。 为了与java关键字区别开来,ja...

  • 泛型

    java 泛型详解-绝对是对泛型方法讲解最详细的,没有之一Java泛型深入理解加泛型面试数组的协变性与范型的不可变性

  • Java泛型

    参考:Java知识点总结(Java泛型) 自定义泛型类 自定义泛型接口 非泛型类中定义泛型方法 继承泛型类 通配符...

  • 泛型

    与Java泛型相同,Kotlin同样提供了泛型支持。对于简单的泛型类、泛型函数的定义,Kotlin 与 Java ...

  • java泛型理解及应用

    内容: java泛型理解及应用

  • 【Android】 Kotlin(七)泛型

    深入理解Kotlin泛型 Kotlin 的泛型与 Java 一样,都是一种语法糖,即只在源代码中有泛型定义,到了c...

  • java泛型

    本质:类型参数化 java总结——泛型

  • Java泛型教程

    Java泛型教程导航 Java 泛型概述 Java泛型环境设置 Java泛型通用类 Java泛型类型参数命名约定 ...

网友评论

      本文标题:Java泛型理解与总结

      本文链接:https://www.haomeiwen.com/subject/iazpxftx.html