你了解泛型通配符与上下界吗?

在进入主题之前, 我们先简单说一下 Java 的泛型(generics)。它是JDK 5中引入的一个新特性,允许在定义类和接口的时候使用类型参数(type parameter)。声明的类型参数在使用时用具体的类型来替换。泛型最主要的应用是在JDK 5中的新集合类框架中。

今天我们主要说如下内容:

  • 泛型的背景
  • 通配符以及上下界
  • 泛型及通配符的使用场景

为什么使用泛型及背后的问题?
我们来看一下官方的说法:

Stronger type checks at compile time.A Java compiler applies strong type checking to generic code and issues errors if the code violates type safety. Fixing compile-time errors is easier than fixing runtime errors, which can be difficult to find.

Elimination of casts.

Enabling programmers to implement generic algorithms.By using generics, programmers can implement generic algorithms that work on collections of different types, can be customized, and are type safe and easier to read.

是的, 终止目的就是想把程序员解放出来,关注他们更应该关注的事情上面去。当我第一次学习 Java 的泛型时,总感觉它类似于 C++ 中的模板。但随着慢慢的深入了解发现它们之间有本质的区别。

Java 中的泛型基本上完全在编译器中实现,由编译器执行类型检查和类型推断,然后生成普通的非泛型的字节码。这种实现技术称为 擦除(erasure)(编译器使用泛型类型信息保证类型安全,然后在生成字节码之前将其清除),这项技术有一些奇怪,并且有时会带来一些令人迷惑的后果。

对于泛型概念的引入,开发社区的观点是褒贬不一。从好的方面来说,上面已经说了,主要是在编译时刻就能发现很多明显的错误。而从不好的地方来说,主要是为了保证与旧有版本的兼容性,Java 泛型的实现上存在着一些不够优雅的地方。

下面我们来看一下,泛型类型的一个定义,后面我们要在这个的基础上进行改造
public class Box<T> {

// T stands for "Type"

private T t;

public Box(T t) ? ?{ this.t = t;? }?

public void set(T t) { this.t = t; }

public T get() { return t; }

}:

接下来下面我们来聊聊 Java 泛型的通配符, 记得刚开始看到通配符(?)时我是惊喜的,因为既然有通配符那么就可以这样定义:

public void doSometing(List<?> list) {

list.add(1); //illegal

}

可是我们如上写法,总是出现编译错误,然后从惊喜变成惊吓,心想有什么卵用了。最后发现原因是在于通配符的表示的类型是未知的。那在这种情况下,我们可以使用上下界来限制未知类型的范围。好吧,写了那么多, 终于等到今天的主角登场了,容易吗?

还记得我们上面定义的 Box 吗, 现在我们再定义 Fruit 类以及它的子类 Orange 类。

class Fruit { }

class Orange extends Fruit {}

现在我们想它里面能装水果,那么我可以这么写。
Box<Fruit> box = Box<Orange>(new Orange) //illegal

不幸的是编译器会报错,这就尴尬了,why?why? why?实际上,编译器认为的容器之间没有继承关系。所以我们不能这样做。

为了解决这样的问题, 大神们想出来了<? extens T> 和 <? super T> 的办法,来让它们之间发生关系。
上界通配符(Upper Bounded Wildcards)
现在我们把上面的 Box 定义改成:
Box<? extends Fruit>

这就是上界通配符, 这样 Box 及它的子类如 Box 就可以赋值了。
Box<? extends Fruit> box = new Box<Orange>(new Orange)

当我们扩展一下上面的类, 食物分成为水果和蔬菜类, 水果有苹果和橘子。
在上面的结构中, Box<? extends Fruit> 涵盖下面的蓝色的区域。

上界只能外围取,不能往里放
我们先看一下下面的例子:

Box<? extends Fruit> box = new Box<Orange>(new Orange);

//不能存入任何元素

box.set(new Fruit); //illegal

box.set(new Orange);//illegal

//取出来的东西只能存放在Fruit或它的基类里

Fruit fruit = box.get();

Object fruit1 = box.get();

Orange fruit2 = box.get(); //illegal

上面的注释已经很清楚了, 往 Box 里放东西的 set() 方法失效, 但是 get() 方法有效。

原因是 Java 编译器只知道容器内是 Fruit 或者它的派生类, 但是不知道是什么类型。可能是 Fruit、 可能是 Orange、可能是Apple?当编译器在看到 box 用 Box 赋值后, 它就把容器里表上占位符 “AAA” 而不是 “水果”等,当在插入时编译器不能匹配到这个占位符,所有就会出错。
下界通配符(Lower Bounded Wildcards)
和上界相对的就是下界 ,语法表示为:
<? super T>

表达的相反的概率:一个能放水果及一切水果基类的 Box。 对应上界的那种图, 下图 Box<? super Fruit> 覆盖×××区域。

下界不影响往里存,但往外取只能放在Object 对象里
同上界的规则相反,下界不影响往里存,但往外取只能放在Object 对象里。

因为下界规定元素的最小的粒度,实际上是容器的元素的类型控制。所以放比 Fruit 粒度小的如 Orange、Apple 都行, 但往外取时, 只有所有类的基类Object对象才能装下。但是这样的话,元素的类型信息就全部消失了。

使用场景
在使用泛型的时候可以遵循一些基本的原则,从而避免一些常见的问题。

在代码中避免泛型类和原始类型的混用。比如List

和List不应该共同使用。这样会产生一些编译器警告和潜在的运行时异常。当需要利用JDK 5之前开发的遗留代码,而不得不这么做时,也尽可能的隔离相关的代码。
在使用带通配符的泛型类的时候,需要明确通配符所代表的一组类型的概念。由于具体的类型是未知的,很多操作是不允许的。

泛型类最好不要同数组一块使用。你只能创建new List<?>[10]这样的数组,无法创建new List[10]这样的。这限制了数组的使用能力,而且会带来很多费解的问题。因此,当需要类似数组的功能时候,使用集合类即可。

不要忽视编译器给出的警告信息。

PECS 原则
如果要从集合中读取类型T的数据, 并且不能写入,可以使用 上界通配符(<?extends>)—Producer Extends。

如果要从集合中写入类型T 的数据, 并且不需要读取,可以使用下界通配符(<? super>)—Consumer Super。

如果既要存又要取, 那么就要使用任何通配符。

原文地址:http://blog.51cto.com/13917525/2323714

时间: 2024-11-10 18:49:26

你了解泛型通配符与上下界吗?的相关文章

java泛型中的上、下界通配符

java泛型中,通配符?表示未知类型,等同于<? extends Object>,<? extends T>是上边界限定通配符,<? super T>是下边界限定通配符. 一.区别 在一个list中,上下界通配符能够存放和读取的对象类型如下图所示: 二.原则 上下界通配符的使用应当遵循PECS原则:Producer Extends,Consumer Super. 限定通配符总是包括自己 上界类型通配符:add方法受限 下界类型通配符:get方法受限 如果你想从一个数据类

java泛型通配符?

转自:http://www.linuxidc.com/Linux/2013-10/90928.htm T  有类型 ?  未知类型 一.通配符的上界 既然知道List<Cat>并不是List<Anilmal>的子类型,那就需要去寻找替他解决的办法, 是AnimalTrianer.act()方法变得更为通用(既可以接受List<Animal>类型,也可以接受List<Cat>等参数).在java里解决办法就是使用通配符"?",具体到Anim

泛型通配符

1.通配符 泛型通配符有两种 上界通配符<? extends xx> 下界通配符<? super xx> 2.举例说明 先上代码吧 class A{ } class B extends A{ } class C extends A{ } public class NonCovariantGenerics { public static void main(String[] args) { List<? extends A> list1= new ArrayList<

Java知多少(42)泛型通配符和类型参数的范围

本节先讲解如何限制类型参数的范围,再讲解通配符(?). 类型参数的范围 在泛型中,如果不对类型参数加以限制,它就可以接受任意的数据类型,只要它是被定义过的.但是,很多时候我们只需要一部分数据类型就够了,用户传递其他数据类型可能会引起错误.例如,编写一个泛型函数用于返回不同类型数组(Integer 数组.Double 数组等)中的最大值: 1 public <T> T getMax(T array[]){ 2 T max = null; 3 for(T element : array){ 4 m

延伸 -- 泛型 -- 通配符的使用

通配符有三种: 1.无限定通配符   形式<?> 2.上边界限定通配符 形式< ? extends Number>    //用Number举例 3.下边界限定通配符    形式< ? super Number>    //用Number举例 1.泛型中的?通配符 如果定义一个方法,该方法用于打印出任意参数化类型的集合中的所有数据,如果这样写 package limeGenericity.genericityPrescribe; import java.util.Arr

Java泛型_上界extends_下界super

?Java泛型_上界extends_下界super ? 通配符类型 <? extends T> 表示类型的上界,表示参数化类型的可能是T或是T的子类 <? super T> 表示类型下界(Java Core中叫超类型限定),表示参数化类型是此类型(T)的超类型(父类型),直至Object 当使用 Upper Bound 通配符时 如下代码, /**  * 代码中通配符<?> 是 <? extends Object> 的简写  *  * @param list

java泛型通配符和类型参数的范围

本节先讲解如何限制类型参数的范围,再讲解通配符(?). 类型参数的范围 在泛型中,如果不对类型参数加以限制,它就可以接受任意的数据类型,只要它是被定义过的.但是,很多时候我们只需要一部分数据类型就够了,用户传递其他数据类型可能会引起错误.例如,编写一个泛型函数用于返回不同类型数组(Integer 数组.Double 数组等)中的最大值: public <T> T getMax(T array[]){ T max = null; for(T element : array){ max = ele

理解Java泛型 通配符 ? 以及其使用

什么是泛型: 泛型从字面上理解,是指一个类.接口或方法支持多种类型,使之广泛化.一般化和更加通用.Java中使用Object类来定义类型也 能实现泛型,但缺点是造成原类型信息的丢失,在使用中容易造成ClassCastException. Java泛型带到的好处: 使得一个类或方法中的类型参数化,最终达到代码复用的效果.( 不使用泛型,你可能需要每种情况的类或方法都要定义一遍 ) 实现类型检查的功能,避免ClassCastException.(这是相对于使用Object类型实现泛型而言.因为我可以

JAVA泛型通配符T,E,K,V区别,网友回复:一文秒懂

先解释下泛型概念泛型是Java SE 1.5的新特性,泛型的本质是参数化类型,也就是说所操作的数据类型被指定为一个参数.这种参数类型可以用在类.接口和方法的创建中,分别称为泛型类.泛型接口.泛型方法.Java语言引入泛型的好处是安全简单. 在Java SE 1.5之前,没有泛型的情况的下,通过对类型Object的引用来实现参数的"任意化","任意化"带来的缺点是要做显式的强制类型转换,而这种转换是要求开发者对实际参数类型可以预知的情况下进行的.对于强制类型转换错误的