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

周見智發表於2015-04-01

原文地址

目錄

 

介紹

我很多朋友或者同事都以為我不喜歡使用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\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):

  • 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對內容進行了核查,謝謝他!

相關文章