
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 函数却可以执行成功?

两个报错都说了,Long 不能转换为?。这个该怎么理解,就是说,一个形参推断出来为 Long,一个形参推断出来为?,就不可以呗? exact2 函数还多说了句,两个形参推断出来的边界不一样。
1 putin541 2019 年 9 月 28 日 我的理解是,类型的集合也可以看作是一种类型。? 是所有类型的集合,所以 Long != ? |
2 realPipiz 2019 年 9 月 28 日 |
3 aguesuka 2019 年 9 月 28 日 equals 写得应该有问题 |
4 itechify PRO https://liuzhicong.cn/index.php/study/extends-T-and-super-T.html 之前我写的可能相关,太长的话,看最后的引用,stackoverflow 里面的第一第二个解答 只要是类型安全问题,编译器经量往最安全的地方去检查你的代码 |
6 amiwrong123 OP @putin541 你的理解貌似也很有道理。但偏偏 wildSubtype 函数接受同样的实参,却不会报编译错误了,这该如何解释呢 |
7 amiwrong123 OP @realPipiz 谢谢,看了。介绍泛型的知识很广,但没有我所疑问的点。 |
8 amiwrong123 OP @aguesuka 怎么个有问题法呀 |
9 amiwrong123 OP @oneisall8955 哈哈哈,上次你就给推荐过啦。看了,介绍了<? extends T> 及 <? super T>这两种引用的合法操作和限制操作,不错。但我这里,主要不懂在于,泛型方法上,涉及了通配符的类型推断,编译器为啥会报这个错== (悄悄地说,等会写博客准备把你那个 copy 的 jdk 例子写进去,哈哈哈) |
10 amiwrong123 OP @Mistwave 谢谢,你说这两点就是<? extends T> 及 <? super T>这两种引用的合法操作和限制操作了吧(一种合法的是“读操作”,另一个是“写操作”)。 你这篇文章也不错,话说你们都喜欢看英文文档啊,我要是能看看 java 官网文档就不错了。。。 主要还是不懂在于,泛型方法上,涉及了通配符(可能是形参涉及了、也可能是实参涉及了)的类型推断,编译器为啥会报这个错== |
11 Mistwave 2019 年 9 月 28 日 via iPhone |
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 就不成立了 |
14 aguesuka 2019 年 9 月 29 日 via Android @amiwrong123 a.equals(b) 和 b.equals(a)的值应该一致。 |
15 amiwrong123 OP @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 ) |
16 amiwrong123 OP |
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> |
18 YUyu101 2019 年 9 月 29 日 编译器不知道上下文,把自己想象成编译器只看这一句语句,上面的代码虽然是你写的,但你当做看不到,?等于占位用的,我不知道是什么类型 Holder<? extends long> 可能是 holder<Along extends long> 也可能是 holder<Blong extends long> 但实际整个工程里没有任何继承 long 的类, |
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 这个类。 |
20 mineV 2019 年 9 月 30 日 via Android ?和? extends Object 是等价的。你用它去当做 exacr2 的参数,就会调用其中的 set 方法,显然这是不可以的。 |