之前一直使用C#开发,最近由于眼馋Java生态环境,并借着工作服务化改造的契机,直接将新项目的开发都转到Java上去。积攒些Java开发经验,应该对.NET开发也会有所启发和益处。
从理论上说,Java和C#语言差别不大,毕竟难听地说,C#就是抄Java出来的。程序语言简史如是介绍这两种语言:
然而随着时间流逝语言发展,个人认为,C#在语言层面已经大大领先了Java。关于Java和C#的比较这几篇文章有着详细的描述。下面我总结一下我在趟过的坑,以供转型或学习的同学参考。
本文并非要比出这些语言谁优谁劣。有时候,好或坏是非常主观的判断,不同人有着不同的看法,强行断定好坏只会引起无畏的争论。这些语言有着各自的特点,有各自适合的场景。就像下面要谈到的Checked Exception特性,这是个很好的特性,但是在一些情况下也会引起不少麻烦。
Checked ExceptionJava是Checked Exception的。这就是说,如果你写了一个方法,这个方法会抛出一些异常,那么你需要用throws关键字标明这个方法会抛出哪些异常。这个特性很难说是好还是不好。Checked Exception本质上是一种类型系统,它明确规定了一个方法除了返回值类型以外,还可能抛出什么异常。这样调用方函数就能够明确地知晓应该处理或者传递哪些异常。这个特性在用得好的人手里,对正确处理各种边边角角的异常十分有用。然而,如果在你无法自己选队友,无法控制开发人员的水平的情况下,你很可能会发现,所有的方法都被标记为throws Exception。
Lambda,以及与Checked Exception产生的奇怪反应Java的Lambda本质上仍然是一个对象。事实上,Java的Lambda函数是一个满足Functional Interface接口的对象。比如下面代码,声明了一个具有一个int参数,返回一个int参数的函数。
@FunctionalInterface interface AFunction { int invokeBalaBala(int a); }我们可以这样定义一个这个函数的变量:AFunction f = x -> 2 * x;。
Java的Lambda和Checked Exception结合在一起后,产生了一个非常棘手的问题。由于Checked Exception是类型系统的一部分,一个不抛出异常的函数和一个会抛出异常的函数,它们的类型是不相同的。这就导致了Java的Lambda泛用性大大减少而且不是很好用。以对List的map操作为例,我们可以用如下代码将list里的每个元素翻倍:
list = list.stream().map(x -> 2 * x).collect(Collectors.toList());这里map接收一个类型为输入一个int参数,返回一个int值的函数。然而,如果我们需要给它的函数有可能抛出异常,比如这个函数会去读取文件、访问网络服务、或者做Json反序列化,则由于类型不同,Java编译器将会报错。
// 这个编译器会报错 list.stream().map(x -> JsonUtil.parse(x)).collect(Collectors.toList());解决方案一种是在函数体中使用try cache处理异常。但是很多时候,异常没办法在这个时刻处理,必须要抛出。那么还有另一种方案:将异常转换为RuntimeException,RuntimeException是所谓的Unchecked Exception,它不是类型系统的一部分,不需要用throws标注,所以不会导致函数类型变化。另一方面,编译器也无法检测出是否可能会抛出RuntimeException。无论采用哪种方案,都使得这个Lambda函数变得没那么好看。
泛型Java的泛型原理和C#不同。C#是运行时泛型,在程序运行的时候仍然能获取泛型的类型信息。而Java的泛型是类型擦除(Type Erasure)式泛型。名称听起来很高大上,意思是Java的泛型仅仅用于编译时类型检查,类型检查完成后,类型信息就被编译器擦除。在最后生成的字节码中中,泛型类型都被改为Object类型。
比如这句:
编译后变成:
HashMap map = new HashMap();Type Erasure方式的影响主要有两个:
运行时无法判断类型;
运行时无法动态生成泛型具现化的类的实例。
像下面两句:
x instanceof T new T()在Java中都会编译出错。而这在C#中都是很常见的代码。在C#中,我们可以有这样的Json反序列化方法:
T parse<T>(string jsonStr)这个方法将jsonStr反序列化为类型T的一个对象。这种写法看起来十分自然。然而在Java中无法实现。因为在parse方法中需要在运行时实例化T的一个对象,而Java在运行时这些泛型都已经被擦除,无法获取类型T的信息,从而无法实例化。要在Java实现类似的方法,需要额外将一个Class对象放到参数:
T parse(String jsonStr, Class<T> type)这样Java才能使用这个type,在运行时使用反射的方式生成类型T的实例。
Getter/Setter