无界通配符

无界通配符

无界通配符<?> 看起来意味着“任何事物”,因此使用无界通配符好像等价于使用原生类型。而事实上,编译器初看起来是支持这种判断的。

public class UnBoundedWildcards {
    static List list1;
    static List<?> list2;
    static List<? extends Object> list3;

    static void assign1(List list) {
        list1 = list;
        list2 = list;

        // warning: unchecked conversion
        // Found: List, Require: List<? extends Object>
        list3 = list;
    }

    static void assign2(List<?> list) {
        list1 = list;
        list2 = list;
        list3 = list;
    }

    static void assign3(List<? extends Object> list) {
        list1 = list;
        list2 = list;
        list3 = list;
    }


    public static void main(String[] args) {
        assign1(new ArrayList());
        assign2(new ArrayList());
        // warning: unchecked conversion
        // Found: ArrayList, Require: List<? extends Object>
        assign3(new ArrayList());

        assign1(new ArrayList<String>());
        assign2(new ArrayList<String>());
        assign3(new ArrayList<String>());

        List<?> wildList = new ArrayList();
        assign1(wildList);
        assign2(wildList);
        assign3(wildList);
    }
}

编译器很少关心使用的是原生类型还是<?>,而在这种情况下,<?>可以被认为是一种装饰,但是它仍旧是很有价值的,因为,实际上,它是在声明:“想用Java的泛型来编写代码,但是我在这里并不是要用原生类型,但是在当前这种情况下,泛型参数可以持有任何类型。”


下面的示例展示了无界通配符的一个重要的应用。当你处理多个泛型参数的时,有时允许一个参数可以是任何的类型,同时为其他参数确定某种特定类型的能力会显得尤为重要。

public class UnBoundedWildcards2 {
    static Map map1;
    static Map<?, ?> map2;
    static Map<String, ?> map3;

    static void assign1(Map map) {
        map1 = map;
    }

    static void assign2(Map<?, ?> map) {
        map2 = map;
    }

    static void assign3(Map<String, ?> map) {
        map3 = map;
    }

    public static void main(String[] args) {
        assign1(new HashMap());
        assign2(new HashMap());
        // warning
        // Unchecked assignment: 'java.util.HashMap' to 'java.util.Map<java.lang.String,?>'
        assign3(new HashMap());
        
        assign1(new HashMap<String, Integer>());
        assign2(new HashMap<String, Integer>());
        assign3(new HashMap<String, Integer>());
    }
}

但是,当拥有的全都是无界通配符的时候,就像在Map<?,?>中看到的那样,编译器看起来无法将其与原生Map区分开了。而另外UnBoundedWildcards.java中也展示了编译器处理List<?>List<? extends Object>时是不同的。
而令人困惑的是,编译器并非总是关注像ListList<?>之间的差异,因此它们看起来就像是相同的事物。事实上,由于泛型参数将擦除到它的第一个边界,因此List<?>看起来就等价于List<Object>,而List实际上也是List<Object>List实际上是表示“持有任何Object类型的原生List”,而List<?>表示“具有某种特定类型”的非原生List,并且我们不知道哪个类型是什么。
那么问题来了,编译器什么时候才会去关注原生类型和涉及无界通配符的类型之间的差异呢?下面的示例使用了之前定义的Holder<T>类,它包含接受Holder作为参数的各种方法,但是它们具有不同的形式,作为原生类型,具有具体的类型参数以及具有无界通配符参数。

public class Wildcards {

    static void rawArgs(Holder holder, Object arg) {
        // unchecked warning
        // holder.set(arg);
        // same warning
        // holder.set(new Wildcards());

        // compile error, don't have any T
        // T t = holder.get();

        // No warning, but type information has been lost
        Object obj = holder.get();
    }


    // 和rawArgs相似,但是其中的warning会变成编译错误
    static void unboundedArg(Holder<?> holder, Object arg) {
        // Compile Error set(capture of ?) but accept Object
        // holder.set(arg);
        // same error
        // holder.set(new Wildcards());

        // compile error, don't have any T
        // T t = holder.get();

        // No warning, but type information has been lost
        Object obj = holder.get();
    }

    static <T> T exact1(Holder<T> holder) {
        T t = holder.get();
        return t;
    }

    static <T> T exact2(Holder<T> holder, T arg) {
        holder.set(arg);
        T t = holder.get();
        return t;
    }

    static <T> T wildSubtype(Holder<? extends T> holder, T arg) {
        // Error
        // set (capture<? extends T>) in Holder cannot be applied to (T)
        // holder.set(arg);
        T t = holder.get();
        return t;
    }

    static <T> void wildSupertype(Holder<? super T> holder, T arg) {
        holder.set(arg);
        // Error
        // Required: T Found: capture<? super T>
        // T t = holder.get();

        // No warning, but type information has been lost
        Object obj = holder.get();
    }

    public static void main(String[] args) {
        Holder raw = new Holder<Long>();
        // Or
        raw = new Holder();
        Holder<Long> qualified = new Holder<>();
        Holder<?> unbounded = new Holder<Long>();
        Holder<? extends Long> bounded = new Holder<Long>();
        Long lng = 1L;

        rawArgs(raw, lng);
        rawArgs(qualified, lng);
        rawArgs(unbounded, lng);
        rawArgs(bounded, lng);

        unboundedArg(raw, lng);
        unboundedArg(qualified, lng);
        unboundedArg(unbounded, lng);
        unboundedArg(bounded, lng);

        // unchecked warning
        Object r1 = exact1(raw);
        Long r2 = exact1(qualified);
        Object r3 = exact1(unbounded);
        Long r4 = exact1(bounded);

        // unchecked warning
        Long r5 = exact2(raw, lng);
        Long r6 = exact2(qualified, lng);
        // Error 参数错误
        // Long r7 = exact2(unbounded, lng);
        // Error 参数错误
        // Long r8 = exact2(bounded, lng);

        // unchecked warning
        Long r9 = wildSubtype(raw, lng);
        Long r10 = wildSubtype(qualified, lng);
        Object r11 = wildSubtype(unbounded, lng);
        Long r12 = wildSubtype(bounded, lng);

        // unchecked warning
        wildSupertype(raw, lng);
        wildSupertype(qualified,lng);
        // Error 参数错误
        // wildSupertype(unbounded,lng);
        // wildSupertype(bounded,lng);
    }
}

rawArgs()中,编译器知道Holder是一种泛型类型,因此即使它在这里被表示为一个原生类型,编译器仍旧知道向set()传递一个Object是不安全的。由于它是原生类型,可以将任何类型的对象传递给set(),而这个对象将向上转型为Object。因此,无论何时,只要使用了原生类型,都会放弃编译器检查。而对get()的调用说明了相同的问题:没有T类型的对象,因此结果只能是一个Object
人们很自然地会开始考虑原生HolderHolder<?>是大致相同的事物。但是uboundedArg()强调它们是不同的——它揭示了相同的问题,但是它将这些问题作为错误而不是警告报告,因为原生Holder将持有任何类型的组合,而Holder<?>将持有某种具体类型的同构集合,因此不能只是向其中传递Object
exact1()exact2()中,都使用了确切的泛型参数——没有任何的通配符。而exact2()exact1()多了一个额外的参数。
wildSubType()中,在Holder类型上的限制被放松为持有任何扩展至T对象的Holder,这还是意味着如果TFruit,那么holder可以是Holder<Apple>。而为了防止将Orange放置到Holder<Apple>中,对set()的调用(或者对任何接受这个类型参数为参数的方法的调用)都是不允许的。但是,你仍然知道任何来自Holder<? extends Fruit>的对象至少是Fruit,因此get()(或者任何将产生具有这个类型参数的返回值的方法)都是允许的。
wildSupertype()展示了超类型通配符,这个方法展示了和wildSubtype()相反的行为:holder可以是持有任何T的基类型的容器,所以,set可以接受T,因为可以工作于基类的对象都可以多态地作于导出类(这里指的是T)。但是,尝试着调用get()是没有用的,因为由holder持有的类型是任何超类型,唯一安全的是Object
这个示例还展示了对于unbounded()中使用无界通配符能够做不能上吗做什么所做出的限制。对于迁移兼容性rawArgs()将接受所有Holder的不同变体,而不会产生警告。unboundedArg()方法也可以接受相同的所有类型,尽管如前所述,它在方法体内部处理这些类型的防暑并不相同。
如果向接受“确切”泛型类型(没有通配符)的方法传递一个原生Holder引用,就会得到一个警告,因为确切的参数期望得到在原生类型中并不存在的信息。如果向exact1()传递一个一个无界引用,就不会有任何可以确定返回类型的信息。
而可以看到,exact2()具有最多的限制,因为它希望精确地得到一个Holder<T>,以及一个具有类型T的参数,正因为如此,它将产生错误或者警告,除非提供确切的参数。虽然有时这样做很好,但是如果它过于受限,那么就可以使用通配符,这取决于是否想从泛型参数中返回类型确定的返回值(就像在wildSubtype中看到的那样),或者是否想要向泛型参数传递类型确定的参数(就像在wildSupertype()中看到那样)。
因此,使用确切类型来替代通配符的好处是,可以用泛型参数来做更多的事,但是使用通配符使得必须接受范围更宽的参数化类型作为参数。因此必须权衡利弊,找到适合的方法。

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

推荐阅读更多精彩内容

  • 通配符 首先,要展示数组的一种特殊行为,可以向导出类型的数组赋予基类型的数组引用。 main()的第一行创建了一个...
    呆呆李宇杰阅读 386评论 0 0
  • 2.简单泛型 -********Java泛型的核心概念:告诉编译器想使用什么类型, 然后编译器帮你处理一切细节 2...
    CodingHou阅读 387评论 0 0
  • 前言 人生苦多,快来 Kotlin ,快速学习Kotlin! 什么是Kotlin? Kotlin 是种静态类型编程...
    任半生嚣狂阅读 26,131评论 9 118
  • 1. 泛型概述 泛型为JDK1.5之后sun公司推出的新功能,泛型可以消除源代码中的许多强制类型转换,泛型对于数据...
    yuan_dongj阅读 4,481评论 0 8
  • 昨天和小祖宗聊天,她说她找不到她真正开怀大笑的照片了。我翻开空间那个“仅对自己可见”的相册,看了许久,自从有自拍以...
    素晨Viory阅读 439评论 2 1