Java 类型推导的不确定性

本文是读完oopsla16的一些心得

Figure 1

上面这个测试例运行的结果是,main函数中抛出一个ClassCastException,异常信息为 “java.lang.Integer cannot be cast to java.lang.String”。原因简单说就是因为JVM运行时检查出了类型问题,发现代码企图将一个Integer类型的对象转化为String类型,这种转换是不允许的。

但是,这种类型问题本应该是在编译期检查出来的。这里是什么导致编译器检查不出这个类型错误?

我们模拟一下Java类型推导的过程。类型推导总是先假设程序类型是对的,然后在这个前提下得出一些条件,最后验证这些条件是否成立。用类型Z代替Constrain<U, ? super T>中的,可以得到Figure 2中的信息。

Figure 2

a)和b)是显而易见的。c)是因为upcast函数的第一个参数的类型是Constrain<A, B>, 而这里传入了一个合法的参数,参数类型为Constrain<U, ? super T>。由于将U作为Bind的类型构造符,所以得到Z==B。upcast第二个参数的类型为T,而声明的类型为B,因此得到T<:B,因为B<:A,这里A已经因为Bind<U>而变为U,因此B<:U。

对于c), d), e)3个条件,Figure-1的例子中c)是满足的,因为upcast接受了一个合法的Constrain<U, Z>类型的参数。由于c)条件满足,所以d)和e)也满足了。
但是, Figure-1的正确仅仅是一个巧合。原因是Java类型推导验证条件的顺序是不确定的。如果顺序的验证条件c), d), e),那么条件满足。如果先验证d),e),再验证c),那么T<:U这个条件就不能满足了。所以不同编译器在编译Figure-1时结果是不同的。

不过我个人认为,造成这种类型推导结果不一致的原因并不能完全归结于条件的验证顺序。如果Java像Ocaml一样严格的区分Some和None,那么对于Figure-1的例子,我们就不可能向upcast函数传一个合法的Constrain<U, ? super T>类型的对象。

oopsla16中还给出了一些其他例子,用于说明Java类型推导的不一致性。

Ocaml 实现

type ('a, 'b) eq = Refl : ('a, 'a) eq



let cast (type a) (type b) (eq: (a, b) eq) (x : a) : b =
  match eq with
  | Refl -> x

let lies : (int, string)eq = Obj.magic 0;;

cast lies 42

上面的Ocaml也实现了一个与Figure-1一样的问题的例子。执行后内存错直接导致ocaml解释器崩溃。不过与Java不同的是,构造(int ,string)eq类型需要使用Obj.magic作为hack手段,而Java直接使用null就可以。

结论

  1. Java的类型推导是难以捉摸的,因此使用时应该尽可能的给出类型参数信息。
  2. 强类型语言的确更安全一些。

参考文献:
[1] Java and Scala’s Type Systems are Unsound
[2] Java's unsoundness (from an ML-ish point of view)

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 136,084评论 19 139
  • 《裕语言》速成开发手册3.0 官方用户交流:iApp开发交流(1) 239547050iApp开发交流(2) 10...
    叶染柒丶阅读 28,519评论 5 19
  • “叮铃铃——” “啊——几点了—嗯—” 我睁开眼睛,啊,八点了。估计现在去学校肯定会被老太婆骂一顿,不去了,请个假...
    苏梓妤阅读 1,751评论 4 5
  • 1、平平淡淡是真没错,只是在强劲撕裂和极度分化的社会发展进程中,你的低风险承受和应对的能力、你的低掌控自己命运的能...
    黑麦的光影部落阅读 1,738评论 0 0

友情链接更多精彩内容