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


    原文地址

    目录

    介绍

    我很多朋友或者同事都以为我不喜欢使用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这个目录下,否则,编译不会通过。编译产生的.class文件也必须放在同一个目录之下,比如aMyClass.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

    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 }
    View Code

    委托

    在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 }
    View Code

    在C#中,枚举不包含方法,也不实现接口。但是我们可以定义一个枚举类型,让其继承自一个基础类型(比如int)

    1 public enum MyEnumeration : uint
    2 {
    3     AValue = 1,
    4     AnotherValue = 2
    5 }

    每个枚举类型隐式地继承自System.Enum。

    无论是C#还是Java中,我们都可以为每个枚举项指定一个特殊的值,如果不指定,默认被分配一个连续的值。

    类型访问级别

    Java中访问级别:

    • package:包内其他类型可访问(默认访问级别);
    • public:所有人可以访问。

    C#中的:

    • internal:程序集中其他类型可访问(默认访问级别);
    • public:所有人可以访问。

    继承

    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#中嵌套类的访问级别有以下几种:

    • internal同一程序集中的其他类型可以访问(默认);
    • protected子类可以访问(包括自己);
    • protected internal同一程序集或其子类可以访问(译者注:这里取的是并集);
    • private自己可以访问;
    • public所有人均可以访问。

    然而Java中嵌套类型的访问级别如下:

    • package同一包内可访问;
    • private自己可访问;
    • protected子类可访问(包括自己);
    • public所有人可访问。

    抽象类

    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++中那种语法去定义析构方法:

    1 public class MyClass
    2 {
    3     ~MyClass()
    4     {
    5         //object is being freed
    6     }
    7 }

    静态成员

    不像C#,Java中允许我们通过一个类实例去访问类中的静态成员,比如:

    1 public class MyClass
    2 {
    3     public static void doSomething()
    4     {
    5     }
    6 }
    7 
    8 MyClass c = new MyClass();
    9 c.doSomething();

    属性

    属性在C#中非常有用处,它允许我们使用一种清晰的语法去访问字段:

    1 public class MyClass
    2 {
    3     public int MyProperty { get; set; }
    4 }
    5 
    6 MyClass c = new MyClass();
    7 c.MyProperty++;

    译者注:原作者在这里举的例子,只是简单地说明C#中属性的用法,并没有充分体现出属性的重要作用。

    我们可以定义一个自动属性(比如上面例子),还可以显式定义私有字段:

     1 public class MyClass
     2 {
     3     private int myField;
     4     public int MyProperty
     5     {
     6         get
     7         {
     8             return this.myField;
     9         }
    10         set
    11         {
    12             this.myField = value;
    13         }
    14     }
    15 }
    View Code

    在Java中,只能通过方法:

    1 public class MyClass
    2 {
    3     private int myProperty;
    4     public void setMyProperty(int value) { this.myProperty = value; }
    5     public int getMyProperty() { return this.myProperty; }
    6 }
    7 
    8 MyClass c = new MyClass();
    9 c.setMyProperty(c.getMyProperty() + 1);

    C#中,我们还可以为类、结构体以及接口定义索引器,比如:

     1 public class MyCollection
     2 {
     3     private Object [] list = new Object[100];
     4     public Object this[int index]
     5     {
     6         get
     7         {
     8             return this.list[index];
     9         }
    10         set
    11         {
    12             this.list[index] = value;
    13         }
    14     }
    15 }
    View Code

    索引不止限制于整型,还可以是其他任何类型。

    最后,属性还可以有不同的访问级别:

     1 public int InternalProperty
     2 {
     3     get;
     4     private set;
     5 }
     6 
     7 public string GetOnlyProperty
     8 {
     9     get
    10     {
    11         return this.InternalProperty.ToString();
    12     }
    13 }

    事件

    C#中一般使用事件去实现“观察者模式”,事件允许我们注册一个方法,当事件激发时,该方法会被调用。

     1 public class MyClass
     2 {
     3     public event EventHandler MyEvent;
     4     public void ClearEventHandlers()
     5     {
     6         //check for registered event handlers
     7         if (this.MyEvent != null)
     8         {
     9             //raise event
    10             this.MyEvent(this, EventArgs.Empty);
    11             //clear event handlers
    12             this.MyEvent = null;
    13         }
    14     }
    15 }
    16 
    17 MyClass a = new MyClass();
    18 //register event handler
    19 c.MyEvent += OnMyEvent;
    20 //unregister event handler
    21 c.MyEvent -= OnMyEvent;
    View Code

    跟属性一样,C#中也允许我们自己显式实现访问器(add/remove),这样我们可以更灵活控制事件的注册和注销:

     1 public class MyClass
     2 {
     3     private EventHandler myEvent;
     4     public event EventHandler MyEvent
     5     {
     6         add
     7         {
     8             this.myEvent += value;
     9         }
    10         remove
    11         {
    12             this.myEvent -= value;
    13         }
    14     }
    15 }
    16 
    17  
    View Code

    字段和属性的自动初始化

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

    成员访问级别

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

    • private类型内部可访问
    • internal同一程序集中可访问
    • protected子类可访问(包括自己)
    • protected internal同一程序集或者子类可访问(译者注:这里取两者并集)
    • public所有人均可访问。

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

    • package同一包中可访问
    • protected子类可访问(包括自己)
    • private自己可访问
    • public所有人可访问。

    虚拟成员

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

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

     1 public class MyBaseClass
     2 {
     3     public virtual void MyMethod()
     4     {
     5 
     6     }
     7 }
     8 public class MyDerivedClass : MyBaseClass
     9 {
    10     public override void MyMethod()
    11     {
    12 
    13     }
    14 }

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

     1 public class MyBaseClass
     2 {
     3     public void MyMethod()
     4     {
     5 
     6     }
     7 }
     8 
     9 public class MyDerivedClass : MyBaseClass
    10 {
    11     public new void MyMethod()
    12     {
    13         //no relation with MyBaseClass.MyMethod
    14     }
    15 }

    密封成员

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

    C#中的语法为:

    1 public class MyClass
    2 {
    3     public sealed void DoSomething()
    4     {
    5 
    6     }
    7 }

    Java中的语法为:

    1 public class MyClass
    2 {
    3     public final void doSomething()
    4     {
    5 
    6     }
    7 }

    抽象成员

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

    泛型方法

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

     1 public class MyClass
     2 {
     3     public static int Compare<T>(T v1, T v2)
     4     {
     5         if (v1 == v2)
     6         {
     7             return 0;
     8         }
     9         return -1;
    10     }
    11 }
    12 //no need to specify the int parameter type
    13 int areEqual = MyClass.Compare(1, 2);

    只读字段

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

    1 public static class Singleton
    2 {
    3     //a C# readonly field
    4     public static readonly Singleton Instance = new Singleton();
    5 }

    Java中使用final来标记:

    1 public class Singleton
    2 {
    3     //a Java final field
    4     public static final Singleton INSTANCE = new Singleton();
    5 }

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

    1 public static class Maths
    2 {
    3     //a C# constant field
    4     public const double PI = 3.1415;
    5 }

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

    技术审阅

    写这篇博客时,我的好友Roberto Cortez对内容进行了核查,谢谢他!

  • 相关阅读:
    Laravel框架中的event事件操作
    PHP魔术方法实例
    PHP 面向对象
    ThinkPHP中where()使用方法详解
    PHP常见错误提示含义解释
    php面向对象编程self和static的区别
    php文件路径获取文件名
    php三种无限分类
    php高精度计算问题
    转:JavaScript定时机制、以及浏览器渲染机制 浅谈
  • 原文地址:https://www.cnblogs.com/xiaozhi_5638/p/4383783.html
Copyright © 2020-2023  润新知