Java 和 C# 的比较研究






2.68/5 (29投票s)
2003年8月31日
14分钟阅读

153830

2
一篇简短的文章,比较和对比 Java 和 C# 的特性。
目录
- 引言
- 面向对象范例
- 运行时环境
- 从命令行编译和运行
- 命名空间和类
- 别名
- 预处理器指令
- 构造函数、析构函数和垃圾回收
- 多个 Main 方法
- 关键字比较
- 常量
- 只读字段
- 逐字字符串
- 值类型
- 访问修饰符
- 类型安全
- 索引器 (Indexers)
- 全局方法
- Foreach 语句
- Switch 语句
- Goto 语句
- 虚方法
- 交错数组
- 集合
- 枚举器
- 装箱
- Reflection(反射)
- 继承
- 父类构造函数
- 异常处理
- 线程同步与安全
- 指针
- 结构体
- 属性
- 属性
- 委托(Delegates)
- 事件
- 文件和流
- 序列化
- 运算符重载
- 结论
- 参考文献
引言
C# 像 Java 一样,是一种“简单、面向对象、分布式、解释型、健壮、安全、可移植、高性能、多线程和动态的语言”。C# 更进一步,实现了“跨语言互操作性”和“混合语言编程”等特性。它支持面向组件的编程,非常适合分布式 Web 应用程序。Java 与 Windows OS 耦合不紧密可能是另一个缺点,因为 Windows 是最广泛使用的操作系统。尽管 Java 和 C# 都继承了 C 和 C++ 的特性,但 C# 不能被视为 Java 的继承者。相反,它们可以被视为同等地位的语言,有许多共同点,但各有独特之处。C# 在开发时考虑了 .NET 平台,因此是开发 .NET 应用程序最可能的选择。简而言之,C# 是一种简单但强大的语言,在概念上类似于 C,功能强大如 C++,优雅如 Java,并且像 VB 一样有用。
面向对象范例
Java 和 .NET 都将数据视为程序开发中的关键要素。每个问题都被分解为一组称为“对象”的实体,然后围绕这些实体构建数据和函数。软件对象由一组相关的数据和函数组成。两者都遵循单一根对象的层次结构。Java 框架中的每个类都是 java.lang.Object 的子类,而 .NET 框架中的每个类都是 System.Object 的子类。
运行时环境
CLR(通用语言运行时)管理 .NET 环境中程序的执行。当 C# 代码编译时,输出的不是可执行文件,而是一种中间代码,称为 MSIL(Microsoft Intermediate Language)。MSIL 是一组汇编级别的指令,它们是平台无关的,并且在概念上等同于 Java 中的字节码。CLR 然后使用 JIT(即时)编译器在运行时将 MSIL 翻译成可执行代码。CLS(通用语言规范)是一组 .NET 编译器应遵守的规则,以促进与其他语言的互操作性。CTS(通用类型系统)是 CLS 的一个子集,每个 .NET 编译器都必须实现它。C# 同时实现了 CLS 和 CTS。最重要的是,该规范已开放(使用 XML 和 SOAP 等开放标准技术),以便可以移植到非 Windows 平台。与 JVM(Java 虚拟机)相比,CLR 设计得很好,并且支持托管代码和非托管代码的执行。.NET 可以被视为 CLR 的扩展,增加了 Windows Forms 和 Enterprise Services 等额外功能。JVM 和 C# 的基本区别在于 C# 编译器生成可执行代码。
从命令行编译和运行程序
在 Java 中
> javac <program_name>.java
> java <program_name>
在C#中
> csc <program_name>.cs
> <program_name>
C# 拥有一个高度复杂的 IDE,Visual Studio .NET,优于任何 Java IDE。
命名空间和类
命名空间用于在语义上对元素进行分组,以提高类和其他命名空间的可组织性和可读性。在一个命名空间中声明的名称不会与其他命名空间中声明的名称冲突。它可以嵌套到任何级别。它们在 Java 中类似于包。
package mySpace1.mySpace2.mySpace3;
class FirstClass
{
}
Java 中的一个规范(如上所示)将相当于 C# 中如下所示的代码
namespace mySpace1
{   namespace mySpace2
   {
     namespace mySpace3
      {
            class FirstClass
            { 
             }
      }
   }  
}
别名
C# 中的 using 指令等同于 Java 中的 import 指令。完全限定名可以用别名代替,就像 C 和 C++ 中使用 typedef 一样。这样做是为了提高可读性。
using printpath = System.Console; 
class testApp{   
    static void Main ( ) { printPath (“Alias test”); }     
}
预处理器指令
与 Java 不同,与 C/C++ 类似,C# 支持使用预处理器指令,如 #define、#undef、#if、#elif、#else、#endif、#warning、#error、#line、#region、#endregion。但是,C# 不支持宏。
构造函数、析构函数和垃圾回收
在 Java 和 C# 中,类都是使用 new 关键字实例化的。构造函数在创建对象时对其进行初始化,并且其语法与 Java 中的相同。
<access modifier>  <classname> 
{ 
     /* constructor code */ 
}
从未使用过的对象中回收内存是动态分配方案的关键方面。在 C# 中,这通过以下两种方式实现
- 它确实有一个类似 Java 的“垃圾回收”方案,在该方案中,系统会自动、不定期地从对象中回收内存,而程序员对此不知情。
- 析构函数可以像 C++ 一样显式调用。这类似于 Java 中的 finalizer方法。
~<classname> 
{ 
     /* constructor code */ 
}
多个 Main 方法
与 Java 不同,C# 支持在单个类中使用多个 main 方法。它用于针对多个测试用例测试类。
 MultipleMain.cs /main:<class name>
关键字比较
与 Java 不同,C# 中的所有数据类型都是对象。C# 中的关键字集更丰富,因为它比 Java 包含更多的关键字。C# 包含以下关键字,此外还包含几乎所有 Java 中的关键字:as、byte、checked、decimal、delegate、enum、event、explicit、fixed、foreach、get、implicit、in、object、operator、out、override、params、readonly、ref、set、sizeof、stackalloc、string、struct、typeof、uint、ulong、unchecked、unsafe、ushort、value 和 virtual。
常量
Java 使用 static final 关键字,而 C# 使用 const 关键字来声明常量。
只读字段
这类似于 Java 中的 final 指令,但它直到运行时才会获得值,并且一旦初始化,该值将保持不变直到结束。
逐字字符串
逐字字符串字面量通过将所需字符串括在 @" 和 " 字符中来定义。逐字字符串语法可以跨越多行,并且会保留空格。这是 C# 的一项独特功能。
string pathname = @"C:\Programs\"
值类型
出于效率目的,CTS(通用类型系统)分为 2 类
- 值类型:此变量包含实际数据。它始终包含一个值,并且不能为 null。例如:枚举、结构和基本类型。 
- 引用类型:这指向已分配的指定类型对象的地址(类型安全指针)。当为 null时,表示它不指向任何对象。例如:类、数组、接口、委托。 
访问修饰符
protected 访问修饰符的作用域与 Java 中的 protected 访问修饰符略有不同。受保护的实体(方法或数据)只能被同一类或其子类访问。internal 访问修饰符在 C# 中等同于 Java 中的 protected 访问修饰符。默认情况下,C# 中所有方法的访问修饰符都是 private,而 Java 的默认访问修饰符是 protected。C# 还有 internal protected 访问修饰符。
类型安全
C# 是一种健壮的语言,并提供许多类型安全措施。它不允许不安全的强制类型转换,检查数组边界,传递的引用参数是类型安全的,并且所有动态分配的对象都初始化为零。
索引器 (Indexers)
索引器允许对象像数组一样被索引。Java 和 C# 都支持索引器。索引器的主要用途是支持创建受约束的专用数组。
element_type this [int index] 
get{ 
    /* return the value specified by index */ 
} 
set{   
    /* set the value specified by the index */ 
}
全局方法
与 Java 一样,方法必须是类的一部分,不允许全局方法。两者都支持对象超出范围(或没有引用)时自动销毁以及内存管理。
foreach 语句
foreach 循环是 C++ 中 for 循环的一个变体,用于遍历对象集合中的元素。
Switch 语句
C# 中的 switch 语句是 Java 中 switch 语句的增强版本。C# 支持在 switch 语句中使用字符串字面量。除非标签中不包含任何代码,否则它也不允许贯穿。
Goto 语句
Java 不支持“goto”语句。尽管备受诟病,C# 支持 goto 语句以提高代码的可重用性和清晰度。
虚方法
在 Java 中,默认情况下所有方法都是虚方法。C# 遵循 C++ 模型,其中必须使用 virtual 关键字显式将方法指定为虚方法。在子类中,可以使用 override 关键字显式覆盖虚方法,或者使用 new 关键字显式选择不覆盖。
交错数组
交错数组是 C# 的一项独特功能。它是数组的数组,其中每个数组的大小可变。它可以被视为一个表格,其中所有行的长度都不相同。Java 没有提供这种数组的机制。
集合
在 Java 和 C# 中,集合都是将多个元素组合成一个单元的对象。集合用于存储、检索、操作和将数据从一个方法传递到另一个方法。System.Collections 在 C# 中包含一组定义良好的接口和类,这些接口和类实现了各种类型的集合。它们通过包含 ArrayList、Stack、Queue、HashTable 等内置接口来简化编程任务。
枚举器
枚举器是一种特殊类型的值类型,限于一组有限且不可更改的数值。定义枚举器时,会提供字面量,然后将它们用作相应值的常量。对于第一个字面量,如果未分配其值,则其值设置为 0。对于所有其他字面量,其值比前一个字面量多一。
public enum Days { 
    Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday
}
装箱
在 Java 中,原始数据类型必须放在适当的包装类中才能作为“对象”使用。在 C# 中,这是通过“装箱”实现的,其中包装类是隐式实现的,不会暴露给程序员。C# 允许将任何值类型转换为相应的引用类型,并将结果“装箱”类型转换回来。当值类型需要作为对象访问时,就会使用此方法。
int val = 101;
object box = val;
if (box is int) 
{ 
    Console.Write ("Box contains an int"); 
}
Reflection(反射)
反射使人们能够找出对象的类型。查询 Type 对象,它会反射与类型相关的信息。这是一种强大的机制,因为人们可以学习和使用仅在运行时才知道的类型的能力。Java 和 C# 都支持反射,但它们的实现略有不同。在 Java 中,反射是在类级别进行的,而在 C# 中,反射是在包级别进行的。
C# 还有 3 个用于运行时类型标识的关键字。
- is:C# 使用- is运算符来确定对象是否为特定类型。它等同于 Java 中的- instanceof运算符。
- as:C# 使用- as运算符在运行时进行类型转换。如果转换成功,则返回类型信息,否则返回 null 引用。
- typeof:C# 使用- typeof运算符获取给定对象的- System.Type,通过它可以确定对象的所有特征。
继承
C# 和 Java 都只支持单级继承,多级继承通过实现接口来完成。Java 和 C# 都可以使用 final 和 sealed 关键字来阻止类的继承。
父类构造函数
base( ) 在 C# 中等同于 Java 中的 super( ) 方法调用,它使您可以调用基类的构造函数。
异常处理
两种语言都使用 try 和 catch 块进行异常处理。try 块指示被检查的区域,而 catch 块处理异常。可以有多个 catch 块来处理多个异常,并使用 finally 块来处理前面 catch 块未处理的任何异常。无论是否生成异常,此代码都会运行。
线程同步与安全
C# 使用 lock 关键字,它等同于 Java 中的 synchronized 关键字。与 Java 类似,C# 也支持同步方法的概念。lock 语句确保由锁保护的代码段对于给定对象只能由获取锁的线程使用。所有其他线程将被阻塞,直到锁被释放。当退出代码块时,锁会被释放。这是通过 C# 中的 Monitor 类实现的。
lock (object)
{
    /* code to be synchronized */
}
指针
Java 不支持指针。使用 C# 中的指针的一个主要问题是存在后台自动垃圾回收进程。垃圾回收尝试释放内存可能会在程序员不知情的情况下更改当前对象的内存位置。因此,任何先前指向该对象的指针将不再指向它。这可能会损害 C# 程序的运行,并可能影响其他程序的完整性。因此,使用指针的代码必须由程序员显式标记为 unsafe。
ref、out(类似于 C 中的指针)告诉 C# 编译器正在传递的参数指向与调用代码中的变量相同的内存。ref 暗示变量必须被初始化。out 暗示变量的初始化不是必需的。
结构体
C# 支持结构,这些结构是值类型,在声明后即被分配。为 struct 创建无参数构造函数是错误的。仅当要包含的数据量非常小,并且方法很少或没有方法时,才使用它。
属性
属性将一个字段与访问它的方法结合起来。它包含一个名称以及 get 和 set 访问器。
<return type> name
{       
    get { /* get accessor code */  } 
    set {  /* set accessor code */  }
}
属性
C# 允许以属性的形式向程序添加声明性信息。属性定义与类、结构或方法相关的附加信息。它不是类的成员,它只指定附加到某个项的附加信息。Java 也有一个支持属性的完善的 API。
委托(Delegates)
委托是一个可以引用方法的对象,并且可以通过此引用调用该方法。通过简单地更改委托引用的方法,可以在运行时使用相同的委托调用不同的方法。
delegate <return_type> <delegate_name> (parameter list);
事件
事件是某个操作已发生的自动通知。对事件感兴趣的对象会为该事件注册一个事件处理程序。当事件发生时,会调用所有注册的事件处理程序。Java 和 C# 都支持事件处理。在 C# 中,事件处理程序由委托表示。
event <event_delegate> object;
文件和流
在 C# 和 Java 中,I/O 和文件处理操作都使用流来实现。
序列化
.NET 环境使用序列化机制来支持用户定义类型的流。它允许您通过文件等流读取和写入对象。它也被称为对象持久化,因为它在对象处于某种存储形式或在不同位置之间传输时保留其状态。序列化是以自动且无缝的方式传输类表示的最佳手段。C# 中的可序列化对象使用 [Serializable] 属性进行指定。不应序列化的成员使用 [Nonserialized] 属性进行指定。在 C# 中,可序列化成员以 2 种格式存储
- XML:这是人类可读的。
- 二进制格式:机器可读的。
还可以使用 ISerializable 接口自定义对象的序列化。Java 中的可序列化对象实现 Serializable 接口。transient 关键字用于指定不应序列化的成员。Java 中的默认可序列化格式是二进制,但它允许自定义格式。
运算符重载
Java 不支持运算符重载,而 C# 像 C++ 一样支持运算符重载。
结论
Java 和 C# 都拥有完善的 API,它们本身都是非常强大的编程语言,并且彻底改变了编程世界。C# 和 Java 之间的竞争是近年来对软件行业最有利的事情之一。C# 作为最新的语言,有机会纠正 Java 的某些缺点,而 Java 则拥有已经确立的语言的优势。最终结果是更好的编程实践,从而带来更好的应用程序。程序员现在可以根据自己的需求,从 Java 或 .NET 平台提供的丰富功能中进行选择。Visual Studio IDE 是一项杰出的作品,它将在提高 .NET 技术的普及度方面发挥重要作用。可以有把握地说,完全取代 Java 并用 .NET 取代它将是一项艰巨的任务。但 C# 汲取了过去的精华,并融入了现代计算机语言设计的最新和最佳成果,从而为自己赢得了作为有史以来最优秀的编程语言之一的地位。
参考文献
书籍
- Inside C#,Tom Archer 和 Andrew Whitechapel,Microsoft Press,ISBN 0735616485
- C#: The Complete Reference,Herbert Schildt,McGraw-Hill Osborne Media,ISBN 0072134852
- .NET Development for Java Programmers,Paul Gibbons,APress Publishers,ISBN 1590590384
- Java in a Nutshell,David Flanagan,O'Riley and Associates Inc.,ISBN 156592262X
- Programming in C#,E. Balagurusamy,Tata McGraw Hill Publishers,ISBN 0070473390
