C# VS Java

摘要:C#的语言规范由Microsoft的Anders Hejlsberg与Scott Wiltamuth编写。在当前Microsoft天花乱坠的宣传中,对C#和C++、Java做一番比较老是颇有趣的。

  1、C#、C++和Javajava

  C#的语言规范由Microsoft的Anders Hejlsberg与Scott Wiltamuth编写。在当前Microsoft天花乱坠的宣传中,对C#和C++、Java做一番比较老是颇有趣的。考虑到当前IT媒体的舆论倾向, 若是你早就知道C#更接近Java而不是C++,事情也不值得大惊小怪。显然,结论应该是:Java和C#虽然不是孪生子,但C#最主要的特点却更接近 Java而不是C++。编程

表1:比较C#、C++和Java最重要的功能
功能 C# C++ Java
继承 容许继承单个类,容许实现多个接口 容许从多个类继承 容许继承单个类,容许实现多个接口
接口实现 经过“interface”关键词 经过抽象类 经过“interface”关键词
内存管理 由运行时环境管理,使用垃圾收集器 须要手工管理 由运行时环境管理,使用垃圾收集器
指针 支持,但只在不多使用的非安全模式下才支持。一般以引用取代指针 支持,一种很经常使用的功能。 彻底不支持。代之以引用。
源代码编译后的形式 .NET中间语言(IL) 可执行代码 字节码
单一的公共基类
异常处理 异常处理 返回错误 异常处理。

  下面将说说C#和Java的一些重要区别。数组

  2、语言规范的比较

  2.一、简单数据类型

  简单数据类型(Primitive)在C#中称为值类型,C#预约义的简单数据类型比Java多。例如,C#有unit,即无符号整数。表2列出了全部C#的预约义数据类型:安全

表2:C#中的值类型
类型 说明
object 全部类型的最终极的基类
string 字符串类型;字符串是一个Unicode字符的序列
sbyte 8位带符号整数
short 16位带符号整数
int 32位带符号整数
long 64位带符号整数
byte 8位无符号整数
ushort 16位无符号整数
uint 32位无符号整数
ulong 64位无符号整数
float 单精度浮点数类型
double 双精度浮点数类型
bool 布尔类型;bool值或者是true,或者是false
char 字符类型;一个char值便是一个Unicode字符
decimal 有28位有效数字的高精度小数类型

  2.二、常量

  忘掉Java中的static final修饰符。在C#中,常量能够用const关键词声明。编程语言

public const int x = 55;

  此外,C#的设计者还增长了readonly关键词。若是编译器编译时未能肯定常量值,你可使用readonly关键词。readonly域只能经过初始化器或类的构造函数设置。函数

  2.三、公用类的入口点

  在Java中,公用类的入口点是一个名为main的公用静态方法。main方法的参数是String对象数 组,它没有返回值。在C#中,main方法变成了公用静态方法Main(大写的M),Main方法的参数也是一个String对象数组,并且也没有返回 值,以下面的原型声明所示:性能

public static void Main(String[] args)

  可是,C#的Main方法不局限于此。若是不向Main方法传递任何参数,你可使用上述Main方法的一个重载版本,即不带参数列表的版本。也就是说,下面的Main方法也是一个合法的入口点:测试

public static void Main()

  另外,若是你认为有必要的话,Main方法还能够返回一个int。例如,下面代码中的Main方法返回1:ui

using System;
public class Hello {
public static int Main() {
Console.WriteLine("Done");
return 1;
}
}

  与此相对,在Java中重载main方法是不合法的。this

  2.四、switch语句

  在Java中,switch语句只能处理整数。但C#中的switch语句不一样,它还可以处理字符变量。请考虑下面用switch语句处理字符串变量的C#代码:

using System;
public class Hello {
public static void Main(String[] args) {
switch (args[0]) {
case "老板":
Console.WriteLine("早上好!咱们随时准备为您效劳!");
break;
case "雇员":
Console.WriteLine("早上好!你能够开始工做了!");
break;
default:
Console.WriteLine("早上好!祝你好运!");
break;
}
}
}

  与Java中的switch不一样,C#的switch语句要求每个case块或者在块的末尾提供一个break语句,或者用goto转到switch内的其余case标签。

  2.五、foreach语句

  foreach语句枚举集合中的各个元素,为集合中的每个元素执行一次代码块。请参见下面的例子。

using System;
public class Hello {
public static void Main(String[] args) {
foreach (String arg in args)
Console.WriteLine(arg);
}
}

  若是在运行这个执行文件的时候指定了参数,好比“Hello Peter Kevin Richard”,则程序的输出将是下面几行文字:

Peter
Kevin
Richard

  2.六、C#没有>>>移位操做符

  C#支持uint和ulong之类的无符号变量类型。所以,在C#中,右移操做符(即“>>”)对于无符号变量类型和带符号变量类型(好比int和long)的处理方式不一样。右移uint和ulong丢弃低位并把空出的高位设置为零;但对于int和long类型的变量,“>>”操做符丢弃低位,同时,只有当变量值是正数时,“>>”才把空出的高位设置成零;若是“>>”操做的是一个负数,空出的高位被设置成为1。

  Java中不存在无符号的变量类型。所以,咱们用“>>>”操做符在右移时引入负号位;不然,使用“>>”操做符。

  2.七、goto关键词

  Java不用goto关键词。在C#中,goto容许你转到指定的标签。不过,C#以特别谨慎的态度对待goto,好比它不容许goto转入到语句块的内部。在Java中,你能够用带标签的语句加上break或continue取代C#中的goto。

  2.八、声明数组

  在Java中,数组的声明方法很是灵活,实际上有许多种声明方法都属于合法的方法。例如,下面的几行代码是等价的:

int[] x = { 0, 1, 2, 3 };
int x[] = { 0, 1, 2, 3 };

  但在C#中,只有第一行代码合法,[]不能放到变量名字以后。

  2.九、包

  在C#中,包(Package)被称为名称空间。把名称空间引入C#程序的关键词是“using”。例如,“using System;”这个语句引入了System名称空间。

  然而,与Java不一样的是,C#容许为名称空间或者名称空间中的类指定别名:

using TheConsole = System.Console;
public class Hello {
public static void Main() {
TheConsole.WriteLine("使用别名");
}
}

  虽然从概念上看,Java的包相似于.NET的名称空间。然而,二者的实现方式不一样。在Java中,包的名字同时也是实际存在的实体,它决定了放置.java文件的目录结构。在C#中,物理的包和逻辑的名称之间是彻底分离的,也就是说,名称空间的名字不会对物理的打包方式产生任何影响。在C#中,每个源代码文件能够从属于多个名称空间,并且它能够容纳多个公共类。

  .NET中包的实体称为程序集(Assembly)。每个程序集包含一个manifest结构。manifest列举程序集所包含的文件,控制哪些类型和资源被显露到程序集以外,并把对这些类型和资源的引用映射到包含这些类型与资源的文件。程序集是自包含的,一个程序集能够放置到单一的文件以内,也能够分割成多个文件。.NET的这种封装机制解决了DLL文件所面临的问题,即臭名昭著的DLL Hell问题。

  2.十、默认包

  在Java中,java.lang包是默认的包,它无需显式导入就已经自动包含。例如,要把一些文本输出到控制台,你可使用下面的代码:

System.out.println("Hello world from Java");

  C#中不存在默认的包。若是要向控制台输出文本,你使用System名称空间Console对象的WriteLine方法。可是,你必须显式导入全部的类。代码以下:

  2.十一、面向对象

  Java和C#都是彻底面向对象的语言。在面向对象编程的三大原则方面,这两种语言接近得不能再接近。

  • 继承:这两种语言都支持类的单一继承,但类能够实现多个接口。全部类都从一个公共的基类继承。
  • 封装与可见性:不管是在Java仍是C#中,你均可以决定类成员是否可见。除了C#的internal访问修饰符以外,二者的可见性机制很是类似。
  • 多态性:Java和C#都支持某些形式的多态性机制,且二者实现方法很是相似。

  2.十二、可访问性

  类的每一个成员都有特定类型的可访问性。C#中的访问修饰符与Java中的基本对应,但多出了一个internal。简而言之,C#有5种类型的可访问性,以下所示:

  • public:成员能够从任何代码访问。
  • protected:成员只能从派生类访问。
  • internal:成员只能从同一程序集的内部访问。
  • protected internal:成员只能从同一程序集内的派生类访问。
  • private:成员只能在当前类的内部访问。

  2.1三、派生类

  在Java中,咱们用关键词“extends”实现继承。C#采用了C++的类派生语法。例如,下面的代码显示了如何派生父类Control从而建立出新类Button:

public class Button: Control { . . }

 

  2.1四、最终类

  因为C#中不存在final关键词,若是想要某个类再也不被派生,你可使用sealed关键词,以下例所示:

sealed class FinalClass { . . }

 

  2.1五、接口

  接口这个概念在C#和Java中很是类似。接口的关键词是interface,一个接口能够扩展一个或者多个其余接口。按照惯例,接口的名字以大写字母“I”开头。下面的代码是C#接口的一个例子,它与Java中的接口彻底同样:

interface IShape { void Draw(); }

  扩展接口的语法与扩展类的语法同样。例如,下例的IRectangularShape接口扩展IShape接口(即,从IShape接口派生出IRectangularShape接口)。

interface IRectangularShape: IShape { int GetWidth(); }

  若是你从两个或者两个以上的接口派生,父接口的名字列表用逗号分隔,以下面的代码所示:

interface INewInterface: IParent1, IParent2 { }

  然而,与Java不一样,C#中的接口不能包含域(Field)。

  另外还要注意,在C#中,接口内的全部方法默认都是公用方法。在Java中,方法声明能够带有public修饰符(即便这并不是必要),但在C#中,显式为接口的方法指定public修饰符是非法的。例如,下面的C#接口将产生一个编译错误。

interface IShape { public void Draw(); }

  2.1六、is和as操做符

  C#中的is操做符与Java中的instanceof操做符同样,二者均可以用来测试某个对象的实例是否属于特定的类型。在Java中没有与C#中的as操做符等价的操做符。as操做符与is操做符很是类似,但它更富有“进取心”:若是类型正确的话,as操做符会尝试把被测试的对象引用转换成目标类型;不然,它把变量引用设置成null。

  为正确理解as操做符,首先请考虑下面这个例子中is操做符的运用。这个例子包含一个IShape接口,以及两个实现了IShape接口的类Rectangle和Circle。

using System;
interface IShape {
void draw();
}
public class Rectangle: IShape {
public void draw() {
}
public int GetWidth() {
return 6;
}
}
public class Circle: IShape {
public void draw() {
}
public int GetRadius() {
return 5;
}
}
public class LetsDraw {
public static void Main(String[] args) {
IShape shape = null;
if (args[0] == "rectangle") {
shape = new Rectangle();
}
else if (args[0] == "circle") {
shape = new Circle();
}
if (shape is Rectangle) {
Rectangle rectangle = (Rectangle) shape;
Console.WriteLine("Width : " + rectangle.GetWidth());
}
if (shape is Circle) {
Circle circle = (Circle) shape;
Console.WriteLine("Radius : " + circle.GetRadius());
}
}
}

  编译好代码以后,用户能够输入“rectangle”或者“circle”做为Main方法的参数。若是用户输入的是“circle”,则shape被实例化成为一个Circle类型的对象;反之,若是用户输入的是“rectangle”,则shape被实例化成为Rectangle类型的对象。随后,程序用is操做符测试shape的变量类型:若是shape是一个矩形,则shape被转换成为Rectangle对象,咱们调用它的GetWidth方法;若是shape是一个圆,则shape被转换成为一个Circle对象,咱们调用它的GetRadius方法。

  若是使用as操做符,则上述代码能够改为以下形式:

using System;
interface IShape {
void draw();
}
public class Rectangle: IShape {
public void draw() {
}
public int GetWidth() {
return 6;
}
}
public class Circle: IShape {
public void draw() {
}
public int GetRadius() {
return 5;
}
}
public class LetsDraw {
public static void Main(String[] args) {
IShape shape = null;
if (args[0] == "rectangle") {
shape = new Rectangle();
}
else if (args[0] == "circle") {
shape = new Circle();
}
Rectangle rectangle = shape as Rectangle;
if (rectangle != null) {
Console.WriteLine("Width : " + rectangle.GetWidth());
}
else {
Circle circle = shape as Circle;
if (circle != null)
Console.WriteLine("Radius : " + circle.GetRadius());
}
}
}

  在上面代码的粗体部分中,咱们在没有测试shape对象类型的状况下,就用as操做符把shape转换成Rectangle类型的对象。若是shape正好是一个Rectangle,则shape被转换成为Rectangle类型的对象并保存到rectangle变量,而后咱们调用它的GetWidth方法。若是这种转换失败,则咱们进行第二次尝试。这一次,shape被转换成为Circle类型的对象并保存到circle变量。若是shape确实是一个Circle对象,则circle如今引用了一个Circle对象,咱们调用它的GetRadius方法。

  2.1七、库

  C#没有本身的类库。可是,C#共享了.NET的类库。固然,.NET类库也能够用于其余.NET语言,好比VB.NET或者JScript.NET。值得一提的是StringBuilder类,它是对String类的补充。StringBuilder类与Java的StringBuffer类很是类似。

  2.1八、垃圾收集

  C++已经让咱们认识到手工管理内存是多么缺少效率和浪费时间。当你在C++中建立了一个对象,你就必须手工地拆除这个对象。代码越复杂,这个任务也越困难。Java用垃圾收集器来解决这个问题,由垃圾收集器搜集再也不使用的对象并释放内存。C#一样采用了这种方法。应该说,若是你也在开发一种新的OOP语言,追随这条道路是一种很是天然的选择。C#仍旧保留了C++的内存手工管理方法,它适合在速度极端重要的场合使用,而在Java中这是不容许的。

  2.1九、异常处理

  若是你据说C#使用与Java类似的异常处理机制,你不会为此而惊讶,对吧?在C#中,全部的异常都从一个名为Exception的类派生(听起来很熟悉?)另外,正如在Java中同样,你还有熟悉的try和catch语句。Exception类属于.NET System名称空间的一部分。

  3、Java没有的功能

  C#出生在Java成熟以后,所以,C#拥有一些Java(目前)尚未的绝妙功能也就不足为奇。

  3.一、枚举器

  枚举器即enum类型(Enumerator,或称为计数器),它是一个相关常量的集合。精确地说,enum类型声明为一组相关的符号常量定义了一个类型名字。例如,你能够建立一个名为Fruit(水果)的枚举器,把它做为一个变量值的类型使用,从而把变量可能的取值范围限制为枚举器中出现的值。

public class Demo {
public enum Fruit {
Apple, Banana, Cherry, Durian
}
public void Process(Fruit fruit) {
switch (fruit) {
case Fruit.Apple:
...
break;
case Fruit.Banana:
...
break;
case Fruit.Cherry:
...
break;
case Fruit.Durian:
...
break;
}
}
}

  在上例的Process方法中,虽然你能够用int做为myVar变量的类型,可是,使用枚举器Fruit以后,变量的取值范围限制到了Applet、Banana、Cherry和Durian这几个值以内。与int相比,enum的可读性更好,自我说明能力更强。

  3.二、结构

  结构(Struct)与类很类似。然而,类是做为一种引用类型在堆中建立,而结构是一种值类型,它存储在栈中或者是嵌入式的。所以,只要谨慎运用,结构要比类快。结构能够实现接口,能够象类同样拥有成员,但结构不支持继承。

  然而,简单地用结构来取代类可能致使惨重损失。这是由于,结构是以值的方式传递,因为这种传递方式要把值复制到新的位置,因此传递一个“肥胖的”结构须要较大的开销。而对于类,传递的时候只需传递它的引用。

下面是一个结构的例子。注意它与类很是类似,只要把单词“struct”替换成“class”,你就获得了一个类。

struct Point {
public int x, y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}

  3.三、属性

  C#类除了能够拥有域(Field)以外,它还能够拥有属性(Property)。属性是一个与类或对象关联的命名的特征。属性是域的一种天然扩展——二者都是有类型、有名字的类成员。然而,和域不一样的是,属性不表示存储位置;相反,属性拥有存取器(accessor),存取器定义了读取或者写入属性值时必须执行的代码。所以,属性提供了一种把动做和读取、写入对象属性值的操做关联起来的机制,并且它们容许属性值经过计算获得。

在C#中,属性经过属性声明语法定义。属性声明语法的第一部分与域声明很类似,第二部分包括一个set过程和/或一个get过程。例如,在下面的例子中,PropertyDemo类定义了一个Prop属性。

public class PropertyDemo {
private string prop;
public string Prop {
get {
return prop;
}
set {
prop = value;
}
}
}

  若是属性既容许读取也容许写入,如PropertyDemo类的Prop属性,则它同时拥有get和set存取过程。当咱们读取属性的值时,get存取过程被调用;当咱们写入属性值时,set存取过程被调用。在set存取过程当中,属性的新值在一个隐含的value参数中给出。

  与读取和写入域的方法同样,属性也能够用一样的语法读取和写入。例如,下面的代码实例化了一个PropertyDemo类,而后写入、读取它的Prop属性。

PropertyDemo pd = new PropertyDemo();
pd.Prop = "123"; // set
string s = pd.Prop; // get

  3.四、以引用方式传递简单数据类型的参数

  在Java中,当你把一个简单数据类型的值做为参数传递给方法时,参数老是以值的方式传递——即,系统将为被调用的方法建立一个参数值的副本。在C#中,你能够用引用的方式传递一个简单数据类型的值。此时,被调用的方法将直接使用传递给它的那个值——也就是说,若是在被调用方法内部修改了参数的值,则原来的变量值也随之改变。

  在C#中以引用方式传递值时,咱们使用ref关键词。例如,若是编译并运行下面的代码,你将在控制台上看到输出结果16。注意i值被传递给ProcessNumber以后是如何被改变的。

using System;
public class PassByReference {
public static void Main(String[] args) {
int i = 8;
ProcessNumber(ref i);
Console.WriteLine(i);
}
public static void ProcessNumber(ref int j) {
j = 16;
}
}

  C#中还有一个容许以引用方式传递参数的关键词out,它与ref类似。可是,使用out时,做为参数传递的变量在传递以前没必要具备已知的值。在上例中,若是整数i在传递给ProcessNumber方法以前没有初始化,则代码将出错。若是用out来取代ref,你就能够传递一个未经初始化的值,以下面这个修改后的例子所示。

using System;
public class PassByReference {
public static void Main(String[] args) {
int i;
ProcessNumber(out i);
Console.WriteLine(i);
}
public static void ProcessNumber(out int j) {
j = 16;
}
}

  通过修改以后,虽然i值在传递给ProcessNumber方法以前没有初始化,但PassByReference类可以顺利经过编译。

  3.五、C#保留了指针

  对于那些以为本身可以恰到好处地运用指针并乐意手工进行内存管理的开发者来讲,在C#中,他们仍旧能够用既不安全也不容易使用的“古老的”指针来提升程序的性能。C#提供了支持“不安全”(unsafe)代码的能力,这种代码可以直接操做指针,可以“固定”对象以便临时地阻止垃圾收集器移动对象。不管从开发者仍是用户的眼光来看,这种对“不安全”代码的支持实际上是一种安全功能。“不安全”的代码必须用unsafe关键词显式地标明,所以开发者不可能在无心之中使用“不安全”的代码。同时,C#编译器又和执行引擎协做,保证了“不安全”的代码不能假装成为安全代码。

using System;
class UsePointer {
unsafe static void PointerDemo(byte[] arr) {
.
.
}
}

  C#中的unsafe代码适合在下列情形下使用:当速度极端重要时,或者当对象须要与现有的软件(好比COM对象或者DLL形式的C代码)交互时。

  3.六、代理

  代理(delegate)能够看做C++或者其余语言中的函数指针。然而,与函数指针不一样的是,C#中的代理是面向对象的、类型安全的、可靠的。并且,函数指针只能用来引用静态函数,但代理既可以引用静态方法,也可以引用实例方法。代理用来封装可调用方法。你能够在类里面编写方法并在该方法上建立代理,此后这个代理就能够被传递到第二个方法。这样,第二个方法就能够调用第一个方法。

  代理是从公共基类System.Delegate派生的引用类型。定义和使用代理包括三个步骤:声明,建立实例,调用。代理用delegate声明语法声明。例如,一个不须要参数且没有返回值的代理能够用以下代码声明:

delegate void TheDelegate();

  建立代理实例的语法是:使用new关键词,并引用一个实例或类方法,该方法必须符合代理指定的特征。一旦建立了代理的实例,咱们就能够用调用方法的语法调用它。

  3.七、包装和解除包装

  在面向对象的编程语言中,咱们一般使用的是对象。但为了提升速度,C#也提供了简单数据类型。所以,C#程序既包含一大堆的对象,又有大量的值。在这种环境下,让这二者协同工做始终是一个不可回避的问题,你必需要有一种让引用和值进行通讯的方法。

  在C#以及.NET运行时环境中,这个“通讯”问题经过包装(Boxing)和解除包装(Unboxing)解决。包装是一种让值类型看起来象引用类型的处理过程。当一个值类型(简单数据类型)被用于一个要求或者可使用对象的场合时,包装操做自动进行。包装一个value-type值的步骤包括:分配一个对象实例,而后把value-type值复制到对象实例。

  解除包装所执行的动做与包装相反,它把一个引用类型转换成值类型。解除包装操做的步骤包括:首先检查并确认对象实例确实是给定value-type的一个通过包装的值,而后从对象实例复制出值。

  Java对该问题的处理方式略有不一样。Java为每一种简单数据类型提供了一个对应的类封装器。例如,用Integer类封装int类型,用Byte类封装byte类型。

相关文章
相关标签/搜索