写点什么

15 个问题告诉你如何使用 Java 泛型

发布于: 2021 年 04 月 28 日

摘要: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 的子类都可以转成 Parentlist.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(newChild())所做的操作是在输入时, 把外部的 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 元素的父类容器。


点击关注,第一时间了解华为云新鲜技术~

发布于: 2021 年 04 月 28 日阅读数: 26
用户头像

提供全面深入的云计算技术干货 2020.07.14 加入

华为云开发者社区,提供全面深入的云计算前景分析、丰富的技术干货、程序样例,分享华为云前沿资讯动态,方便开发者快速成长与发展,欢迎提问、互动,多方位了解云计算! 传送门:https://bbs.huaweicloud.com/

评论

发布
暂无评论
15个问题告诉你如何使用Java泛型