原文地址

目录

 

介绍

我很多朋友或者同事都以为我不喜欢使用Java,他们都认为我是一个纯粹的.NET技术爱好者,其实事实并不是这样子的:)。我也喜欢使用Java,并已经从事Java开发很多年了。

网上已经有很多有关C#与Java之间“异同”的文章,但是我认为并没有哪一篇文章的内容让我非常满意。在接下来我的几篇博客里,我将会竭尽全力地去阐述它们两者之间的“异同之处”。虽然我并不想挑起“口水仗”,但是我还是会以我的角度去阐述谁好谁不好(毫不留情地)。我尽可能地覆盖各个方面,但是并不会去深入比较两者的API,相反我会将重点放在各自语言特性上。

第一篇博客主要集中在各自的顶层结构上,比如命名空间、类型等。这里两者的版本分别是Java8和C#5.0。

相似点

两种语言都是大小写敏感的,严格意义上的“面向对象语言”,支持类、枚举以及接口,并且只允许单继承,所以的类型定义必须放在命名空间(namespace/package)中。同时,都支持注释,方法以及字段(包括静态的)。两者的最基本类型均是Object。两者都有相同的基本运算符、相似的异常处理机制。两者的程序启动方法均是一个名叫Main/main的静态方法。

编译单元

一个Java类编译之后会生成一个class文件,这些文件虽然可以单独存在,但是实际中它们通常和一些清单文件一起被打包进jar,war或者ear文件中,这样做是为了方便管理。jar(或其他格式)文件中还可以包含一些其他资源,比如图片,文本文件等。

C#类通常存在于一个程序集中,程序集有两种格式:

  • dll一个库,不能独自运行;
  • exe一个可以独自运行的可执行文件,可以是一个Console程序,或者Winform,也可以是一个wpf程序。

程序集同样可以包含一些元数据、嵌入的资源。C#/.NET中其实还定义了另外一个编译单元:模块(module)。但是通常情况下,一个module匹配一个assembly。

命名空间

C#和Java中都有namespace和package的概念。在C#中,namespace必须在它所有其他类型的最外部,一个源文件中,可以存在多个namespace,甚至嵌套形式的。

 1 namespace MyNamespace1
 2 {
 3     public class Class1
 4     {
 5     }
 6 }
 7 namespace MyNamespace2
 8 {
 9     public class Class2
10     {
11     }
12     namespace MyNamespace3
13     {
14         public class Class3
15         {
16         }
17     }
18 }

在Java中,package的定义在源文件最顶部位置,换句话说,一个源文件只能有一个package定义。

1 package myPackage;
2 public class MyClass
3 {
4 }

这里有一个非常大的不同之处,在Java中,package的定义必须与物理文件目录一致,也就是说,一个类如果属于a.b 这个package,那么这个类文件必须存在于a\b这个目录下,否则,编译不会通过。编译产生的.class文件也必须放在同一个目录之下,比如a\b\MyClass.class。

Java和C#中都可以通过导入命名空间的方式去访问其他命名空间的类型,比如Java中可以这样导入所有类型(使用*匹配所有类型),也可以一次导入一个。

1 import java.io.*;
2 import java.lang.reflect.Array;

C#中不支持单独导入一个类型,只能导入命名空间中的全部类型。

1 using System;
2 using System.IO;

不过C#中允许我们使用using关键字为一个类型定义一个别名。

1 using date = System.DateTime;
2 public class MyClass
3 {
4     public date GetCurrentDate()
5     {
6         //...
7     }
8 }

这种方式跟单独导入一个类型其实差不多意思。

顶层成员(类型)

Java和C#提供相似的语法,从某种程度上讲,如果我们忽略它们是两种不同的语言,那么有时候是难区分它们有什么不同,当然即使这样,它们之间还是有一些重要不同之处的。

Java中提供了以下几种顶级成员,除了package之外:

  • 类(包括泛型)
  • 接口(包括泛型)
  • 枚举

C#中要多几个:

  • 类(包括泛型)
  • 接口(包括泛型)
  • 枚举
  • 结构体
  • 委托(包括泛型)

基础类型

两种语言中有以下基础类型(C#/Java):

  • Object/object(C#简写:object)
  • String/string(C#简写:string)
  • Byte/byte(C#简写:byte)
  • SByte/N/A(C#简写:sbyte)
  • Boolean/boolean(C#简写:bool)
  • Char/char(C#简写:char)
  • Int16/short(C#简写:short)
  • UInt16/N/A(C#简写:unit)
  • Int32/int(C#简写:int)
  • UInt32/N.A(C#简写:uint)
  • Int64/long(C#简写:long)
  • UInt64/N/A(C#简写:ulong)
  • Single/float(C#简写:float)
  • Double/double(C#简写:double)
  • Decimal/N/A(C#简写:decimal)
  • dynamic/N/A
  • Arrays

如你所见,对于所有的整型类型,C#提供有符号和无符号两种,并且还提供精度更高的Decimal类型。(译者注:上面列表中,“/”前面是C#中的结构体写法,Int32其实是System.Int32,每种类型都提供一种简写方式,System.Int32对应的简写方式是int。Java中不存在结构体,只有一种写法

C#中提供三种数组:

  • 一维数组:int[] numbers(每个元素都为int)
  • 多维数组:int[,] matrix (每个元素都为int)
  • 数组的数组,又叫锯齿数组:int[][] matrix ((int[])[] 每个元素都是一个int[])

Java中也提供一维数组和锯齿数组,但是并没有多维数组。(译者注:在某种意义上讲,锯齿数组可以代替多维数组)

C#允许我们使用var关键字来定义一个变量并且初始化它,这是一种初始化变量的简写方式:

1 var i = 10;            //int
2 var s = "string";      //string
3 var f = SomeMethod();  //method's return type, except void

与Java一样,C#同样允许我们在一个数字后面添加后缀来标明它是什么类型的数据:

  • 10n:integer
  • 10l:long
  • 10f:float
  • 10d:double
  • 10u:unsigned int(仅C#)
  • 10ul:unsigned long(仅C#)
  • 10m:decimal(仅C#)

大小写均可作为后缀。

C#和Java中,类都分配在堆中。一个类只允许单继承自另外一个类,如果没有指定,默认继承自Object。每个类均可以实现多个接口。(单继承,多实现)

结构体

C#中有一套完整的类型系统,也就是说,所有基本类型(比如int、bool等)均和其他类型一样遵循同一套类型规则。这和Java明显不同,在Java中,int和Integer没有关系(虽然它们之间可以相互转换)。在C#中,所有的基本类型均是结构体(非class),它们均分配在栈中。在Java中,基本类型(int、long等)同样分配在栈中,但是它们并不是结构体,同样,Java中我们并不能自己定义一种分配在栈中的数据类型。C#中的结构体不能显式地继承自任何一个类,但是可以实现接口。

1 public struct MyStructure : IMyInterface
2 {
3     public void MyMethod()
4     {
5 
6     }
7 }

在C#中,结构体和枚举被称为“值类型”,类和接口被称为“引用类型”。由于C#(.NET)的统一类型系统,结构体隐式继承自System.ValueType。

译者注:严格意义上讲,Java并非完全面向对象。Java中的类型存在特殊,比如基础类型int、long、bool等,这些类型完全脱离了主流规则。此外,在C#中我们可以定义一种分配在栈中的类型,比如结构体

接口

在C#中,一个接口可以包含:

  • 实例方法声明
  • 实例属性声明
  • 实例事件声明

当然它们也可以是泛型的。类和结构体均可实现接口,一个接口可以被赋值为NULL,因为它是引用类型。

在Java中,情况有一点不同。因为接口中可以有静态成员、方法的实现:

  • 实例方法声明
  • 字段(静态)附带一个初始化值
  • 默认方法:包含默认实现,使用default关键字标记

它们同样可以是泛型的。在Java中,一个接口中的方法可以存在访问级别,也就是说,不一定总是public。

在Java中如果一个接口仅仅包含一个方法声明(同时可以包含一个或多个“默认方法”),那么这个接口可以被标记为“函数式接口”(Funcitional Interface),它可以用在lambda中,接口中的方法被隐式地调用(参见后面有关委托部分)(译者注:可以将一个lambda表达式赋给函数式接口,然后通过该接口去执行lambda表达式。默认方法、函数式接口、lambda表达式均属于Java8中新增加内容)。

泛型

C#和Java中的泛型有很大的不同。虽然两者都支持泛型类、泛型接口等,但是在C#中,泛型得到了更好的支持,而Java中泛型一旦经过编译后,类型参数就不存在了。也就是说在Java中,List<String>在运行阶段就会变成List类型,泛型参数String会被抹去,这样设计主要是为了与Java更老版本进行兼容。Java中的这种情况并不会发生在C#中,C#中我们可以通过反射得到一个泛型类的所有信息,当然也包括它的参数。

两种语言都支持多个泛型参数,并且都有一些限制。C#中的限制如下:

  • 基类、结构体、接口:可以强制泛型参数继承自一个特定的类(或实现特定的接口);
  • 具备无参构造方法的非抽象类:只允许非抽象并且具备无参构造方法的类型作为泛型参数;
  • 引用类型和值类型:泛型参数要么被指定为引用类型(类、接口),要么被指定为值类型(结构体、枚举)。

比如:

 1 public class GenericClassWithReferenceParameter<T> where T : class
 2 {
 3 
 4 }
 5 public class GenericClassWithValueParameter<T> where T : struct
 6 {
 7 
 8 }
 9 public class GenericClassWithMyClassParameter<T> where T : MyClass
10 {
11 
12 }
13 public class GenericClassWithPublicParameterlessParameter<T> where T : new()
14 {
15 
16 }
17 public class GenericClassWithRelatedParameters<K, V> where K : V
18 {
19 
20 }
21 public class GenericClassWithManyConstraints<T> where T : IDisposable where T : new() where T : class
22 {
23 
24 }
View Code

相关文章: