Java / C#类型系统有哪些不足之处?

它经常听说Haskell(我不知道)有一个非常有趣的类型系统..我对Java非常熟悉,而且对C#有点熟悉,有时碰巧我正在与类型系统作斗争,所以一些设计适应或以某种方式更好地工作。

这让我想知道……

由于Java / C#类型系统的不足,以某种方式出现了什么问题? 你怎么处理他们?

arrays坏了。

Object[] foo = new String[1]; foo[0] = new Integer(4); 

给你java.lang.ArrayStoreException

你谨慎对待它们。

可空性是另一个大问题。 NullPointerExceptions随处可见。 除了切换语言之外,你真的无法做任何事情,或者尽可能使用避免它们的惯例(正确初始化字段等)。

更一般地说,Java / C#的类型系统不是很有表现力。 Haskell可以给你的最重要的事情是,通过它的类型你可以强制执行这些函数没有副作用。 编译时certificate部分程序只是被评估的表达式,使程序更可靠,可组合,更容易推理。 (忽略这一事实,Haskell的实现为您提供了绕过它的方法)。

与Java相比,调用方法几乎可以做任何事情!

Haskell也有模式匹配,它为您提供了创建程序的不同方式; 你有关于哪些函数运行的数据,通常是递归的。 在模式匹配中,您可以破坏数据以查看它是什么类型,并根据它进行操作。 例如,你有一个列表,它可以是空的,也可以是头尾。 如果要计算长度,可以定义一个函数:如果list为空,length = 0,否则length = 1 + length(tail)。

如果您真的想了解更多,那么有两个优秀的在线资源:

了解Haskell和真实世界Haskell

我不喜欢Java中原始(本机)类型( intbooleandouble )与它们对应的类包装器( IntegerBooleanDouble )之间的区别。

这通常非常烦人,特别是在编写通用代码时。 本机类型不能通用化,您必须实例化一个包装器。 generics应该使您的代码更抽象,更容易重用,但在Java中,它们带来了限制,显然没有理由。

 private static  T First(T arg[]) { return arg[0]; } public static void main(String[] args) { int x[] = {1, 2, 3}; Integer y[] = {3, 4, 5}; First(x); // Wrong First(y); // Fine } 

在.NET中,即使存在单独的值和引用类型,也没有这样的问题,因为它们严格意识到“一切都是对象”。

关于generics的这个问题表明了java类型系统的表现力的不足
Java中更高级的generics

我不喜欢这样的事实,即类不是一流的对象,你不能做一些奇特的事情,例如让静态方法成为接口的一部分。

Java / .net类型系统的一个根本弱点是它没有声明性的方法来指定对象的状态如何与其引用类型字段的内容相关,也没有指定允许方法保持引用类型参数的方法。 虽然在某种意义上,运行时能够使用一种类型ICollection的字段Foo来表示许多不同的东西,但类型系统不可能为不变性,等效性测试,克隆等事物提供真正的支持。 ,或任何其他此类function,而不知道Foo是否代表:

  1. 对集合的只读引用,任何东西都不会变异; 该类可以自由地与外部代码共享此类引用,而不会影响其语义。 该引用仅封装不可变状态,并且可能不封装标识。
  2. 对类型可变但不会实际变异的集合的可写引用; 该类只能与可信任的代码共享此类引用,而不会使其变异。 如上所述,引用仅封装不可变状态,并且可能不封装标识。
  3. 宇宙中任何地方唯一引用它变异的集合。 该引用将封装可变状态,但不会封装标识(将该集合替换为另一个保存相同项目的集合不会更改封闭对象的状态)。
  4. 对它变异的集合的引用,它认为它的内容是它自己的,但是外部代码保存了它希望附加到`Foo`当前状态的引用。 该引用将封装身份和可变状态。
  5. 对某个其他对象拥有的可变集合的引用,它希望将其附加到该另一个对象的状态(例如,如果持有`Foo`的对象应该显示其他某些集合的内容)。 该引用将封装身份,但不会封装可变状态。

假设有人想要将包含Foo的对象的状态复制到一个新的,分离的对象。 如果Foo表示#1或#2,则可以在新对象中存储Foo中的引用的副本,或者对保存相同数据的新对象的引用; 复制引用会更快,但两个操作都是正确的。 如果Foo表示#3,则正确的分离副本必须保存对从原始状态复制状态的新分离对象的引用。 如果Foo表示#5,则正确的分离副本必须保存原始引用的副本 – 它不能保留对新分离对象的引用。 如果Foo代表#4,则不能孤立地复制包含它的对象的状态; 有可能复制一堆互连的对象以产生一个状态等同于原始状态的新束,但不可能单独复制对象的状态。

虽然类型系统不可能以声明方式指定对象之间可能存在的所有可能关系以及应该对它们做些什么,但类型系统和框架应该可以正确地生成代码以在语义上生成 – 正确的等价测试,克隆方法,平滑互操作的可变,不可变和“可读”类型等,在大多数情况下,如果它知道哪些字段封装了身份,可变状态,两者或两者都没有。 此外,框架应该可以最小化防御性复制和包装,在这种情况下,它可以确保传递的引用不会被赋予任何会改变它们的东西。

(Re:C#具体。)

我很喜欢被标记的工会 。

同上类,方法,属性等的第一类对象 。

虽然我从未使用它们,但Python有类型类 ,它们基本上是表示类及其行为方式的类型。

不可为空的引用类型,因此不需要进行空值检查。 它原本被认为是C#但被丢弃了。 (这有一个堆栈溢出问题。)

协方差,所以我可以将List转换为List

这是次要的,但对于Java和C#的当前版本,声明对象会破坏DRY原则 :

 Object foo = new Object; Int x = new Int; 

他们都没有元编程设施,比如说旧的C ++狗有。

使用“使用”复制和缺少typedef是违反DRY的一个例子,甚至可能导致用户引起的“别名”错误等等。 Java’模板’甚至不值得一提……