您的位置:首页 > Web前端

The Similarities and Differences Between C# and Java -- Part 1(译)

2015-04-01 15:30 459 查看
原文地址

[b]目录[/b]

介绍(Introduction)

相似点(Similarities)

编译单位(Compiled Units)

命名空间(Namespaces)

顶层成员(类型)(Top Level Elements(Types))

基础类型(Basic Types)

类(Classes)

结构体(Structures)

接口(Interfaces)

泛型(Generic Types)

委托(Delegates)

枚举(Enumerations)

类型访问级别(Type Visibilities)

继承(Inheritance)

嵌套类(Inner Classes)

抽象类(Abstract Classes)

密封类(Sealed Classes)

静态类(Static Classes)

可空类型(Nullable Types)

部分类(Partial Classes)

匿名类(Anonymous Classes)

类型成员(Type Members)

静态构造方法(Static Constructors)

析构方法(Destructors)

静态成员(Static Members)

属性(Properties)

事件(Events)

字段与属性的自动初始化(Automatic Initialization of Fields and Properties)

成员访问级别(Member Visibilities)

虚拟成员(Virtual Members)

密封成员(Sealed Members)

抽象成员(Abstract Members)

泛型成员(Generic Members)

只读字段(Read-Only and Constant Fields)

技术审阅(Technical Review)

[b]介绍[/b]

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

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

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

[b]相似点[/b]

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

[b]编译单元[/b]

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

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

dll一个库,不能独自运行;

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

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

[b]命名空间[/b]

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

namespace MyNamespace1
{
public class Class1
{
}
}
namespace MyNamespace2
{
public class Class2
{
}
namespace MyNamespace3
{
public class Class3
{
}
}
}


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

package myPackage;
public class MyClass
{
}


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

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

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


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

using System;
using System.IO;


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

using date = System.DateTime;
public class MyClass
{
public date GetCurrentDate()
{
//...
}
}


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

[b]顶层成员(类型)[/b]

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

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

类(包括泛型)

接口(包括泛型)

枚举

C#中要多几个:

类(包括泛型)

接口(包括泛型)

枚举

结构体

委托(包括泛型)

[b]基础类型[/b]

两种语言中有以下基础类型(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关键字来定义一个变量并且初始化它,这是一种初始化变量的简写方式:

var i = 10;            //int
var s = "string";      //string
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#)

大小写均可作为后缀。

[b]类[/b]

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

[b]结构体[/b]

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

public struct MyStructure : IMyInterface
{
public void MyMethod()
{

}
}


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

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

[b]接口[/b]

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

实例方法声明

实例属性声明

实例事件声明

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

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

实例方法声明

字段(静态)附带一个初始化值

默认方法:包含默认实现,使用default关键字标记

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

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

[b]泛型[/b]

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

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

基类、结构体、接口:可以强制泛型参数继承自一个特定的类(或实现特定的接口);

具备无参构造方法的非抽象类:只允许非抽象并且具备无参构造方法的类型作为泛型参数;

引用类型和值类型:泛型参数要么被指定为引用类型(类、接口),要么被指定为值类型(结构体、枚举)。

比如:

public class MyClass
{
private EventHandler myEvent;
public event EventHandler MyEvent
{
add
{
this.myEvent += value;
}
remove
{
this.myEvent -= value;
}
}
}


View Code

[b]字段和属性的自动初始化[/b]

一个类中所有的字段都会初始化为对应类型的默认值(比如int初始化0,bool初始化为false等)。C#中的属性同样可以按照这种方式自动初始化。这方面两种语言都是一样的(当然Java中没有属性)。

[b]成员访问级别[/b]

C#类型成员中有以下访问级别:

private类型内部可访问

internal同一程序集中可访问

protected子类可访问(包括自己)

protected internal同一程序集或者子类可访问(译者注:这里取两者并集)

public所有人均可访问。

Java中类型成员的访问级别为:

package同一包中可访问

protected子类可访问(包括自己)

private自己可访问

public所有人可访问。

[b]虚拟成员[/b]

在Java中,除非被声明成了final,否则所有成员默认均是虚拟的(但是没有virtual关键字标记)。

在C#中,如果要定义一个虚拟成员,我们必须使用virtual关键字:

public class MyBaseClass
{
public virtual void MyMethod()
{

}
}
public class MyDerivedClass : MyBaseClass
{
public override void MyMethod()
{

}
}


如果派生类中有一个与基类重名的成员(但不是重写基类成员),这时候我们需要使用new关键字标记该成员(这样的话派生类成员会覆盖基类成员):

public class MyBaseClass
{
public void MyMethod()
{

}
}

public class MyDerivedClass : MyBaseClass
{
public new void MyMethod()
{
//no relation with MyBaseClass.MyMethod
}
}


[b]密封成员[/b]

在C#和Java中,我们都可以定义一个密封成员(sealed/final),密封成员在派生类中不能被重写。

C#中的语法为:

public class MyClass
{
public sealed void DoSomething()
{

}
}


Java中的语法为:

public class MyClass
{
public final void doSomething()
{

}
}


[b]抽象成员[/b]

两种语言中,抽象类中都可以存在抽象方法,但这不是必须的。也就是说,一个抽象类中可以没有任何抽象成员。在C#中,除了抽象方法外,还可以有抽象属性和抽象事件。

[b]泛型方法[/b]

方法也可以是泛型的,不管它是否存在于一个泛型类中。泛型方法可以自动识别它的类型参数:

public class MyClass
{
public static int Compare<T>(T v1, T v2)
{
if (v1 == v2)
{
return 0;
}
return -1;
}
}
//no need to specify the int parameter type
int areEqual = MyClass.Compare(1, 2);


[b]只读字段[/b]

Java和C#中都有只读字段,但是C#中使用readonly来标记:

public static class Singleton
{
//a C# readonly field
public static readonly Singleton Instance = new Singleton();
}


Java中使用final来标记:

public class Singleton
{
//a Java final field
public static final Singleton INSTANCE = new Singleton();
}


C#中也另外一种只读字段:常量。一个常量总是静态的,并且会被初始化一个基本类型值,或者枚举值:

public static class Maths
{
//a C# constant field
public const double PI = 3.1415;
}


readonly和const声明的变量有区别:const变量只能在声明时初始化,并且初始化表达式必须是可计算的,编译之后不能再改变,它的值永远是确定一样的;而readonly变量既可以在声明时初始化还可以在构造方法中初始化,所以每次运行,readonly变量的值可能不一样(虽然之后也不能改变)。

[b]技术审阅[/b]

写这篇博客时,我的好友Roberto Cortez对内容进行了核查,谢谢他!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: