Java泛型
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了Java泛型,小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含7554字,纯文字阅读大概需要11分钟。
内容图文
![Java泛型](/upload/InfoBanner/zyjiaocheng/695/134e74f45a1b40f590c2c9a9ab72c192.jpg)
泛型基础
泛型类
我们首先定义一个简单的Box类:
1 | public class { |
这是最常见的做法,这样做的一个坏处是Box
里面现在只能装入String
类型的元素,今后如果我们需要装入Integer
等其他类型的元素,还必须要另外重写一个Box
,代码得不到复用,使用泛型可以很好的解决这个问题。
1 | public class <T> { |
这样我们的Box类便可以得到复用,我们可以将T替换成任何我们想要的类型:
1 | Box<Integer> integerBox = new Box<Integer>(); |
泛型方法
看完了泛型类,接下来我们来了解一下泛型方法。声明一个泛型方法很简单,只要在返回类型前面加上一个类似<K, V>
的形式就行了:
1 | public class Util { |
或者在Java1.7/1.8利用type inference
,让Java自动推导出相应的类型参数:
1 | Pair<Integer, String> p1 = new Pair<>(1, "apple"); |
边界符
现在我们要实现这样一个功能,查找一个泛型数组中大于某个特定元素
的个数,我们可以这样实现:
1 | public static <T> int countGreaterThan(T[] anArray, T elem) { |
但是这样很明显是错误的,因为除了short, int, double, long, float, byte, char等原始类型,其他的类并不一定能使用操作符>,所以编译器报错,那怎么解决这个问题呢?答案是使用边界符:
1 | public interface Comparable<T> { |
做一个类似于下面这样的声明,这样就等于告诉编译器类型参数T代表的都是实现了Comparable接口的类,这样等于告诉编译器它们都至少实现了compareTo方法
1 | public static <T extends Comparable<T>> int countGreaterThan(T[] anArray, T elem) { |
通配符
在了解通配符之前,我们首先必须要澄清一个概念,还是借用我们上面定义的Box
类,假设我们添加一个这样的方法:
1 | public void boxTest (Box<Number> n) { /*...*/ } |
那么现在Box<Number> n
允许接受什么类型的参数?我们是否能够传入Box<Integer>
或者Box<Double>
呢?答案是否定的,虽然Integer
和Double
是Number
的子类,但是在泛型中Box<Integer>
或者Box<Double>
与Box<Number>
之间并没有任何的关系。这一点非常重要,接下来我们通过一个完整的例子来加深一下理解:
首先我们先定义几个简单的类,下面我们将用到它:
1 | class Fruit {} |
下面这个例子中,我们创建了一个泛型类Reader
,然后在f1()
中当我们尝试Fruit f = fruitReader.readExact(apples);
编译器会报错,因为List<Fruit>
与List<Apple>
之间并没有任何的关系。
1 | public class GenericReading { |
但是按照我们通常的思维习惯,Apple
和Fruit
之间肯定是存在联系,然而编译器却无法识别,那怎么在泛型代码中解决这个问题呢?我们可以通过使用通配符
来解决这个问题:
1 | static class CovariantReader<T> { |
这样就相当与告诉编译器,fruitReader
的readCovariant
方法接受的参数只要是满足Fruit
的子类就行(包括Fruit自身)
,这样子类
和父类
之间的关系也就关联上了。
PECS原则
上面我们看到了类似<? extends T>
的用法,利用它我们可以从list
里面get
元素,那么我们可不可以往list
里面add
元素呢?我们来尝试一下:
1 | public class GenericsAndCovariance { |
答案是否定,Java编译器不允许我们这样做,为什么呢?对于这个问题我们不妨从编译器的角度去考虑。因为List<? extends Fruit> flist
它自身可以有多种含义:
1 | List<? extends Fruit> flist = new ArrayList<Fruit>(); |
- 当我们尝试
add一个Apple
的时候,flist
可能指向new ArrayList<Orange>()
; - 当我们尝试
add一个Orange
的时候,flist
可能指向new ArrayList<Apple>()
; - 当我们尝试
add一个Fruit
的时候,这个Fruit
可以是任何类型的Fruit
,而flist
可能只想某种特定类型的Fruit
,编译器无法识别所以会报错。
所以对于实现了<? extends T>
的集合类只能
将它视为Producer向外提供(get)元素
,而不能作为Consumer来对外获取(add)元素
。
如果我们要add
元素应该怎么做呢?可以使用<? super T>
:
1 | public class GenericWriting { |
这样我们可以往容器里面添加元素了,但是使用super
的坏处是以后不能get容器里面的元素了
,原因很简单,我们继续从编译器的角度考虑这个问题,对于List<? super Apple> list
,它可以有下面几种含义:
1 | List<? super Apple> list = new ArrayList<Apple>(); |
当我们尝试通过list
来get
一个Apple
的时候,可能会get
得到一个Fruit
,这个Fruit
可以是Orange
等其他类型的Fruit
。
根据上面的例子,我们可以总结出一条规律,”Producer Extends, Consumer Super”`:
“Producer Extends”
: 如果你需要一个只读List
,用它来produce T
,那么使用? extends T
。“Consumer Super”
: 如果你需要一个只写List
,用它来consume T
,那么使用? super T
。- 如果需要同时读取以及写入,那么我们就不能使用通配符了。
如果阅读过一些Java
集合类的源码,可以发现通常我们会将两者结合起来一起用,比如像下面这样:
1 | public class Collections { |
内容总结
以上是互联网集市为您收集整理的Java泛型全部内容,希望文章能够帮你解决Java泛型所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。