原文地址:http://www.25hoursaday.com/CsharpVsJava.html html
簡介 java
C#語言是一門面向對象的語言,開發者可使用C#和微軟.NET平臺快速構建各類應用程序。C#和.NET平臺的目標是把開發者從一些諸如內存管理、類型安全問題、底層類庫、數組邊界檢查等等的底層問題中解放出來並節約大量的時間,這樣開發者就能夠真正把時間和精力放在他們的應用程序和業務邏輯上。對於Java開發者來講,把前面那句話的開頭改成「Java語言和平臺」,這句話也一樣能夠總結Java語言和平臺。 linux
後面的內容介紹了C#和Java編程語言的異同,這些都是基於我使用兩個語言的經歷。全部代碼都通過微軟.NET框架2.0以及Java SE 6的測試。 算法
注意:做者有些代碼不符合.NET 3.5或JAVA SE 7(或以上)版本的最佳實踐寫法而且也不能覆蓋它們所提供的新語法和新特性,但不影響本文的重點也就是平臺的比較。 編程
第一部分:C#和JAVA基本一致的地方 設計模式
一、咱們都是Object 數組
C#的類層次中有一個根,也就是全部C#的類都是System.Object的子類,Java也是這樣,全部類都是java.lang.Object的子類。兩個語言的Object類的方法有些相同(好比System.Object的ToString()和java.lang.Object的toString()),也有一些不一樣(System.Object沒有java.lang.Object中的wait()、notify()或notifyAll())。 安全
注意:在C#中object類能夠寫成object或Object。小寫的object C#關鍵字在編譯的時候會替換爲System.Object。 網絡
二、關鍵字一覽 數據結構
Java和C#有不少語法很類似,除了throws、transient和strictfp幾乎全部Java關鍵字都有C#的對應。下表示Java和C#的關鍵字對照表,Java關鍵字標紅而C#關鍵字標藍。
注意:儘管goto和const是Java語言的關鍵字,可是在Java中並無用到。C#中的[NonSerialized]特性等價於Java中的transient關鍵字。
三、虛擬機和語言運行時
Java通常編譯成Java字節碼並運行於託管的執行環境(Java虛擬機、JVM),一樣,C#代碼編譯成中間語言(IL)運行於公共語言運行時(CLR)。兩個平臺都經過JIT編譯器提供本機編譯。
注意:雖然Java平臺支持字節碼的解釋和JIT編譯兩種方式,可是.NET平臺只支持C#代碼的本機執行,IL代碼在運行前老是會編譯成本機代碼。
四、基於堆和垃圾收集
在Java中,對象使用new關鍵字建立在堆上。在C#中大多數類使用new關鍵字建立在堆上。和JVM同樣,CLR也是經過標記和壓縮垃圾回收算法管理銷燬對象。
注意:C#還支持基於棧的類,叫作值類型,後文會介紹到這個。
五、數組能夠是交錯的
對於C或C++這樣的語言,多維數組的每個子數組都必須有相同的維度。在Java和C#中數組沒必要統一,交錯數組能夠認爲是數組的一維數組。交錯數組的項就是保持類型或引用的另外一個數組,這樣交錯數組的行和列就不須要有統一的長度,以下代碼所示:
int [][]myArray = new int[2][]; myArray[0] = new int[3]; myArray[1] = new int[9];
六、沒有全局方法
和Java同樣,和C++不同,C#中的方法必須是類的一部分,做爲成員方法或靜態方法。
七、有接口但沒有多重繼承
C#和Java同樣支持接口的概念,接口相似純抽象類。C#和Java同樣都支持類的單繼承,但支持藉口的多重繼承(或實現)。
八、字符串不可變
C#的System.String類和java.lang.String類類似。它們都是不可變的,也就是字符串的值在建立後一次都不能修改。字符串提供的一些實例方法看似能夠修改字符串的內容,實際上是建立了一個新的字符串並返回,原始的字符串並無修改。以下的C#和Java代碼都沒有修改字符串:
C# Code String csString = "Apple Jack"; csString.ToLower(); Java Code String jString = "Grapes"; jString.toLowerCase();
九、密封類
Java和C#都提供了一種機制確保類是繼承體系中的最後一個,不能夠有子類。在Java中能夠爲類修飾final關鍵字,而C#則經過sealed關鍵字修飾類。以下是兩種語言密封類的例子:
C# Code sealed class Student { string fname; string lname; int uid; void attendClass() {} } Java Code final class Student { String fname; String lname; int uid; void attendClass() {} }
十、拋出和捕獲異常
C#和Java的異常有不少類似的地方。兩種語言都使用try塊來表示須要守護的區域,catch塊來處理拋出的異常,finally塊在離開方法以前釋放資源。兩種語言都有繼承體系,全部的異常都從一個Exception類繼承。而且均可以在捕獲到異常並進行了一些錯誤處理以後從新拋出異常。最後,它們都提供了機制把異常包裝成另一個異常,這樣就能夠捕獲到一個異常後拋出另外一個異常。這裏能夠舉一個例子,好比三層結構的應用程序,數據訪問的時候捕獲到了一個SQLException,能夠拋出一個應用程序相關的異常。這樣的話,應用程序異常可使用原始的SQLException來初始化,處理應用程序異常的時候若是須要還能夠訪問到原始的異常。以下代碼演示了兩種語言在異常方面的類似:
C# Code using System; using System.IO; class MyException: Exception{ public MyException(string message): base(message){ } public MyException(string message, Exception innerException): base(message, innerException){ } } public class ExceptionTest { static void DoStuff(){ throw new FileNotFoundException(); } public static void Main(string[] args){ try{ try{ DoStuff(); return; //won't get to execute }catch(IOException ioe){ /* parent of FileNotFoundException */ throw new MyException("MyException occured", ioe); /* rethrow new exception with inner exception specified */ } }finally{ Console.WriteLine("***Finally block executes even though MyException not caught***"); } }//Main(string[]) } // ExceptionTest Java Code class MyException extends Exception{ public MyException(String message){ super(message); } public MyException(String message, Exception innerException){ super(message, innerException); } } public class ExceptionTest { static void doStuff(){ throw new ArithmeticException(); } public static void main(String[] args) throws Exception{ try{ try{ doStuff(); return; //won't get to execute }catch(RuntimeException re){ /* parent of ArithmeticException */ throw new MyException("MyException occured", re); /* rethrow new exception with cause specified */ } }finally{ System.out.println("***Finally block executes even though MyException not caught***"); } }//main(string[]) } // ExceptionTest
十一、定義的時候進行成員初始化和靜態構造方法
C#和Java中均可以在定義實例和靜態變量的時候進行初始化。若是成員變量是實例變量則在構造方法執行以前調用,靜態成員則在第一次使用成員以及第一次建立類實例以前初始化。還能夠指定一段代碼在類建立以前或靜態方法調用以前被調用。這段代碼在C中叫作靜態構造方法而在Java中叫作靜態初始化塊。靜態構造方法會在第一次調用類的靜態方法或第一次建立類實例以前調用。
using System; class StaticInitTest{ string instMember = InitInstance(); static string staMember = InitStatic(); StaticInitTest(){ Console.WriteLine("In instance constructor"); } static StaticInitTest(){ Console.WriteLine("In static constructor"); } static String InitInstance(){ Console.WriteLine("Initializing instance variable"); return "instance"; } static String InitStatic(){ Console.WriteLine("Initializing static variable"); return "static"; } static void DoStuff(){ Console.WriteLine("Invoking static DoStuff() method"); } public static void Main(string[] args){ Console.WriteLine("Beginning main()"); StaticInitTest.DoStuff(); StaticInitTest sti = new StaticInitTest(); Console.WriteLine("Completed main()"); } }
class Main{ String instMember = initInstance(); static String staMember = initStatic(); Main(){ System.out.println("In instance constructor"); } static{ System.out.println("In static constructor"); } static String initInstance(){ System.out.println("Initializing instance variable"); return "instance"; } static String initStatic(){ System.out.println("Initializing static variable"); return "static"; } static void doStuff(){ System.out.println("Invoking static DoStuff() method"); } public static void main(String[] args){ System.out.println("Beginning main()"); Main.doStuff(); Main sti = new Main(); System.out.println("Completed main()"); } }
代碼執行結果:
Initializing static variable
In static constructor
Beginning main()
Invoking static DoStuff() method
Initializing instance variable
In instance constructor
Completed main()
注意:這裏做者的代碼有誤,小小修改了一下
十二、裝箱
在某些狀況下,值類型須要當作對象,.NET和Java運行時會自動把值類型轉換成在堆上分配的引用類型,這個過程叫作裝箱。自動把對象轉換成相應的值類型的過程叫作拆箱,好比把java.lang.Integer的實例轉換成int。以下例子演示了運行時發生裝箱的各類狀況:
C# Code using System; using System.Collections; //分配在棧上的結構須要裝箱才能當作object struct Point{ //member fields private int x; private int y; public Point (int x, int y){ this.x = x; this.y = y; } public override string ToString(){ return String.Format("({0}, {1})", x, y); } }//Point class Test{ public static void PrintString(object o){ Console.WriteLine(o); } public static void Main(string[] args){ Point p = new Point(10, 15); ArrayList list = new ArrayList(); int z = 100; PrintString(p); //p在傳參的時候裝箱了 PrintString(z); //z在傳參的時候裝箱了 // 在保存到集合的時候int和float裝箱了 // 不須要Java的包裝類 list.Add(1); list.Add(13.12); list.Add(z); for(int i =0; i < list.Count; i++) PrintString(list[i]); } }
Java Code import java.util.*; class Test{ public static void PrintString(Object o){ System.out.println(o); } public static void PrintInt(int i){ System.out.println(i); } public static void main(String[] args){ Vector list = new Vector(); int z = 100; Integer x = new Integer(300); PrintString(z); //z boxed to object when passed to PrintString PrintInt(x); //x unboxed to int when passed to PrintInt // integers and float boxed when stored in collection // therefore no need for Java wrapper classes list.add(1); list.add(13.12); list.add(z); for(int i =0; i < list.size(); i++) PrintString(list.elementAt(i)); } }
第二部分:C#和JAVA基本一致但語法不一樣的地方
一、Main方法
C#和Java程序的入口點都是main方法。表面區別是C#的Main方法是大寫的M(.NET框架的方法名的慣例),而Java中的main方法是小寫字母m(一樣也是Java方法的慣例)。還有一個區別就是C#的Main()方法能夠沒有參數。
C# Code using System; class A{ public static void Main(String[] args){ Console.WriteLine("Hello World"); } } Java Code class B{ public static void main(String[] args){ System.out.println("Hello World"); } }
通常推薦應用程序的每個類都有一個main方法來測試類的功能,好比可能有兩個類A和B都包含main方法。在Java中,類是編譯的單元,只須要經過命令行指定須要運行的類,在C#中也可使用/main開關編譯應用程序指定應用程序建立時使用哪一個main做爲入口點實現相同的效果。使用main以及預處理指令條件編譯是不錯的測試技術。
Java Example C:\CodeSample> javac A.java B.java C:\CodeSample> java A Hello World from class A C:\CodeSample> java B Hello World from class B C# Example C:\CodeSample> csc /main:A /out:example.exe A.cs B.cs C:\CodeSample> example.exe Hello World from class A C:\CodeSample> csc /main:B /out:example.exe A.cs B.cs C:\CodeSample> example.exe Hello World from class B
對於Java,改變使用的main不須要進行從新編譯,而C#應用程序須要從新編譯。可是,從另外一方面來講,Java又不支持條件編譯,main方法可能就會帶入發行版本中。
二、繼承語法
C#對於繼承使用了C++的語法,都使用冒號來實現繼承和接口實現,在Java中則是extends和implements關鍵字。
C# Code using System; class B:A, IComparable{ int CompareTo(){} public static void Main(String[] args){ Console.WriteLine("Hello World"); } } Java Code class B extends A implements Comparable{ int compareTo(){} public static void main(String[] args){ System.out.println("Hello World"); } }
C#的這種語法更符合從C++轉來開發者的習慣,而Java的語法則能夠經過類的聲明直接知道類是子類呢仍是隻是實現了接口,C#則沒法區分。可是咱們知道根據.NET的命名約定,接口須要以大寫的字母I打頭,好比ICloneable,這樣就不會有這個問題了。
三、運行時類型標識(is操做符)
C#的js操做符和Java的instanceof操做符同樣。以下代碼等價:
C# Code if(x is MyClass) MyClass mc = (MyClass) x; Java Code if(x instanceof MyClass) MyClass mc = (MyClass) x;
四、命名空間
C#命名空間是對類進行分組的方式,和Java的包構造差很少。使用C++的人會發現C#命名空間和C++的差很少,在Java中,包名字代筆應用程序中源文件目錄結構,而C#的命名空間則不會要求源文件的物理層次和邏輯結構有關聯:
C# Code namespace com.carnage4life{ public class MyClass { int x; void doStuff(){} } } Java Code package com.carnage4life; public class MyClass { int x; void doStuff(){} }
C#的命名空間語法容許進行命名空間的嵌套,以下:
C# Code using System; namespace Company{ public class MyClass { /* Company.MyClass */ int x; void doStuff(){} } namespace Carnage4life{ public class MyOtherClass { /* Company.Carnage4life.MyOtherClass */ int y; void doOtherStuff(){} public static void Main(string[] args){ Console.WriteLine("Hey, I can nest namespaces"); } }// class MyOtherClass }// namespace Carnage4life }// namespace Company
五、構造方法、析構方法以及終結器
C#中的構造方法的語法和語義和Java同樣。C#還有析構方法的概念,這和C++的析構器語法比較類似,和Java的終結器語義一致。儘管存在析構方法這樣的語法,可是仍是不推薦使用,有許多緣由。首先咱們沒有辦法控制它運行的時間,若是在析構方法裏面還持有其它引用的話狀況更復雜,其次還會帶來性能問題,由於垃圾回收線程不能直接回收帶有析構方法的對象,必須等到終結器線程執行以後才能夠回收,這樣具備析構方法的對象可能比沒有析構方法的對象存在的時間更長。以下是C#和Java的例子:
C# Code using System; public class MyClass { static int num_created = 0; int i = 0; MyClass(){ i = ++num_created; Console.WriteLine("Created object #" + i); } ~MyClass(){ Console.WriteLine("Object #" + i + " is being finalized"); } public static void Main(string[] args){ for(int i=0; i < 10000; i++) new MyClass(); } } Java Code public class MyClass { static int num_created = 0; int i = 0; MyClass(){ i = ++num_created; System.out.println("Created object #" + i); } public void finalize(){ System.out.println("Object #" + i + " is being finalized"); } public static void main(String[] args){ for(int i=0; i < 10000; i++) new MyClass(); } }
注意:在C#中析構方法(終結器)會在執行後自動調用基類的終結器,Java中不會。
六、同步方法和代碼塊
在Java中能夠指定同步塊來確保在同一時刻只有一個線程訪問某個對象,C#提供了lock語句對應Java的synchronized語句。
C# Code public void WithdrawAmount(int num){ lock(this){ if(num < this.amount) this.amount -= num; } } Java Code public void withdrawAmount(int num){ synchronized(this){ if(num < this.amount) this.amount -= num; } }
C#和Java都有同步方法的概念。在調用同步方法的時候,調用方法的線程會鎖定包含方法的對象,所以其它線程調用相同對象的同步方法必須等到其它線程執行完方法釋放鎖以後才能執行。同步方法在Java中用synchronized關鍵字來標記,而在C#中使用[MethodImpl(MethodImplOptions.Synchronized)]特性來修飾。同步方法的例子以下:
C# Code using System; using System.Runtime.CompilerServices; public class BankAccount{ [MethodImpl(MethodImplOptions.Synchronized)] public void WithdrawAmount(int num){ if(num < this.amount) this.amount - num; } }//BankAccount Java Code public class BankAccount{ public synchronized void withdrawAmount(int num){ if(num < this.amount) this.amount - num; } }//BankAccount
七、訪問修飾符
以下表格對照C#和Java的訪問修飾符。C++愛好者比較失望,在Java2中Sun改變了protected關鍵字的語義,而C#的protected關鍵字和C++的同樣。也就是說,protected成員只有在類的成員方法或派生類的成員方法中能夠訪問。internal修飾符表示成員能夠被類相同程序集的其它類訪問。internal protected修飾符表示的是internal或protected。
注意:在沒有指定訪問修飾符的狀況下,C#字段或方法的默認訪問級別是private,而Java則是protected。
八、反射
在C#和Java中發現類中方法和字段以及運行時調用類方法的能力通常叫作反射。Java和C#中反射的主要區別是,C#的反射是程序集級別的,而Java是類級別的。因爲程序集通常保存爲DLL,對於C#須要包含類的DLL,而Java須要能夠加載類文件或目標類。以下例子遍歷某個類的方法,以此比較C#和Java在反射上的差別:
C# Code using System; using System.Xml; using System.Reflection; using System.IO; class ReflectionSample { public static void Main( string[] args){ Assembly assembly=null; Type type=null; XmlDocument doc=null; try{ // 加載程序集以得到類型 assembly = Assembly.LoadFrom("C:\\WINNT\\Microsoft.NET\\Framework\\v1.0.2914\\System.XML.dll"); type = assembly.GetType("System.Xml.XmlDocument", true); //Unfortunately one cannot dynamically instantiate types via the Type object in C#. doc = Activator.CreateInstance("System.Xml","System.Xml.XmlDocument").Unwrap() as XmlDocument; if(doc != null) Console.WriteLine(doc.GetType() + " was created at runtime"); else Console.WriteLine("Could not dynamically create object at runtime"); }catch(FileNotFoundException){ Console.WriteLine("Could not load Assembly: system.xml.dll"); return; }catch(TypeLoadException){ Console.WriteLine("Could not load Type: System.Xml.XmlDocument from assembly: system.xml.dll"); return; }catch(MissingMethodException){ Console.WriteLine("Cannot find default constructor of " + type); }catch(MemberAccessException){ Console.WriteLine("Could not create new XmlDocument instance"); } // 得到方法 MethodInfo[] methods = type.GetMethods(); //打印方法簽名和參數 for(int i=0; i < methods.Length; i++){ Console.WriteLine ("{0}", methods[i]); ParameterInfo[] parameters = methods[i].GetParameters(); for(int j=0; j < parameters.Length; j++){ Console.WriteLine (" Parameter: {0} {1}", parameters[j].ParameterType, parameters[j].Name); } }//for (int i...) } }
上面的代碼能夠看到C#的反射API略微優雅一點,C#提供了ParameterInfo包含方法的參數元數據,而Java提供的只是Class對象丟失了諸如參數名等信息。
有的時候須要獲取指定類元數據對象,那麼可使用Java的java.lang.Class或C#的System.Type對象。要從類的實例獲取元數據,在Java中可使用getClass()方法而在C#中可使用GetType()方法。若是要根據名字而不是建立一個類的實例來獲取元數據能夠這麼作:
C# Code Type t = typeof(ArrayList); Java Code Class c = java.util.Arraylist.class; /* 必須在類的完整名字後跟 .class */
九、聲明常量
在Java中要聲明常量可使用final關鍵字。final的變量能夠在編譯時或運行時進行設置。在Java中,若是在基元上使用final的話基元的值不可變,若是在對象引用上使用final的話,則引用只能夠指向一個對象。final的成員能夠在聲明的時候不初始化,可是必需要構造方法中初始化。
在C#中要聲明常量使用const關鍵字來表示編譯時常量,使用readonly關鍵字來表示運行時常量。基元常量和引用常量的語義對於C#和Java來講是同樣的。
和C++不一樣的是,C#和Java不能經過語言結構來指定不可變的類。
C# Code using System; public class ConstantTest{ /* 編譯時常量*/ const int i1 = 10; //隱含表示這是static的變量 // 下面的代碼不能經過編譯 // public static const int i2 = 20; /* 運行時常量 */ public static readonly uint l1 = (uint) DateTime.Now.Ticks; /* 對象引用做爲常量 */ readonly Object o = new Object(); /* 未初始化的只讀變量 */ readonly float f; ConstantTest() { // 未初始化的只讀變量必須在構造方法中初始化 f = 17.21f; } } Java Code import java.util.*; public class ConstantTest{ /* Compile time constants */ final int i1 = 10; //instance variable static final int i2 = 20; //class variable /* run time constants */ public static final long l1 = new Date().getTime(); /* object reference as constant */ final Vector v = new Vector(); /* uninitialized final */ final float f; ConstantTest() { // unitialized final variable must be initialized in constructor f = 17.21f; } }
注意:Java語言還支持方法上的final參數。在C#中沒有這個功能。final參數只要用於容許傳入方法的參數可讓方法內的內部類進行訪問。
十、基元類型
對於Java中的每個基元類型在C#中都有同名的對應(除了byte以外)。Java中的byte是有符號的,等價於C#中的sbyte,不一樣於C#的byte。C#還提供了一些基元類型的無符號版本,好比ulong、uint、ushort和byte。C#中最不一樣的基元類型是decimal類型,不會有舍入錯誤,固然也就須要更多空間也更慢。
C# Code decimal dec = 100.44m; //m is the suffix used to specify decimal numbers double dbl = 1.44e2d; //e is used to specify exponential notation while d is the suffix used for doubles
十一、數組聲明
Java有兩種方式聲明數組。一種方式爲了兼容C/C++的寫法,另一種更具備可讀性,C#只能使用後者。
C# Code int[] iArray = new int[100]; //valid, iArray is an object of type int[] float fArray[] = new float[100]; //ERROR: Won't compile Java Code int[] iArray = new int[100]; //valid, iArray is an object of type int[] float fArray[] = new float[100]; //valid, but isn't clear that fArray is an object of type float[]
十二、調用基類構造方法和構造方法鏈
C#和Java自動調用基類構造方法,而且提供了一種方式能夠調用基類的有參構造方法。兩種語言都要求派生類的構造方法在任何初始化以前先調用基類的構造方法防止使用沒有初始化的成員。兩種語言還提供了在一個構造方法中調用另外一個構造方法的方式以減小構造方法中代碼的重複。這種方式叫作構造方法鏈:
C# Code using System; class MyException: Exception { private int Id; public MyException(string message): this(message, null, 100){ } public MyException(string message, Exception innerException): this(message, innerException, 100){ } public MyException(string message, Exception innerException, int id): base(message, innerException){ this.Id = id; } } Java Code class MyException extends Exception{ private int Id; public MyException(String message){ this(message, null, 100); } public MyException(String message, Exception innerException){ this(message, innerException, 100); } public MyException( String message,Exception innerException, int id){ super(message, innerException); this.Id = id; } }
1三、可變長度參數列表
在C和C++中能夠指定函數接收一組參數。在printf和scanf相似的函數中大量使用這種特性。C#和Java容許咱們定義一個參數接收可變數量的參數。在C#中能夠在方法的最後一個參數上使用params關鍵字以及一個數組參數來實現,在Java中能夠爲類型名經過增長三個.來實現。
C# Code using System; class ParamsTest{ public static void PrintInts(string title, params int[] args){ Console.WriteLine(title + ":"); foreach(int num in args) Console.WriteLine(num); } public static void Main(string[] args){ PrintInts("First Ten Numbers in Fibonacci Sequence", 0, 1, 1, 2, 3, 5, 8, 13, 21, 34); } } Java Code class Test{ public static void PrintInts(String title, Integer... args){ System.out.println(title + ":"); for(int num : args) System.out.println(num); } public static void main(String[] args){ PrintInts("First Ten Numbers in Fibonacci Sequence", 0, 1, 1, 2, 3, 5, 8, 13, 21, 34); } }
1四、泛型
C#和Java都提供了建立強類型數據結構且不須要在編譯時知道具體類型的機制。在泛型機制出現之前,這個特性經過在數據結構中指定object類型而且在運行時轉換成具體類型來實現。可是這種技術有許多缺點,包括缺少類型安全、性能不佳以及代碼膨脹。
以下代碼演示了兩種方式:
# Code using System; using System.Collections; using System.Collections.Generic; class Test{ public static Stack GetStackB4Generics(){ Stack s = new Stack(); s.Push(2); s.Push(4); s.Push(5); return s; } public static Stack<int> GetStackAfterGenerics(){ Stack<int> s = new Stack<int>(); s.Push(12); s.Push(14); s.Push(50); return s; } public static void Main(String[] args){ Stack s1 = GetStackB4Generics(); int sum1 = 0; while(s1.Count != 0){ sum1 += (int) s1.Pop(); //cast } Console.WriteLine("Sum of stack 1 is " + sum1); Stack<int> s2 = GetStackAfterGenerics(); int sum2 = 0; while(s2.Count != 0){ sum2 += s2.Pop(); //no cast } Console.WriteLine("Sum of stack 2 is " + sum2); } }
Java Code import java.util.*; class Test{ public static Stack GetStackB4Generics(){ Stack s = new Stack(); s.push(2); s.push(4); s.push(5); return s; } public static Stack<Integer> GetStackAfterGenerics(){ Stack<Integer> s = new Stack<Integer>(); s.push(12); s.push(14); s.push(50); return s; } public static void main(String[] args){ Stack s1 = GetStackB4Generics(); int sum1 = 0; while(!s1.empty()){ sum1 += (Integer) s1.pop(); //cast } System.out.println("Sum of stack 1 is " + sum1); Stack<Integer> s2 = GetStackAfterGenerics(); int sum2 = 0; while(!s2.empty()){ sum2 += s2.pop(); //no cast } System.out.println("Sum of stack 2 is " + sum2); } }
儘管和C++中的模板概念類似,C#和Java中的泛型特性實現上不同。在Java中,泛型功能使用類型擦除來實現。泛型信息只是在編譯的時候出現,編譯後被編譯器擦除全部類型聲明替換爲Object。編譯器自動在覈實的地方插入轉換語句。這麼作的緣由是,泛型代碼和不支持泛型的遺留代碼能夠互操做。類型擦除的泛型類型的主要問題是,泛型類型信息在運行時經過反射或運行時類型標識不可用。而且對於這種方式,泛型類型數據結構必須使用對象和非基元類型進行生命。因此只能有Stack<Integer>而不是Stack<int>。
在C#中,.NET運行時中間語言IL直接支持泛型。泛型在編譯的時候,生成的IL包含具體類型的佔位符。在運行的時候,若是初始化一個泛型類型的引用,系統會看是否有人已經用過這個類型了,若是類型以前請求過則返回以前生成的具體類型,若是沒有JIT編譯器把泛型參數替換爲IL中的具體類型而後再初始化新的類型。若是請求的類型是引用類型而不是值類型的話,泛型類型參數會替換爲Object,可是不須要進行轉換,由於.NET運行時會在訪問的時候內部進行轉換。
在某些時候,咱們的方法須要操做包含任意類型的數據結構而不是某個具體類型,可是又但願利用強類型泛型的優點。這個時候咱們能夠經過C#的泛型類型推斷或Java通配類型實現。以下代碼所示:
C# Code using System; using System.Collections; using System.Collections.Generic; class Test{ //Prints the contents of any generic Stack by //using generic type inference public static void PrintStackContents<T>(Stack<T> s){ while(s.Count != 0){ Console.WriteLine(s.Pop()); } } public static void Main(String[] args){ Stack<int> s2 = new Stack<int>(); s2.Push(4); s2.Push(5); s2.Push(6); PrintStackContents(s2); Stack<string> s1 = new Stack<string>(); s1.Push("One"); s1.Push("Two"); s1.Push("Three"); PrintStackContents(s1); } } Java Code import java.util.*; class Test{ //Prints the contents of any generic Stack by //specifying wildcard type public static void PrintStackContents(Stack<?> s){ while(!s.empty()){ System.out.println(s.pop()); } } public static void main(String[] args){ Stack <Integer> s2 = new Stack <Integer>(); s2.push(4); s2.push(5); s2.push(6); PrintStackContents(s2); Stack<String> s1 = new Stack<String>(); s1.push("One"); s1.push("Two"); s1.push("Three"); PrintStackContents(s1); } }
C#和Java都提供了泛型約束。對於C#有三種類型的約束:
1)派生約束,告訴編譯器泛型類型參數須要從某個基類型繼承,好比接口或基類
2)默認構造方法約束,告訴編譯器泛型類型參數須要提供公共的默認構造方法
3)引用、值類型約束,泛型類型參數須要是引用類型或值類型
對於Java支持派生約束:
C# Code using System; using System.Collections; using System.Collections.Generic; public class Mammal { public Mammal(){;} public virtual void Speak(){;} } public class Cat : Mammal{ public Cat(){;} public override void Speak(){ Console.WriteLine("Meow"); } } public class Dog : Mammal{ public Dog(){;} public override void Speak(){ Console.WriteLine("Woof"); } } public class MammalHelper<T> where T: Mammal /* derivation constraint */, new() /* default constructor constraint */{ public static T CreatePet(){ return new T(); } public static void AnnoyNeighbors(Stack<T> pets){ while(pets.Count != 0){ Mammal m = pets.Pop(); m.Speak(); } } } public class Test{ public static void Main(String[] args){ Stack<Mammal> s2 = new Stack<Mammal>(); s2.Push(MammalHelper<Dog>.CreatePet()); s2.Push(MammalHelper<Cat>.CreatePet()); MammalHelper<Mammal>.AnnoyNeighbors(s2); } }
Java Code import java.util.*; abstract class Mammal { public abstract void speak(); } class Cat extends Mammal{ public void speak(){ System.out.println("Meow"); } } class Dog extends Mammal{ public void speak(){ System.out.println("Woof"); } } public class Test{ //derivation constraint applied to pets parameter public static void AnnoyNeighbors(Stack<? extends Mammal> pets){ while(!pets.empty()){ Mammal m = pets.pop(); m.speak(); } } public static void main(String[] args){ Stack<Mammal> s2 = new Stack<Mammal>(); s2.push(new Dog()); s2.push(new Cat()); AnnoyNeighbors(s2); } }
C#還提供了default操做符能夠返回類型的默認值。引用類型的默認值是null,值類型(好比int、枚舉和結構)的默認值是0(0填充結構)。對於泛型default頗有用,以下代碼演示了這個操做符的做用:
C# Code using System; public class Test{ public static T GetDefaultForType<T>(){ return default(T); //return default value of type T } public static void Main(String[] args){ Console.WriteLine(GetDefaultForType<int>()); Console.WriteLine(GetDefaultForType<string>()); Console.WriteLine(GetDefaultForType<float>()); } }
輸出:
0
0
1五、for-each循環
for-each循環是不少腳本語言、編譯工具、方法類庫中很是常見的一種迭代結構。for-each循環簡化了C#中實現System.Collections.IEnumerable接口或Java中java.lang.Iterable接口數組或類的迭代。
在C#中經過foreach關鍵字來建立for-each循環,而在Java中經過操做符:來實現。
C# Code string[] greek_alphabet = {"alpha", "beta", "gamma", "delta", "epsilon"}; foreach(string str in greek_alphabet) Console.WriteLine(str + " is a letter of the greek alphabet"); Java Code String[] greek_alphabet = {"alpha", "beta", "gamma", "delta", "epsilon"}; for(String str : greek_alphabet) System.out.println(str + " is a letter of the greek alphabet");
1六、元數據註解
元數據註解提供了一種強大的方式來擴展編程語言和語言運行時的能力。註解是能夠要求運行時進行一些額外任務、提供類額外信息擴展功能的一些指令。元數據註解在許多編程環境中很常見,好比微軟的COM和linux內核。
C#特性提供了爲模塊、類型、方法或成員變量增長註解的方式。以下描述了一些.NET自帶的特性以及如何使用它們來擴展C#的能力:
1)[MethodImpl(MethodImplOptions.Synchronized)] 用於指定多線程訪問方法的時候使用鎖進行保護,和Java的sychronized同樣。
2)[Serializable]用於把類標記爲可序列化的,和Java的實現Serializable接口類似。
3)[FlagsAttribute]用於指定枚舉支持位操做。這樣枚舉就能夠有多個值。
C# Code //declaration of bit field enumeration [Flags] enum ProgrammingLanguages{ C = 1, Lisp = 2, Basic = 4, All = C | Lisp | Basic } aProgrammer.KnownLanguages = ProgrammingLanguages.Lisp; //set known languages ="Lisp" aProgrammer.KnownLanguages |= ProgrammingLanguages.C; //set known languages ="Lisp C" aProgrammer.KnownLanguages &= ~ProgrammingLanguages.Lisp; //set known languages ="C" if((aProgrammer.KnownLanguages & ProgrammingLanguages.C) > 0){ //if programmer knows C //.. do something }
4)[WebMethod]在ASP.NET中用於指定方法能夠經過Web服務訪問。
能夠經過反射來訪問模塊、類、方法或字段的特性(詳見http://msdn.microsoft.com/zh-cn/library/system.attributetargets.aspx)。對於運行時獲取類是否支持某個行爲特別有用,開發者能夠經過繼承System.Attribute類來建立他們本身的特性。以下是使用特性來提供類做者信息的例子,而後咱們經過反射來讀取這個信息。
C# Code using System; using System.Reflection; [AttributeUsage(AttributeTargets.Class)] public class AuthorInfoAttribute: System.Attribute{ string author; string email; string version; public AuthorInfoAttribute(string author, string email){ this.author = author; this.email = email; } public string Version{ get{ return version; } set{ version = value; } } public string Email{ get{ return email; } } public string Author{ get{ return author; } } } [AuthorInfo("Dare Obasanjo", "kpako@yahoo.com", Version="1.0")] class HelloWorld{ } class AttributeTest{ public static void Main(string[] args){ /* Get Type object of HelloWorld class */ Type t = typeof(HelloWorld); Console.WriteLine("Author Information for " + t); Console.WriteLine("================================="); foreach(AuthorInfoAttribute att in t.GetCustomAttributes(typeof(AuthorInfoAttribute), false)){ Console.WriteLine("Author: " + att.Author); Console.WriteLine("Email: " + att.Email); Console.WriteLine("Version: " + att.Version); }//foreach }//Main }
Java的提供了爲包、類型、方法、參數、成員或局部變量增長註解的能力。Java語言只內置了三種註解,以下:
1)@Override用於指定方法覆蓋基類的方法。若是註解的方法並無覆蓋基類的方法,在編譯的時候會出錯。
2)@Deprecated用於指示某個方法已經廢棄。使用廢棄的方法會在編譯的時候產生警告。
3)@SuppressWarnings用於防止編譯器發出某個警告。這個註解可選接收參數來代表屏蔽哪一種警告。
和C#同樣,能夠經過反射來讀取模塊、類、方法或字段的註解。可是不一樣的是Java註解能夠是元註解。開發者能夠建立自定義的註解,建立方法和接口類似,只不過須要定義@interface關鍵字。以下是使用註解和經過反射讀取信息的例子:
Java Code import java.lang.annotation.*; import java.lang.reflect.*; @Documented //we want the annotation to show up in the Javadocs @Retention(RetentionPolicy.RUNTIME) //we want annotation metadata to be exposed at runtime @interface AuthorInfo{ String author(); String email(); String version() default "1.0"; } @AuthorInfo(author="Dare Obasanjo", email="kpako@yahoo.com") class HelloWorld{ } public class Test{ public static void main(String[] args) throws Exception{ /* Get Class object of HelloWorld class */ Class c = Class.forName("HelloWorld"); AuthorInfo a = (AuthorInfo) c.getAnnotation(AuthorInfo.class); System.out.println("Author Information for " + c); System.out.println("======================================="); System.out.println("Author: " + a.author()); System.out.println("Email: " + a.email()); System.out.println("Version: " + a.version()); } }
1七、枚舉
枚舉用於建立一組用於自定義的命名常量。儘管從表面上看C#和Java的枚舉很是相同,可是在實現上它們徹底不一樣。Java的枚舉類型是純種的類,也就是它們是類型安全並能夠增長方法、字段或甚至實現接口進行擴展,而在C#中枚舉純粹是一個包裝了數字類型(通常是int)的語法糖,而且不是類型安全的。以下代碼演示了二者的區別:
C# Code using System; public enum DaysOfWeek{ SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY } public class Test{ public static bool isWeekDay(DaysOfWeek day){ return !isWeekEnd(day); } public static bool isWeekEnd(DaysOfWeek day){ return (day == DaysOfWeek.SUNDAY || day == DaysOfWeek.SATURDAY); } public static void Main(String[] args){ DaysOfWeek sun = DaysOfWeek.SUNDAY; Console.WriteLine("Is " + sun + " a weekend? " + isWeekEnd(sun)); Console.WriteLine("Is " + sun + " a week day? " + isWeekDay(sun)); /* Example of how C# enums are not type safe */ sun = (DaysOfWeek) 1999; Console.WriteLine(sun); } }
Java Code enum DaysOfWeek{ SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY; public boolean isWeekDay(){ return !isWeekEnd(); } public boolean isWeekEnd(){ return (this == SUNDAY || this == SATURDAY); } } public class Test{ public static void main(String[] args) throws Exception{ DaysOfWeek sun = DaysOfWeek.SUNDAY; System.out.println("Is " + sun + " a weekend? " + sun.isWeekEnd()); System.out.println("Is " + sun + " a week day? " + sun.isWeekDay()); } }
運行結果:
Is SUNDAY a weekend? true
Is SUNDAY a week day? false
第三部分:C#中有,Java中也有但徹底不一樣的地方
一、嵌套類
在Java和C#中能夠在一個類中嵌套另外一個。在Java中有兩種類型的嵌套類,非靜態的嵌套類也就是內部類,以及靜態的嵌套類。Java的內部類能夠認爲是內部類和其包含類一對一的關係,每個包含類的實例都保存了一個相應內部類的實例,內部類能夠額訪問包含類的成員變量以及包含非靜態的方法。Java的靜態內部類能夠訪問包含類的靜態成員和方法。C#也有Java的靜態嵌套類,可是沒有Java的內部類。以下嵌套類聲明是等價的:
C# Code public class Car{ private Engine engine; private class Engine{ string make; } } Java Code public class Car{ private Engine engine; private static class Engine{ String make; } }
注意:在Java中,嵌套類能夠在任何代碼塊中聲明,包括方法,在C#中則不能。在方法中建立嵌套類看上去沒必要要,可是結合匿名內部類能夠提供強大的設計模式。
二、線程和易失成員
線程是程序內的順序控制流程。程序或進程能夠有多個線程並行執行,在執行任務的時候能夠共享數據也能夠獨立運行。這樣開發人員就能夠在一個程序或進程中一次執行多個任務。線程的優勢包括充分利用多處理器架構的資源、經過一邊處理任務一邊等待阻塞的系統調用(好比打印機或其它IO)減小執行時間,避免GUI應用程序失去響應。
Java線程能夠經過繼承java.lang.Thread並重寫run()方法或者實現java.lang.Runable接口並實現run()方法來實現。在C#中,能夠建立System.Threading.Thread對象而且傳入System.Threading.Thread委託來表示須要線程運行的方法。在Java中,每個類都從java.lang.Object繼承wait()、notify()以及notify()。在C#中的等價是Thread.Threading.Montir類的Wait()、Pulse()以及PulseAll()。
using System; using System.Threading; using System.Collections; public class WorkerThread { private int idNumber; private static int num_threads_made = 1; private ThreadSample owner; public WorkerThread(ThreadSample owner) { idNumber = num_threads_made; num_threads_made++; this.owner = owner; }/* WorkerThread() */ //sleeps for a random amount of time to simulate working on a task public void PerformTask() { Random r = new Random((int)DateTime.Now.Ticks); int timeout = (int)r.Next() % 1000; if (timeout < 0) timeout *= -1; //Console.WriteLine(idNumber + ":A"); try { Thread.Sleep(timeout); } catch (ThreadInterruptedException e) { Console.WriteLine("Thread #" + idNumber + " interrupted"); } //Console.WriteLine(idNumber + ":B"); owner.workCompleted(this); }/* performTask() */ public int getIDNumber() { return idNumber; } } // WorkerThread public class ThreadSample { private static Mutex m = new Mutex(); private ArrayList threadOrderList = new ArrayList(); private int NextInLine() { return (int)threadOrderList[0]; } private void RemoveNextInLine() { threadOrderList.RemoveAt(0); //all threads have shown up if (threadOrderList.Count == 0) Environment.Exit(0); } public void workCompleted(WorkerThread worker) { try { lock (this) { while (worker.getIDNumber() != NextInLine()) { try { //wait for some other thread to finish working Console.WriteLine("Thread #" + worker.getIDNumber() + " is waiting for Thread #" + NextInLine() + " to show up."); Monitor.Wait(this, Timeout.Infinite); } catch (ThreadInterruptedException e) { } }//while Console.WriteLine("Thread #" + worker.getIDNumber() + " is home free"); //remove this ID number from the list of threads yet to be seen RemoveNextInLine(); //tell the other threads to resume Monitor.PulseAll(this); } } catch (SynchronizationLockException) { Console.WriteLine("SynchronizationLockException occurred"); } } public static void Main(String[] args) { ThreadSample ts = new ThreadSample(); /* Launch 25 threads */ for (int i = 1; i <= 25; i++) { WorkerThread wt = new WorkerThread(ts); ts.threadOrderList.Add(i); Thread t = new Thread(new ThreadStart(wt.PerformTask)); t.Start(); } Thread.Sleep(3600000); //wait for it all to end }/* main(String[]) */ }
Java Code import java.util.*; class WorkerThread extends Thread{ private Integer idNumber; private static int num_threads_made = 1; private ThreadSample owner; public WorkerThread(ThreadSample owner){ super("Thread #" + num_threads_made); idNumber = new Integer(num_threads_made); num_threads_made++; this.owner = owner; start(); //calls run and starts the thread. }/* WorkerThread() */ //sleeps for a random amount of time to simulate working on a task public void run(){ Random r = new Random(System.currentTimeMillis()); int timeout = r.nextInt() % 1000; if(timeout < 0) timeout *= -1 ; try{ Thread.sleep(timeout); } catch (InterruptedException e){ System.out.println("Thread #" + idNumber + " interrupted"); } owner.workCompleted(this); }/* run() */ public Integer getIDNumber() {return idNumber;} } // WorkerThread public class ThreadSample{ private Vector threadOrderList = new Vector(); private Integer nextInLine(){ return (Integer) threadOrderList.firstElement(); } private void removeNextInLine(){ threadOrderList.removeElementAt(0); //all threads have shown up if(threadOrderList.isEmpty()) System.exit(0); } public synchronized void workCompleted(WorkerThread worker){ while(worker.getIDNumber().equals(nextInLine())==false){ try { //wait for some other thread to finish working System.out.println (Thread.currentThread().getName() + " is waiting for Thread #" + nextInLine() + " to show up."); wait(); } catch (InterruptedException e) {} }//while System.out.println("Thread #" + worker.getIDNumber() + " is home free"); //remove this ID number from the list of threads yet to be seen removeNextInLine(); //tell the other threads to resume notifyAll(); } public static void main(String[] args) throws InterruptedException{ ThreadSample ts = new ThreadSample(); /* Launch 25 threads */ for(int i=1; i <= 25; i++){ new WorkerThread(ts); ts.threadOrderList.add(new Integer(i)); } Thread.sleep(3600000); //wait for it all to end }/* main(String[]) */ }//ThreadSample
在許多狀況下,咱們不能確保代碼執行的順序和源代碼一致。產生這種狀況的緣由包括編譯器優化的時候重排語句順序、或多處理器系統不能在全局內存中保存變量。要避免這個問題,C#和Javay引入了volatile關鍵字來告訴語言運行時不要從新調整字段的指令順序。
C# Code /* Used to lazily instantiate a singleton class */ /* WORKS AS EXPECTED */ class Foo { private volatile Helper helper = null; public Helper getHelper() { if (helper == null) { lock(this) { if (helper == null) helper = new Helper(); } } return helper; } } Java Code /* Used to lazily instantiate a singleton class */ /* BROKEN UNDER CURRENT SEMANTICS FOR VOLATILE */ class Foo { private volatile Helper helper = null; public Helper getHelper() { if (helper == null) { synchronized(this) { if (helper == null) helper = new Helper(); } } return helper; } }
儘管上面的代碼除了lock和synchronized關鍵字以外沒什麼不一樣,Java的版本不保證在全部的JVM下都工做,詳見http://www.javaworld.com/javaworld/jw-02-2001/jw-0209-double.html。在C#中volatile的語義不會出現這樣的問題,由於讀寫次序不能調整,一樣被標記volatile的字段不會保存在寄存器中,對於多處理器系統確保變量保存在全局內存中。
三、操做符重載
操做符重載容許特定的類或類型對於標準操做符具備新的語義。操做符重載能夠用於簡化某個經常使用操做的語法,好比Java中的字符串鏈接。操做符重載也是開發人員爭議的一個地方,它在帶來靈活性的同時也帶來了濫用的危險。有些開發者會亂用重載(好比用++或--來表示鏈接或斷開網絡)或是讓操做符不具備原本的意義(好比[]返回一個集合中某個索引項的複製而不是原始的對象)或重載了一半操做符(好比重載<可是不重載>)。
注意,和C++不用,C#不容許重載以下的操做符:new、()、||、&&、=或各類組合賦值,好比+=、-=。可是,重載的組合賦值會調用重載的操做符,好比+=會調用重載的+。
C# Code using System; class OverloadedNumber{ private int value; public OverloadedNumber(int value){ this.value = value; } public override string ToString(){ return value.ToString(); } public static OverloadedNumber operator -(OverloadedNumber number){ return new OverloadedNumber(-number.value); } public static OverloadedNumber operator +(OverloadedNumber number1, OverloadedNumber number2){ return new OverloadedNumber(number1.value + number2.value); } public static OverloadedNumber operator ++(OverloadedNumber number){ return new OverloadedNumber(number.value + 1); } } public class OperatorOverloadingTest { public static void Main(string[] args){ OverloadedNumber number1 = new OverloadedNumber(12); OverloadedNumber number2 = new OverloadedNumber(125); Console.WriteLine("Increment: {0}", ++number1); Console.WriteLine("Addition: {0}", number1 + number2); } } // OperatorOverloadingTest
四、switch語句
C#的switch和Java的switch有兩個主要的區別。在C#中,switch語句支持字符串常量,除非標籤不包含任何語句不然不容許貫穿。貫穿是顯式禁止的,由於可能致使難以找到的bug。
C# Code switch(foo){ case "A": Console.WriteLine("A seen"); break; case "B": case "C": Console.WriteLine("B or C seen"); break; /* ERROR: Won't compile due to fall-through at case "D" */ case "D": Console.WriteLine("D seen"); case "E": Console.WriteLine("E seen"); break; }
五、程序集
C#程序集和Java的JAR文件有不少共性。程序集是.NET環境最基本的代碼打包單元。程序集包含了中間語言代碼、類的元數據以及其它執行任務所須要的數據。因爲程序集是最基本的打包單元,和類型相關的一些行爲必須在程序集級別進行。例如,安全受權、代碼部署、程序集級別的版本控制。Java JAR文件有着類似的做用,可是實現不同。程序集通常是EXE或DLL而JAR文件是ZIP文件格式的。