转贴:Java语言的十大问题及引申思考

来源:互联网 发布:域名劫持简单原理 编辑:程序博客网 时间:2024/05/01 04:56

转自:http://blog.163.com/gordonkkk/blog/static/63425684200962795039630/

从CSDN上看到一篇批评Java语言诸多问题的翻译文章,原文作者是Mario Fusco。他指出了Java语言落后于时代,积重难返的10个问题。看过之后引起了我的一些联想。下面列出他所说的10大问题。桔黄色字体是我一点点不成熟的思考。

1、缺少闭包(closure):我想这个不需要解释了。函数式编程已经存在几十年了,但最近几年,它们获得了越来越多的关注,最主要的原因,是它可以自然地编写并行程序。我部分的同意Joshua Bloch强调在Java中引入闭包的问题需要再想一想(BGGA提议的方式真的很糟),至少闭包的缺失,使得在Java中做任何真正的函数式编程都是不可能的。

闭包的定义和特性,Java里很难体会,因为Java里没有真正意义的闭包,我也是从JavaScript里对闭包有了大致的了解。Java是个逻辑严格自洽的语言,严密到了一定程度,以至于目前的Java里根本没有闭包的容身之处——闭包中引用的外围局部变量,到底放在堆栈中,还是托管堆中?他们的作用域和生存期如何处理?又该如何被GC回收?但闭包确实是好东西。即使我自认为是一个Java程序员,在写多了JavaScript代码又回到Java后,对于不能随时随地的写一段{}括起来的代码块,感觉很不自由。针对闭包问题,Java还是有自己能将就的办法,那就是内部类。至少还有内部类,至少内部类还有一部分的闭包特性,使得一些优雅的设计(如集合框架迭代器)能够存在。

2、缺少一等函数:这个问题与前一个有些关联,但我认为它更糟糕。在Java里,要达到类似效果的唯一方式,是使用著名的、丑陋悲惨的单方法匿名内部类,但这看上去的确是一个拙劣的方法。甚至在C#中,也通过代理机制,提供了一个更好的实现。

所谓一等函数,面向对象的Java没有似乎不能强求。但它带来的问题,就是Java里有时候策略模式的拙劣实现。C#作为一个高级语言,又是强类型的,也一样可以有delegate,匿名方法,甚至Lambda表达式。Java里就只能生造出一个单方法接口,new一个无意义的对象,来承载其中的函数。

3、原生类型(Primitive types):如果在Java中一切皆对象,那是多么完美啊,但他们偏偏不这样设计。因而,这一点导致了一些问题,比如,不能把一个int放到集合(Collection)里,这个在Java5中通过自动装箱特性得到了解决(下面会提到)。它也造成了传值与传引用上的困扰,原生类型数据是通过值传给方法的(复制一份拷贝,然后传给函数),而真正的对象是通过传递(译注:其实是复制对象地址再传递,因此应该也是传值方式,只是由于函数内部可通过这个对象地址访问对象,因此效果上类似传引用)。
这似乎不是个太大的问题,我觉得这里有些吹毛求疵了。Effective Java里建议在新的代码中不要使用原生类型,这正是一个建设性的做法。Java并不是没有原生类型的包装类,不去使用的责任应该由程序员来负,而不是Java。

4、自动装箱(Autoboxing)和自动拆箱(autounboxing):这个特性是为了解决因原生类型的存在所导致的问题,在Java5引入的。它允许静默地转换原生类型到相应的对象,但这常常导致其它的问题。比如Integer可以为null,但int不能,因此这时JVM只能抛出一个难以调试的空指针异常(NullPointerException)。此外,它还可能导致其它奇怪的行为,就像下面的例子,我们就很难理解,变量test为什么是false:
Intger a = new Integer(1024);
Intger b = new Integer(1024);
boolean test = a < b || a == b || a > b;
这个例子老实说我认为很不恰当。诚然,a和b都是对象,a<b和a>b的判断中的自动拆箱和a==b的未拆箱造成了混淆,但要知道自动拆箱和装箱本身只是便利措施,不能对它要求更多。这里更有问题的其实是==判断。String类遇到==也是一样的问题,Java未在语言级别提供==的算符重载,个人认为是一种失策。

5、缺少范型具类化:范型是Java5引入的一个很酷的特征,但是为了保持与旧版本Java的兼容性,导致缺失某些重要的特性,尤其是不能在运行时反省范型的类型。例如,你有一个方法,接受List< ?>参数,如果传进来一个List< String>,你却不能知道运行里该范型的确切类型。同理,你也不能创建范型数组。这意味着,尽管这样的代码看起来很自然,但却不编译不了。

擦除,意味着在JVM中所谓泛型,已经彻底还原成了基本的未参数化形式。Java的泛型,自我学习的第一天起,就不停的被告知,被提醒“擦除”的概念。擦除带来的好处只有一个,就是和旧代码兼容。擦除带来的不足却有很多,而补偿起来却非常困难。同样的,自从我学习Java泛型的第一天起,就不停的提醒我自己,你知道Java泛型能做什么,现在更要知道它做不到什么。有时候我们可以简单的多引入一个参数记录类型的Class对象,但更多的时候,享用泛型解决问题,此路不通。

6、不可避免的范型警告:你有发现过自己陷入不可能去掉的关于范型的警告么?如果你像我一样大量使用范型,我打赌你碰到过。事实上,是这个问题的规模化症状,让他们认为需要引入一个特定的注解 (@SuppressWarnings("unchecked")) 来处理这种情况,我觉得,范型应该可能被设计的更好。

泛型的转换警告我感觉是很无厘头的。从一个参数类型转换到另一个,编辑器告诉我它可能是不安全的。为什么是不安全的?因为它记不住类型参数,因为类型参数运行时要被擦除......既然自己无法做泛型层面的类型检查,把维护类型安全的责任推给了程序员,又何必假模假式的做出警告?强制类型转换的时候怎么不来提醒转换有可能失败呢?两者的区别又有多大呢?


7、不能传void给方法调用:我得承认,这种给方法传递void的需求,乍一看有些怪异。我喜欢DSL,当我实现自己的DSL库(lambdaj)的一个特定特性时,我不得不需要一个方法声明成这样的签名:void doSomething(Object parameter),这里为这个方法传进来的参数parameter,是另一个方法调用的结果,它唯一的目的,是注册调用(的对象)自身,以可以在以后执行它。让我吃惊的是,即使println方法返回void,看上去也并没有一个好理由,不允许我把代码写成这样,:
doSomething(System.out.println("test"));

这条我基本没看懂。我很同意他那句话“这种给方法传递void的需求,乍一看有些怪异”。如果是我的话,完全可以不设计这样别扭的API。void就是void,不应该再有更多的责任。想想JavaScript中Null和Undefined之间造成的混淆吧,Java里就别来这套了。

8、没有原生的代理机制:代理是一种非常有效和应用广泛的模式,但Java提供的代理机制,只针对接口,而不是具体类。这是为什么象cblib这样提供这种机制的库,被如此多的主流框架,如Spring和Hibernate,采用的原因。此外,由于cglib通过运行时创建被代理类的子类来实现的,因此这些种方式有一个众所周知的限制——不能代理final类,比如String.

final类是用来做什么的,不就是防止被改变的吗?以它作为引用的类型,可以放心的认为对象的行为一定和类的定义中写的一模一样,这才是final的用意。从这个角度考虑,想要代理final类,与final的语义起了冲突。asm-cglib是很好的第三方工具,我认为足以满足一般性的需求了。但如果Java能有一种能语言级的代理的支持,显然会更好。无论以何种方式处理,总比运行时动态创建被代理的子类效率高,api的使用难度也会更低。

9、差劲的Switch……case语句:Java规定,switch……case只能选择int和enum(Java5开始)。这一点如果跟更现代的语言如Scala相比,看起来简直太弱了。

这个问题似乎在目前的Java中消失了.....很大的一个原因是,由于Java中Switch太烂,以至于很少使用了......

10、受检查异常(Checked exception):类似原生类型,受检查异常也已经成为Java的一个罪孽之源。它迫使程序员必须做下面两件极其糟糕讨厌的事情中的一个:让你的代码里充斥大量的、糟糕难读的、容易出错的try……catch语句,而这样做的最大意义,只是将捕获的异常,包装成运行时异常,然后再重新抛出;或者是让大量的抛出声明子句污染你的API,让接口缺少灵活性和可扩展性。

这是Java一个广泛被批评的地方。我觉得Java的受检查异常还是有意义的。在很多所谓一定会出异常,一定要考虑异常处理的地方,受检查异常是合理的——你要么应当立即处理它,要么必须抛出它,真正必须处理的异常出现在接口中,并不算污染API吧。现在的问题是,受检查的异常被大量滥用。我个人认为,可以将这些异常化为运行时异常,并做一个小小的标记,在未被Catch或Throw时引起编译器警告,再引入一个特定的,忽略该警告的注解。这样可能会是一个圆满的解决方法。

 


11、痛苦的线程同步编程:这是我自己认为Java的问题。Java的线程机制实在有点扯。你必须在脑中模拟一切运行时的可能性,而任何微小的代码变化都可能破坏你原先的设想。在这种情况下小心翼翼的在线程间通讯和同步。编写线程安全的复杂代码,简直是一场头脑风暴,并不是任何人都能胜任这样的工作。这个问题和Java没有闭包,不能进行函数式编程是紧密相关的,目前的Java很难做的更好,现有的API已经在尽量降低使用难度了。但涉及到实际业务的复杂需求时,Java不能提供任何语言级别的协助,还是让人相当沮丧。当然事情发生在我这里时,我只能说自己的水平差,不是一个足够优秀的Java程序员。

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/jinxfei/archive/2009/09/20/4573317.aspx