在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
目录
我很多朋友或者同事都以为我不喜欢使用Java,他们都认为我是一个纯粹的.NET技术爱好者,其实事实并不是这样子的:)。我也喜欢使用Java,并已经从事Java开发很多年了。 网上已经有很多有关C#与Java之间“异同”的文章,但是我认为并没有哪一篇文章的内容让我非常满意。在接下来我的几篇博客里,我将会竭尽全力地去阐述它们两者之间的“异同之处”。虽然我并不想挑起“口水仗”,但是我还是会以我的角度去阐述谁好谁不好(毫不留情地)。我尽可能地覆盖各个方面,但是并不会去深入比较两者的API,相反我会将重点放在各自语言特性上。 第一篇博客主要集中在各自的顶层结构上,比如命名空间、类型等。这里两者的版本分别是Java8和C#5.0。 两种语言都是大小写敏感的,严格意义上的“面向对象语言”,支持类、枚举以及接口,并且只允许单继承,所以的类型定义必须放在命名空间(namespace/package)中。同时,都支持注释,方法以及字段(包括静态的)。两者的最基本类型均是Object。两者都有相同的基本运算符、相似的异常处理机制。两者的程序启动方法均是一个名叫Main/main的静态方法。 一个Java类编译之后会生成一个class文件,这些文件虽然可以单独存在,但是实际中它们通常和一些清单文件一起被打包进jar,war或者ear文件中,这样做是为了方便管理。jar(或其他格式)文件中还可以包含一些其他资源,比如图片,文本文件等。 C#类通常存在于一个程序集中,程序集有两种格式:
程序集同样可以包含一些元数据、嵌入的资源。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):
如你所见,对于所有的整型类型,C#提供有符号和无符号两种,并且还提供精度更高的Decimal类型。(译者注:上面列表中,“/”前面是C#中的结构体写法,Int32其实是System.Int32,每种类型都提供一种简写方式,System.Int32对应的简写方式是int。Java中不存在结构体,只有一种写法) C#中提供三种数组:
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#同样允许我们在一个数字后面添加后缀来标明它是什么类型的数据:
大小写均可作为后缀。 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中,情况有一点不同。因为接口中可以有静态成员、方法的实现:
它们同样可以是泛型的。在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 } Java中有以下限制:
一些示例: 1 public class GenericClassWithBaseClassParameter<T extends BaseClass> 2 { 3 4 } 5 public class GenericClassWithInterfaceParameter<T extends Interface> 6 { 7 8 } 9 public class GenericClassWithBaseMatchingParameter<T, ? super T> 10 { 11 12 } 13 public class GenericClassWithManyInterfaceParameters<T implements BaseInterface1 & BaseInterface2> 14 { 15 16 } 在C#中,委托是一类方法的签名,由以下组成:
一个委托可以指向一个静态的、或者一个实例的甚至一个匿名(lambda表达式)的方法,只要这些方法的签名与委托一致即可。 1 public delegate double Operation(double v1, double v2); 2 //a delegate pointing to a static method 3 Operation addition = Operations.Add; 4 //a delegate pointing to an instance method 5 Operation subtraction = this.Subtract 6 //a delegate pointing to an anonymous method using lambdas 7 Operation subtraction = (a, b) => 8 { 9 return a + b; 10 }; 当然,委托也可以是泛型的,比如: 1 public delegate void Action<T>(T item); 委托默认继承自System.Delegate类型,所以它们对动态以及异步调用均有了默认支持。 Java中有一个与委托类似的结构:函数式接口(译者注:见前面有关接口的内容),它们指那些只包含一个方法声明的接口(可以有其他默认方法)。函数式接口可以用来调用lambda表达式,比如: 1 public interface MyWorkerFunction 2 { 3 @FunctionalInterface 4 public void doSomeWork(); 5 } 6 public void startThread(MyWorkerFunction fun) 7 { 8 fun.doSomeWork(); 9 } 10 public void someMethod() 11 { 12 startThread(() -> System.out.println("Running...")); 13 } 如果一个被标记为“函数式接口”的接口包含了不止一个方法的声明,那么编译不会通过。 (译者注:C#中通常使用委托去实现观察者模式,而Java中使用接口去实现观察者模式) Java中的枚举可以包含多种成员(构造方法、字段以及方法等),甚至可以实现接口,而这些在C#中是不允许的。 1 public enum MyEnumeration implements MyInterface 2 { 3 A_VALUE(1), 4 ANOTHER_VALUE(2); 5 private int value; 6 private MyEnumeration(int value) 7 { 8 this.value = value; 9 } 10 public static String fromInt(int value) 11 { 12 if (value == A_VALUE.value) return ("A_VALUE"); 13 else return ("ANOTHER_VALUE"); 14 } 15 } 在C#中,枚举不包含方法,也不实现接口。但是我们可以定义一个枚举类型,让其继承自一个基础类型(比如int) 1 public enum MyEnumeration : uint 2 { 3 AValue = 1, 4 AnotherValue = 2 5 } 每个枚举类型隐式地继承自System.Enum。 无论是C#还是Java中,我们都可以为每个枚举项指定一个特殊的值,如果不指定,默认被分配一个连续的值。 Java中访问级别:
C#中的:
C#中的继承类与实现接口的语法是一样的: 1 public class MyClass : BaseClass, IInterface 2 { 3 } 但是在Java中,继承类和实现接口的语法不一样,分别使用extends和implements: 1 public class MyClass extends BaseClass implements Interface 2 { 3 4 } 5 public interface DerivedInterface extends BaseInterface1, BaseInterface2 6 { 7 8 } 两者中,都只允许单继承、多实现。并且接口可以继承自其他接口。 在C#中,实现接口有两种方式:
下面看一下在C#中,显式实现IMyInterface1接口和隐式实现IMyinterface2接口: 1 public class MyClass : IMyInterface1, IMyInterface2 2 { 3 void IMyInterface1.MyMethod1() 4 { 5 6 } 7 public void MyMethod2() 8 { 9 10 } 11 } 显式实现的成员总是私有的,并且不能是虚拟的也不能是抽象的。如果我们要调用接口中的方法,必须先将类型实例转换成接口: 1 MyClass c = new MyClass(); 2 IMyInterface1 i = (IMyInterface1) c; 3 i.MyMethod(); Java中只有隐式实现接口的概念: 1 public class MyClass implements MyInterface 2 { 3 public void myMethod() 4 { 5 6 } 7 } 在Java和C#中都支持多层嵌套类的定义,但是在Java中,这些嵌套类既可以是静态嵌套类也可以是实例嵌套类: 1 public class MyClass 2 { 3 public static class MyStaticInnerClass 4 { 5 6 } 7 public class MyInnerClass 8 { 9 10 } 11 } 实例嵌套类的实例化必须通过它的外层类实例来完成(注意这里奇怪的语法): 1 MyClass.MyStaticInnerClass c1 = new MyClass.MyStaticInnerClass(); 2 MyClass c2 = new MyClass(); 3 MyClass.MyInnerClass c3 = c2.new MyInnerClass(); 在C#中,所有的嵌套类在任何时候都可以被实例化,并不需要通过它的外层类实例完成(只要访问级别允许): 1 public class MyClass 2 { 3 public class MyInnerClass 4 { 5 6 } 7 } 8 MyClass.MyInnerClass c = new MyClass.MyInnerClass(); C#中嵌套类的访问级别有以下几种:
然而Java中嵌套类型的访问级别如下:
C#和Java中都有抽象类的概念,定义抽象类的语法也是相同的: 1 public abstract class MyClass 2 { 3 public abstract void myMethod(); 4 } C#中的结构体不能是抽象的。 两种语言中都允许将一个类声明为sealed/final(密封类),我们不能从密封类派生出新的类型: 1 public sealed class MyClass 2 { 3 //a C# sealed class 4 } 5 public final class MyClass 6 { 7 //a Java final class 8 } C#中的结构体总是sealed的。 在C#中,我们可以定义静态类,静态类同时也属于抽象类(不能实例化)、密封类(不能被继承)。静态类中只能包含静态成员(属性、方法、字段以及事件): 1 public static class MyClass 2 { 3 public static void MyMethod() 4 { 5 } 6 public static string MyField; 7 public static int MyProperty { get; set; } 8 public static event EventHandler MyEvent; 9 } Java中没有静态类的概念。(译者注:注意Java中可以有嵌套静态类) 在C#中,结构体、枚举等变量(值类型)均被分配在栈(stack)中,因此它们任何时候都代表了一个具体的值,它们不能为null,但是我们可以使用某种语法创建一个可空的值类型,可以将null赋给它: 1 int ? nullableInteger = null; 2 nullableInteger = 1; 3 if (nullableInteger.HasValue) //if (nullableInteger != null) 4 { 5 int integer = nullableInteger.Value; //int integer = nullableInteger 6 } 在Java中,基本类型(int、bool)变量永远都不能为null,我们需要使用对应的封装类来实现这一目的: 1 Integer nullableInteger = null; 2 nullableInteger = new Integer(1); C#中的类、接口属于引用类型,引用类型变量本身就可以赋值null。 (译者注:在C语言中,普通变量和指针变量有区别,普通变量内存中存储的是变量本身代表的数值,而指针变量内存中存储的是一个内存地址,该地址可以“不存在”(不指向任何内存)。道理跟这里一致。) C#中允许将一个类标记为partial,也就是说,我们可以在多个源文件中同时定义一个类。编译时,这些不同源文件中的代码可以自动组合起来形成一个整体。这非常有利于我们存储那些自动生成的代码,因为自动生成的代码一般不需要再修改,所以完全可以放在一个单独的源文件中: 1 //in file MyClass.Generated.cs 2 public partial class MyClass 3 { 4 public void OneMethod() 5 { 6 7 } 8 } 9 10 //in file MyClass.cs 11 public partial class MyClass 12 { 13 public void AnotherMethod() 14 { 15 16 } 17 } (译者注:部分类的出现,可以说主要是为了方便“可视化开发”,因为在现代软件开发过程中,IDE通常会根据设计器中的操作为我们生成固定代码,这些代码一般不需要我们再人工调整,完全可以单独放在一个源文件中。) 在Java中,我们可以创建一个实现了某些接口、或者继承某个类的匿名类,只要该匿名类中实现了基类(接口)所有没被实现的方法: 1 this.addEventListener(new ListenerInterface 2 { 3 public void onEvent(Object source, Event arg) 4 { 5 6 } 7 }); C#中的匿名类并没有显式定义方法,而仅仅只包含只读属性。如果两个匿名类中的属性类型相同,并且顺序一样,那么就可以认为这两个匿名类是相同的类型。 1 var i1 = new { A = 10, B = "" }; 2 var i2 = new { A = 1000, B = "string" }; 3 //these two classes have the same type 4 i1 = i2; 为了支持匿名类,C#引进了var关键字。 在.NET(C#)中,有如下类型成员:
Java中的类型成员仅包含:
Java和C#中的静态构造方法比较相似,但是语法上有细微差别,Java的静态构造方法这样: 1 public class MyClass 2 { 3 static 4 { 5 //do something the first time the class is used 6 } 7 } 而C#中的静态构造方法这样写: 1 public class MyClass 2 { 3 static MyClass() 4 { 5 //do something the first time the class is used 6 } 7 } Java中支持另外一种封装体:构造代码块。数量上没有限制,这些代码会自动合并到该类的构造方法中: 1 public class MyClass 2 { 3 { 4 System.out.println("First constructor block, called before constructor"); 5 } 6 public MyClass() 7 { 8 System.out.println("MyClass()"); 9 } 10 { 11 System.out.println("Second constructor block, called before constructor but after first constructor block"); 12 } 13 } 在C#中,析构方法(或者说析构器)是Finalize方法的一种简写方式。当GC准备回收一个堆中对象的内存之前时,会先调用对象的析构方法。Java中有一个确定的方法叫finalize,它的功能与C#中的析构方法类似。 在C#中,我们可以按照C++中那种 |
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论