Java 泛型方法与通配符 其中的类型推断该如何理解? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
amiwrong123
V2EX    程序员

Java 泛型方法与通配符 其中的类型推断该如何理解?

  •   amiwrong123 2019 年 9 月 28 日 3487 次点击
    这是一个创建于 2296 天前的主题,其中的信息可能已经有所发展或是发生改变。
    class Holder<T> { private T value; public Holder() {} public Holder(T val) { value = val; } public void set(T val) { value = val; } public T get() { return value; } public boolean equals(Object obj) { return value.equals(obj); } } public class Wildcards { static <T> T exact2(Holder<T> holder, T arg) { holder.set(arg); T t = holder.get(); return t; } // 有界 extends 通配符的参数: static <T> T wildSubtype(Holder<? extends T> holder, T arg) { // holder.set(arg); // 编译错误,不能写操作 T t = holder.get();//只可以读操作 return t; } // 有界 super 通配符的参数: static <T> void wildSupertype(Holder<? super T> holder, T arg) { holder.set(arg);//只可以写操作 // T t = holder.get(); // 编译错误,不能读操作 // 本来是不能读操作的,但 super 有上限 Object,所以这是唯一合法的读操作: Object obj = holder.get(); } public static void main(String[] args) { Holder raw = new Holder<Long>(); // 上下两行都一样,反正泛型是伪泛型,重要的还是引用的类型 raw = new Holder(); Holder<Long> qualified = new Holder<Long>(); Holder<?> unbounded = new Holder<Long>(); Holder<? extends Long> bounded = new Holder<Long>(); Long lng = 1L; Long r5 = exact2(raw, lng); // unchecked 警告: // 类型推断为了 Long,所以第一个参数会有 unchecked 的警告 Long r6 = exact2(qualified, lng); //Long r7 = exact2(unbounded, lng); // 编译错误 //Long r8 = exact2(bounded, lng); // 编译错误 Long r9 = wildSubtype(raw, lng); // unchecked 警告 Long r10 = wildSubtype(qualified, lng); // 只能返回给 Object。因为传递进入的实参类型是无界通配符 Object r11 = wildSubtype(unbounded, lng); Long r12 = wildSubtype(bounded, lng); wildSupertype(raw, lng); // unchecked 警告 wildSupertype(qualified, lng); //wildSupertype(unbounded, lng); // 编译错误 //wildSupertype(bounded, lng); // 编译错误 } } ///:~ 

    此例来自于 java 编程思想,在主函数中 exact2 和 wildSupertype 的两处调用都会有编译错误,看了提示,感觉不是特别理解。而且相对的,wildSubtype 函数却可以执行成功? ulgDbD.png ulghKf.png

    两个报错都说了,Long 不能转换为?。这个该怎么理解,就是说,一个形参推断出来为 Long,一个形参推断出来为?,就不可以呗? exact2 函数还多说了句,两个形参推断出来的边界不一样。

    20 条回复    2019-09-30 11:33:08 +08:00
    putin541
        1
    putin541  
       2019 年 9 月 28 日
    我的理解是,类型的集合也可以看作是一种类型。? 是所有类型的集合,所以 Long != ?
    realPipiz
        2
    realPipiz  
       2019 年 9 月 28 日
    aguesuka
        3
    aguesuka  
       2019 年 9 月 28 日
    equals 写得应该有问题
    itechify
        4
    itechify  
    PRO
       2019 年 9 月 28 日 via Android
    https://liuzhicong.cn/index.php/study/extends-T-and-super-T.html

    之前我写的可能相关,太长的话,看最后的引用,stackoverflow 里面的第一第二个解答

    只要是类型安全问题,编译器经量往最安全的地方去检查你的代码
    Mistwave
        5
    Mistwave  
       2019 年 9 月 28 日 via iPhone
    wildSubtype
    holder 里是 T 的 subtype,所以你不能 set 一个 T 给 holder。就好比 holder 要持有 Dog,T 是 Animal,不是所有的 T 都能给 holder 去持有,比如 Duck 就不行。


    wildSupertype
    T 是 holder 里的 subtype,从 holder 里 get,当然不能赋值给一个 T。好比 T 是 Dog,holder 里是 Animal,当然是不能 holder.get()然后复制给 t 的。

    可以看看这篇讲协变和逆变的文章
    http://duanyifu.com/2019/08/30/variance/
    amiwrong123
        6
    amiwrong123  
    OP
       2019 年 9 月 28 日
    @putin541
    你的理解貌似也很有道理。但偏偏 wildSubtype 函数接受同样的实参,却不会报编译错误了,这该如何解释呢
    amiwrong123
        7
    amiwrong123  
    OP
       2019 年 9 月 28 日
    @realPipiz
    谢谢,看了。介绍泛型的知识很广,但没有我所疑问的点。
    amiwrong123
        8
    amiwrong123  
    OP
       2019 年 9 月 28 日
    @aguesuka
    怎么个有问题法呀
    amiwrong123
        9
    amiwrong123  
    OP
       2019 年 9 月 28 日
    @oneisall8955
    哈哈哈,上次你就给推荐过啦。看了,介绍了<? extends T> 及 <? super T>这两种引用的合法操作和限制操作,不错。但我这里,主要不懂在于,泛型方法上,涉及了通配符的类型推断,编译器为啥会报这个错==
    (悄悄地说,等会写博客准备把你那个 copy 的 jdk 例子写进去,哈哈哈)
    amiwrong123
        10
    amiwrong123  
    OP
       2019 年 9 月 28 日
    @Mistwave
    谢谢,你说这两点就是<? extends T> 及 <? super T>这两种引用的合法操作和限制操作了吧(一种合法的是“读操作”,另一个是“写操作”)。

    你这篇文章也不错,话说你们都喜欢看英文文档啊,我要是能看看 java 官网文档就不错了。。。

    主要还是不懂在于,泛型方法上,涉及了通配符(可能是形参涉及了、也可能是实参涉及了)的类型推断,编译器为啥会报这个错==
    Mistwave
        11
    Mistwave  
       2019 年 9 月 28 日 via iPhone
    @amiwrong123 这个很直观的:
    编译器报错==类型不安全
    那么什么情况下类型不安全?
    在这个地方,子类型替换就安全,别的就不安全


    哈哈哈哈英文文章多看就熟练了,没有别的技巧
    secondwtq
        12
    secondwtq  
       2019 年 9 月 28 日
    @amiwrong123
    我也是最近也开始研究 subtyping,我发现一个规律是 covariance 和 contravariance 一般是对称的,这个问题可以从这个角度出发理解:
    Holder<?>,这里的 ? 可能是任何类型 X

    在 T wildSubtype(Holder<? extends T> holder, T arg) 中,可以直接把 T 推断为 Object,注意 Object 是 Top Type,是任何类型的 supertype (包括它自己),X <: T 这个 constraint 对任意 Holder<X> 都成立,X 既可以是 Integer,也可以是 InternalFrameInternalFrameTitlePaneInternalFrameTitlePaneMaximizeButtonWindowNotFocusedState

    在 T void wildSupertype(Holder<? super T> holder, T arg) 中,没有合适的 ?:
    假设 X 是 Object,然后我们的 constraint 是 T <: X,然后既然 X 是任意的类型,那么这个 T 应该直接推断成 Bottom Type,也就是 null,所以 wildSupertype(unbounded, null) 是可以过编译的
    但是你给的 lng,无论把 T 推断成 Long/Number/Object 等等都是错的,比如 T 是 Object,那么 X 就只能是 Object,T 是 Long,那 X 就只能是 Long/Number/Object/Serializable ... 无论哪种情况,如果 我的 Holder<?> 实际上是一个 Holder<HasThisTypePatternTriedToSneakInSomeGenericOrParameterizedTypePatternMatchingStuffAnywhereVisitor>,T <: X 就不成立了
    secondwtq
        13
    secondwtq  
       2019 年 9 月 28 日
    @secondwtq s/假设 X 是 Object,然后我们的 // ...
    aguesuka
        14
    aguesuka  
       2019 年 9 月 29 日 via Android
    @amiwrong123 a.equals(b) 和 b.equals(a)的值应该一致。
    amiwrong123
        15
    amiwrong123  
    OP
       2019 年 9 月 29 日
    @secondwtq
    谢谢层主,刚看还有点没看大东,多看了几遍大概懂了。话说你是从哪里知道这些知识,感觉我又学到了很多。

    首先我才意识到 bottom type 是 null,以前一直以为 java 有上限,没下限。

    可能你的分析过程我理解得比较浅显:首先编译器总会找到最合适的推断类型出来,使得泛型方法可调用。且<?>的范围代表了所有类型。

    1. wildSubtype 中,由于一个形参推断出来为 Long,一个形参推断出来为?,所以 T 可以被推断为 Long/Number/Object。但为了形参<? extends T>的范围大于等于实参<?>的范围,T 必须被推断为 Object,才可以。

    2.wildSupertype 中,同样,由于一个形参推断出来为 Long,一个形参推断出来为?,所以 T 可以被推断为 Long/Number/Object。但形参是<? super T>,无论 T 被推断哪个,其形参的范围都无法大于等于实参<?>的范围。(说得形象点,此函数中,形参的范围是从 Long 到 top,而实参的范围是从 bottom 到 top )
    amiwrong123
        16
    amiwrong123  
    OP
       2019 年 9 月 29 日
    @secondwtq
    话说大佬可以帮忙解释一下 exact2 函数为什么会有编译错误吗?
    仅仅是因为,两个形参都要求确切的类型,所以两个实参里不能有一个带通配符的吗?
    by73
        17
    by73  
       2019 年 9 月 29 日
    @amiwrong123 `exact2` 这个,因为形参用的是 `Holder<T>` 这样的 concrete type,传入实参 `Holder<? extends T>` 是一个“集合”,编译器无法判断到底该该采用哪一个类型传进去。

    先改下 `bounded` 的声明为 `Holder<? extends Number>`;那么如果我调用 `exact2(bounded, 2.0)` 时编译器把类型 capture 为 `Holder<Double>`,但实际上你 `bounded` 的真正类型应该是 `Holder<Long>`,这就引发了矛盾。因此编译器不允许 wildcard type 和 concrete type 之间的转换。

    > The capture of a wildcard is compatible to a corresponding wildcard, never to a concrete type. Correspondingly, the capture of a bounded wildcard is compatible solely to other wildcards, but never to the bound.
    >
    > 来源:<http://www.angelikalanger.com/GenericsFAQ/FAQSections/TechnicalDetails.html#FAQ503>
    YUyu101
        18
    YUyu101  
       2019 年 9 月 29 日
    编译器不知道上下文,把自己想象成编译器只看这一句语句,上面的代码虽然是你写的,但你当做看不到,?等于占位用的,我不知道是什么类型
    Holder<? extends long>
    可能是 holder<Along extends long>
    也可能是 holder<Blong extends long>
    但实际整个工程里没有任何继承 long 的类,
    YUyu101
        19
    YUyu101  
       2019 年 9 月 29 日
    假设你习惯把 Long 的子类起名成 XXLong
    Holder<? extends Long>编译器就当成了 Holder<XXLong>
    推断 exact2 就是 static <XXLong> XXLong exact2(Holder<XXLong> holder, XXLong arg)
    XXLong 是 Long 的子类,你后面一个参数传了 Long,Long 不能向下转型成 XXLong
    但实际上并没有 XXLong 这个类。
    mineV
        20
    mineV  
       2019 年 9 月 30 日 via Android
    ?和? extends Object 是等价的。你用它去当做 exacr2 的参数,就会调用其中的 set 方法,显然这是不可以的。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     925 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 28ms UTC 20:00 PVG 04:00 LAX 12:00 JFK 15:00
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86