Java泛型食用笔记(四) -- 通配符

Java泛型食用笔记(四) -- 通配符


1. 三种通配符

通配符为一个泛型类所指定的类型集合提供了一个有用的类型范围,Java 里有三种通配符:

  • 无限定通配符, <?>
  • 上界限定符, <? extends Number>
  • 下界限定符, <? super Number>

上界限定符接受 extends 后面类的本身与其子类, 下界限定符接受 super 后面类的本身与其父类。无限定通配符接受任何类。

2. 无限定通配符

无限定通配符表示匹配任意类。ArrayList<?>ArrayListArrayList<Object> 看上去功能有点类似,但实际却不一样。 ArrayList<?> 是任意 ArrayList<T> 的超类,而我们知道 ArrayList<Object> 并不是。ArrayList<?> 虽然可以匹配任何类,我们并不知道那个类的类型,但我们知道里面的所有元素都有相同的类。而原始类 ArrayList 可以添加任意不同类型的元素,编译器并不能进行类型判断,但运行的时候可能会抛出异常。ArrayList<Object> 明确的告诉我们可以添加任何类型的对象。

看一个无限定通配符例子

public class GenericTest07 {
    public static void printCollection(Collection<Object> col1) {
        for (Object obj : col1) {
            System.out.println(obj);
        }
    }

    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        printCollection(list);  // compile error
    }
}

由于 ArrayList<Integer> 不是 Collection<Object> 的子类,故编译不能通过。改用无限定通配符即可编译通过:

public class GenericTest07 {
    public static void printCollection(Collection<?> col1) {
        for (Object obj : col1) {
            System.out.println(obj);
        }
    }

    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        printCollection(list);  
    }
}

Collection<?> 是任意 T ArrayList<T> 的超类,可以编译通过。但是,在printCollection 方法中,却不能使用任何带有类型参数的方法,比如 add(T t), 而 remove(Object obj) contains(Object obj) 等方法都是可以调用的。

再看一个例子:

public class GenericTest08 {
    public static void reBox(Box<?> box) {
        System.out.println(box.put(box.get()));
    }
}

public interface Box<T> {
    public T get();
    public void put(T t);
}

这段代码初看应该是 work 的,然而事实可能要让你失望了,编译会提示类型不兼容的错误。事实上,你根本就不能在此处调用 box.put() 方法,因为box.put() 的形参类型是未知的,编译器不能检验你的实参类型是否与形参类型一致。

有没有办法实现这个逻辑的,我们需要借助一个辅助方法。

public class GenericTest08 {
    public static void reBox(Box<?> box) {
        reboxHelper(box);
    }

    private static <V> void reboxHelper(Box<V> box) {
        box.put(box.get());
    }
}

reboxHelper 方法帮助编译器保留一部分类型信息。

上界限定符

我们知道,和数组不一样,泛型并不是协变的。比如 DogAnimal 的子类,那 Dog[] 也是 Animal[] 的子类,但 List<Dog> 并不是 List<Animal> 的子类。这个时候上界限定符的作用就体现出来了,写法是 List<? extends Animal>,可以解释为“可以放入任何 Animal 及其子类的列表”,之前讲到泛型编译后会抹除类型参数成 Object 类型,当你使用上界限定符后,就抹除成上界。

看代码

public class GenericTest {
    public static void main(String[] args) {
        ArrayList<Integer> holder = new ArrayList<Integer>();
        holder.add(new Integer(1));

        // ArrayList<Number> numHolder = holder;  // 1. compile failed. ArrayList<Number> 不是 ArrayList<Integer> 的父类
        ArrayList<? extends Number> numHolder = holder; // 2. ok
        Number num = numHolder.get(0);  // 3. ok. return Number
        numHolder.contains(new Integer(2)); // 4. ok
        // numHolder.add(new Integer(2));  // 5. compile error
    }
}

1 处编译错误是因为泛型不是协变;Integer 是 Number 子类,故 2 处正确;3 处正确,正如上段所说,上界限定符抹除成上届,返回类型为 Number;4 处 ok,是因为 contains 方法接受的是 Object 类。5 处需要稍微注意,add 的形参类型也是 <? extends Number>,编译器只能知道类型是 Number 的子类,并不能确定具体类型是什么,因此无法验证类型的安全性。

下界限定符

下界限定符也称为超类通配符,写法是 List<? super Dog>, 列表可以接受 Dog 类型及其父类对象。
上一接的第 5 处,如果向往里添加元素,需要使用下界限定符。

public void writeTo(List<? super Integer> list) {
    list.add(new Integer(2));  // ok
}

<? super Integer> 说明类型参数一定是 Integer 的父类。因此向里添加 Integer 类或其子类的对象一定是安全的。

PECS 原则

根据上面的例子,我们看到,使用上界限定符定义的类,可以向外提供东西,也就是说作为 Producer。使用下界限定符定义的类,可以作为 Consumer 接收外部往自身添加东西。

总结起来就是, "Producer Extends, Consumer Super":

  • "Producer Extends" - 如果你需要一个只读类型,用它来produce T,那么使用<? extends T>
  • "Consumer Super" - 如果你需要一个只写类型,用它来consume T,那么使用<? super T>
  • 如果需要同时读取以及写入,那么我们就不能使用通配符了

下面一个方法同时涉及了这两条规则。

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

推荐阅读更多精彩内容