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

在进入主题之前, 我们先简单说一下 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 涵盖下面的蓝色的区域。


上界只能外围取,不能往里放

我们先看一下下面的例子:

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 覆盖黄色区域。


下界不影响往里存,但往外取只能放在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。

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容