摘要:Java泛型其本质是参数化类型,也就是说所操作的数据类型被指定为一个参数(type parameter)这种参数类型能够用在类、接口和办法的创立中,别离称为泛型类、泛型接口、泛型办法。

本文分享自华为云社区《15个问题把握java泛型》,原文作者:breakDraw 。

Java泛型是J2 SE1.5中引入的一个新个性,其本质是参数化类型,也就是说所操作的数据类型被指定为一个参数(type parameter)这种参数类型能够用在类、接口和办法的创立中,别离称为泛型类、泛型接口、泛型办法。

泛型办法

个别定义如下,即办法的后面加了个<T>

public class FTest {    public <T> List<T> f(T t){...};}

三种泛型参数推断形式:

1、间接在f()后面加确定泛型

fTest.<Integer>f(xxx)
2、通过输出参数确定, 上面这个推断为Integer

int number = 0;fTest.f(number)

3、可通过 返回值 确定

List<Integer> list = fTest.f(xxx);
Q: 上面这段代码哪里有问题? 是toString()那里吗?

public class A<T> {    public static void  test(T t){          System.out.println(t.toString());    }}

A:test是static办法, 因而无奈感知A<T>实例里的T
须要改成
public static <T> void test(T t)

toString()那里没问题,toString就是Object的办法。

泛型参数和类型打消

Q: 泛型参数T在运行时,会变成什么?
A: 对立变成Object且不蕴含任何类型信息。

Q: 泛型参数T能够能够应用instanceof做比拟吗?

class A<T> {   void f(Object arg)   if(arg instanceof T) {      ...   }}

A: 不能,编译器会报错。

Q: 泛型参数T能够进行new T()或者new T[]操作吗?
A: 不能,编译器会报错。

Q: 能调用泛型参数对象里的办法吗?

T.f();
A: 只能调用Object的办法。

Q: 能够用T做强制转化吗?

T t = (T)object;
A: 能运行, 但不会真正产生转型, 编译时会触发waring正告。

新建泛型对象时的问题

先假设有2个类, 基类Parent 和子类Child

class Parent{}class Child extends Parent{}

答复以下问题:
Q:上面这句话有问题吗?

List<Parent> list = new ArrayList<Child>()

A:有问题,编译就谬误了。 List<Parent>和ArrayList<Child>并不存在父子类的关系

Q:

List<? extends Parent> list = new ArrayList<Child>();

这个list有什么特点?

A:这个list能够调用A a = list.get(), 然而不能list.add(new Parent())

  • 起因:
    list.get()所做的操作是在返回时, 把外部的<? extend Parent> 强转成Parent, 是正当的,任何Parent的子类都能够转成Parent
    list.add(new Parent())所做的操作是在输出时, 把内部的A转成外部的<? extend Parent>, 这是不合理的,因为咱们不晓得这个Parent对象能够转成哪个Parent的子类。
    Q:
List<? super Child> list = new ArrayList<Parent>();

这个list有什么特点?
上面谁会报错

list.add(new Child())list.add(new Parent())Parent a= list.get();Child b = list.get()

A:截图如下:

  • Child c = list.get() 或者Parent p = list.get()所做的操作是在返回时, 把外部的<? super Child> 强转成内部的Parent或者child, 是不合理的, 因为编译器感觉child的父类 不肯定 能转成parent或者child,所以禁止了这种行为( 比方parent的父类是object, 但object不肯定就能转成parent或者child)。*list.add(new Child())所做的操作是在输出时, 把内部的child或者parent转成外部的<? super Child>, 这是正当的,因为child和parent肯定能转成child的父类。

Q:

List<?> list = new ArrayList<A>();
这个list有什么特点?

A:get和add都不行,只能做remove等无返回值无输出A的操作。
PS: 留神,不是说不能调用get或add办法, 而是调用get或add时,不能应用A这个对象去操作。
即无奈做add(A) 或者 A a = get(0)
然而能够做add(object) 或者Object o = get(0)
因为?能够转为Object, 然而无奈转为A。

Q:上面这个代码会报错吗?

   List<Fruit> fruitList = new ArrayList<>();   fruitList.add(new Fruit());   List<Apple> appleList = new ArrayList<>();   appleList.add(new Apple());   fruitList.addAll(appleList);   System.out.println(fruitList);

A:不会报错。会失常打印后果。

PECS准则
留神PECS准则和下面的区别!
下面之前提到的? extend或者? supert, 都是在申明对象的时候用的。
而PECS准则是用于泛型对象的办法输出参数!

假如有一个类定义如下:

public static class MyList<T> {    List<T> list = new ArrayList<>();    // 把输出参数塞给本人,相似于生产操作    public void pushList(List<T> t) {        list.addAll(t);    }    // 把本人的内容塞给输出参数,相似于让输出参数做生产。    public void pollList(List<T> t) {         t.addAll(list);    }}

则T就是泛型参数。

Q:上面代码能失常运行吗?

MyList<Number> myList = new MyList<>();List<Integer> intList = new ArrayList<>();myList.pushList(intList);List<Object> objectList = new ArrayList<>();myList.pollList(objectList);

A:不能失常运行, pushList和pollList都会报错

因为编译器查看后,认为 List<Integer>和List<Number>不是一个货色!

Q: 如果上文要反对pushList,应该怎么批改pushList办法的定义?
A:改成这样:

// 把输出参数塞给本人,相似于生产操作public void pushList(List<? extends T> t) {    list.addAll(t);}

即编译器认为,List<Integer> 和List<? extend Number>是一个货色,容许!

Q: 如果要反对pollList,怎么批改定义?
A:

// 把本人的内容塞给输出参数,相似于让输出参数做生产。public void pollList(List<? super T> t) {    t.addAll(list);}

因为是把本人的货色塞给输出参数, 而想要能塞进去,必须保障本人这个T,是输出参数的子类,反过来说,输出参数必须是T的父类,所以用super
于是编译器认为,List<Object> 和List<? super Number>是一个货色,容许!

PECS准则出自Effective Java, 留神只是一个编程倡议而已!

  • 如果有一个类A,泛型参数为T
  • 如果他个别只用于接管输出容器List后,塞入本人外部的T容器, 则类A就叫生产者, 因而输出参数最好定义为<? extend T>最好, 以便能接管任何T子类的容器。
  • 如果他个别只用于接管输出容器后List, 把本人外部的T元素塞给它, 那么这个类A就叫消费者, 输出参数最好定义为<? super T>\ 最好, 以便本人的T元素能塞给任何T元素的父类容器。

点击关注,第一工夫理解华为云陈腐技术~