java基础知识——泛型

简介

泛型就是编写模板代码来适应任意类型。即编写一次,万能匹配,又通过编译器保证了类型安全,这就是泛型。

擦拭法

泛型是一种类似”模板代码“的技术,不同语言的泛型实现方式不一定相同,Java语言的泛型实现方式是擦拭法(Type Erasure),所谓擦拭法是指,虚拟机对泛型其实一无所知,所有的工作都是编译器做的。导致了以下两点特性:

  • 编译器把类型<T>视为Object
  • 编译器根据<T>的类型自动实现安全的强制转型

使用泛型的时候,我们编写的代码也是编译器看到的代码:

Pair<String> p = new Pair<>("Hello", "world");
String first = p.getFirst();
String last = p.getLast();

而虚拟机执行的代码并没有泛型:

Pair p = new Pair("Hello", "world");
String first = (String) p.getFirst();
String last = (String) p.getLast();

所以,Java的泛型是由编译器在编译时实行的,编译器内部永远把所有类型T视为Object处理,但是,在需要转型的时候,编译器会根据T的类型自动为我们实行安全地强制转型。

Java泛型的局限

  • <T>不能是基本类型,例如int,因为实际类型是Object,Object类型无法持有基本类型,实在有需要可以用包装类
  • 无法取得带泛型的Class,因为取到的全是在T是Object情况下的Class。
  • 无法判断带泛型的Class,原因同上,只有唯一的 类名.class,例如:x instanceof Pair<String>;不能判断x是否是 Pair<String>类型还是Pair<Object>
  • 不能实例化T类型,name = new T();等价于 name = new Object();
  • 也不能直接创建泛型数组T[],因为擦拭后代码变为Object[]

extends通配符

Pair<Integer>不是Pair<Number>的子类,故在需要Pair<Number>的类中无法传入值Pair<Integer>,有没有办法使得方法参数接受Pair<Integer>?办法是有的,这就是使用Pair<? extends Number>使得方法接收所有泛型类型为Number或Number子类的Pair类型。
这样一来,给方法传入Pair<Integer>类型时,它符合参数Pair<? extends Number>类型。这种使用<? extends Number>的泛型定义称之为上界通配符(Upper Bounds Wildcards),即把泛型类型T的上界限定在Number了。使用类似<? extends Number>通配符作为方法参数时有两个特性:

  • 方法内部可以调用获取Number引用的方法,例如:Number n = obj.getFirst();
  • 方法内部无法调用传入Number引用的方法(null除外),例如:obj.setFirst(Number n);

即一句话总结:使用extends通配符表示可以读,不能写。

super通配符

与extends通配符相反,我们这次希望接受Pair<Integer>类型,以及Pair<Number>、Pair<Object>,因为Number和Object是Integer的父类,可以使用<? super Integer>,注意到Pair<? super Integer>表示,方法参数接受所有泛型类型为Integer或Integer父类的Pair类型。使用<? super Integer>通配符表示:

  • 允许调用set(? super Integer)方法传入Integer的引用
  • 不允许调用get()方法获得Integer的引用,因为无法使用Integer类型来接收getFirst()的返回值(? super Integer万一是个Number,Integer接不住),唯一例外是可以用Object获取引用:Object o = p.getFirst()

即一句话总结:使用super 通配符表示只能写,不能读。

对比extends和super通配符

  • <? extends T>允许调用读方法T get()获取T的引用,但不允许调用写方法set(T)传入T的引用(传入null除外)
  • <? super T>允许调用写方法set(T)传入T的引用,但不允许调用读方法T get()获取T的引用(获取Object除外)
  • 一个是允许读不允许写,另一个是允许写不允许读

PECS原则

何时使用extends,何时使用super?为了便于记忆,我们可以用PECS原则:Producer Extends Consumer Super
即:如果需要返回T,它是生产者(Producer),要使用extends通配符;如果需要写入T,它是消费者(Consumer),要使用super通配符。

无限定通配符

Java的泛型还允许使用无限定通配符(Unbounded Wildcard Type),即只定义一个? 因为<?>通配符既没有extends,也没有super,因此:

  • 不允许调用set(T)方法并传入引用(null除外)
  • 不允许调用T get()方法并获取T引用(只能获取Object引用)
  • 既不能读,也不能写,那只能做一些null判断
  • <?>通配符有一个独特的特点,就是:Pair<?>是所有Pair的超类

注意事项

  • 可以把ArrayList<Integer>向上转型为List<Integer>(T不能变!),但不能把ArrayList<Integer>向上转型为ArrayList<Number>(T不能变成父类)
  • 不指定泛型参数类型时,编译器会给出警告,且只能将<T>视为Object类型;
  • 静态方法不能引用泛型类型<T>,必须定义其他单独的类型(例如<K>)来实现静态泛型方法
  • 泛型可以同时定义多种类型,例如Map<K, V>
  • 反射允许你在运行时分析任意的对象,但如果对象是泛型类的实例,关于泛型类型参数则得不到太多信息,因为它们会被擦除
  • 可以声明带泛型的数组,但不能用new操作符直接创建带泛型的数组,必须通过强制转型实现带泛型的数组:Pair<String>[ ] ps = new Pair<String>[2];//error Pair<String>[ ] ps = (Pair<String>[ ]) new Pair[2]; //ok!
  • 可以通过Array.newInstance(Class, int)创建T[ ]数组,需要强制转型
  • 未完待续。。。
发布了59 篇原创文章 · 获赞 8 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_22136439/article/details/104344376