該文章從Java的C#程序員的角度概述了Java和C#編程語言之間的差別。這種比較不是百科全書式的,而是強調了一些潛在的麻煩或其餘顯着的基本點。在適當的地方註明了Java SE 5-11主要版本中引入的新功能。html
因爲兩種語言都與它們各自的運行時緊密相關,所以,我還將介紹Java虛擬機(JVM)與.NET Framework以及基本庫類之間的任何相關差別。我沒有介紹用於網絡,序列化,GUI,XML等的庫框架。在這些方面,兩個平臺的功能大體相同,可是實現方式卻大不相同。java
C#7及更高版本 — C#6發佈後,Microsoft將快速發展的語言移至Github,而且顯然再也不費心編寫適當的規範。某些新功能是直接從Java複製的(例如,數字分隔符),可是一般能夠假定C#7+功能在Java中不可用。此頁面的其他部分僅涵蓋版本6以前的C#。git
進一步閱讀
有關如何編譯和運行Java程序的快速入門,請參閱編譯Java代碼。程序員
Oracle的JDK 11文檔包括全部參考資料,包括Java語言和VM規範,Java Platform SE 11 API規範以及Java教程。有關Java語言和庫元素的全面性能分析,請參見Mikhail Vorontsov的Java Performance Tuning Guide。github
最好的印刷介紹是Horstmann的Core Java和Bloch的Effective Java,以及Horstmann的《不耐煩的Core Java》做爲快速概述。請參閱Java書籍以獲取更多建議。Andrei Rinea的教程系列「 .NET Developers的Beginning Java」更詳細地介紹了選定的主題。web
文章內容
1.關鍵字
Java和C#使用很是類似的語法,並提供類似的關鍵字集,它們均源自C / C ++。在這裏,我將簡要列出如下各節中未說起的一些值得注意的區別。算法
assert
等效於C#Debug.Assert
調用。該斷言設備須要一種新的語言關鍵字,由於Java提供沒有其餘辦法的Elid有條件方法調用。有關捕獲斷言失敗的詳細信息,請參見異常。(Java SE 1.4)class
在類型名稱後面typeof
用做「類文字」時,它等效於C#(示例)。final
等價於具備const
編譯時常量的原始或字符串變量上的C#;readonly
其餘領域的C#和C#sealed
的類和方法。Java保留const
但不使用它。instanceof
與C#等效,is
用於檢查對象的運行時類型。沒有等效的C#as
,所以在類型檢查成功後,您必須始終使用顯式強制轉換。native
extern
與聲明外部C函數的C#等效。- C#
string
丟失。您必須始終使用大寫的庫類型String
。 synchronized
等效於C#MethodImplOptions.Synchronized
做爲方法屬性,並等效於方法中lock
代碼塊周圍的C#。transient
等同於C#NonSerializableAttribute
,可免除字段的序列化。var
像C#中同樣聲明隱式類型的局部變量。(Java SE 10,但請參見下文)Object... args
(三個句點)等效於C#params
,而且還從全部列出的參數隱式建立一個數組。(Java SE 5)
SE 10以前的Java版本缺少,var
但提供了lambda表達式(Java SE 8)和泛型類型參數的類型推斷,包括泛型構造函數的菱形符號。不要var
與通用類型推斷結合使用,不然省略的類型變量可能被推斷爲Object
!有關更多信息,請參見Stuart W. Marks的樣式指南。express
缺乏關鍵字
Java徹底缺乏如下C#關鍵字和功能:編程
#if/#define/#pragma
和條件編譯。解決方法包括動態類加載和外部預處理器或其餘構建工具。#region
塊沒有等效項,可是Java IDE固然容許語法摺疊。async/await
用於異步回調和yield break/return
枚舉器。您必須手工編寫所需的狀態機。dynamic
在運行時進行動態鍵入。Java SE 7提供了invokedynamic JVM指令,但未在Java中公開它。event
以及事件的隱式代碼生成。您必須手動編寫整個事件基礎結構,如從C#到Java:事件中所示(但請參見下文)。fixed/sizeof/stackalloc/unsafe
和指針操做。解決方法包括native
方法和特定於平臺的庫。get/set
用於相似字段的屬性。使用具備相應前綴的傳統方法語法(由JavaBeans模式標準化)。operator/explicit/implicit
和運算符重載,包括自定義索引器和轉換運算符。請參閱有關字符串運算符的特殊說明。partial
類和方法。每一個類和非抽象方法都在一個文件中徹底定義。ref/out
和按引用致電。經過引用傳遞方法參數的惟一方法是將它們包裝在另外一個對象中,例如一個元素數組。- 命名和可選方法參數。一樣,您必須使用對象包裝器經過名稱設置參數或提供默認參數值。
- 經過名稱設置成員的對象初始化程序,以及經過索引設置元素的索引初始化程序。您可使用難看的雙括號初始化習慣來模擬它們。
- C#6中的如下任何新功能:空條件(
?.
)和nameof
運算符,表達式函數,異常過濾器(catch…when
)和字符串插值($
)。
做爲編寫本身的事件基礎結構的替代方法,請考慮使用JavaFX包javafx.beans及其子包中定義的「可觀察」對象。此類對象使您能夠附加在值更改時獲得通知的偵聽器-一般是事件的預期用途。(注意:從Java SE 11開始,JavaFX已成爲單獨的下載,如今能夠在此處下載。)api
Java沒有等效於LINQ關鍵字。從Java SE 8開始,lambda表達式和流庫將LINQ的基於方法的化身複製到對象,並完成了惰性和/或並行評估。Java中相似LINQ的查詢的第三方庫包括iciql,Jinq,jOOQ和QueryDSL。
2.原語
Java和C#具備等效的原始類型集(int
等),但如下狀況除外:
- Java僅具備帶符號的數字類型,包括
byte
。缺乏全部未簽名的變體。Java SE 8將未簽名的操做添加爲庫方法。 - C#
decimal
與Java類BigDecimal類似,後者沒有相應的原語。 - 全部Java原語都有等效的庫類。可是,這些不是 C#中的同義詞,而是盒裝版本。這是由於Java 在其類層次結構中不支持值類型。
- 出於一樣的緣由,Java沒有基元的可爲空的變體。只需使用等效的庫類-它們都是引用類型,所以能夠爲空。
- 除了數字值的十進制,十六進制和八進制文字外,Java SE 7還添加了二進制文字和下劃線文字。
請注意,C#和Java中的基本等效類之間的區別。在C#中,int
而且System.Integer
是同義詞:二者都表明未裝箱的原始值類型。您須要進行(Object)
強制轉換以將引用明確地包裝在這些值周圍。
可是在Java中,只有int
一個未裝箱的原始值,而java.lang.Integer表明強類型的裝箱版本!C#程序員必須注意不要互換使用Java原語和相應的類名。有關更多詳細信息,請參見自動裝箱。(Java SE 5)
Java會根據須要在分配,數學運算或方法調用的上下文中自動將原始值從其強類型包裝對象中解包。顯式轉換到原始類型從更通常的類拆箱時只須要(Number
,Object
)。
3.算術
Java缺乏C#checked/unchecked
來切換對算術表達式的溢出檢查。相反,積分溢出會像C / C ++同樣默默地截斷位,而浮點溢出會產生負無窮或正無窮大。浮點0/0產生NaN(不是數字)。只有被零除的整數會拋出ArithmeticException
。Java SE 8 …Exact
向java.lang.Math添加了各類用於算術運算和類型轉換的方法,這些方法老是在溢出時引起異常。
Java提供了strictfp
可應用於類,接口和方法的修飾符。它強制將全部浮點運算的中間結果截斷爲IEEE 754大小,以在各個平臺上實現可重複的結果。庫類java.lang.StrictMath還基於fdlibm定義了一組具備可移植行爲的標準函數。
4.控制流程
Java保留goto
但未定義它。可是,語句標籤確實存在,而且發生了奇怪的變化,break
而且continue
獲得了加強,能夠接受它們。您只能跳出本地塊,但能夠break
在任何塊上操做-不只是循環。這使得Java break
幾乎徹底等同於C#goto
。
Java switch
對(裝箱或未裝箱的)原語和枚舉進行操做,而且因爲Java SE 7也對字符串進行操做。您不須要case
像C#中那樣用枚舉類型限定枚舉值。Java容許落空,從一個case
到下一個,就像C / C ++。使用編譯器選項-Xlint:fallthrough
來警告缺乏的break
語句。沒有等效於C#goto
定位case
標籤的方法。
5.數組
與.NET中同樣,Java數組是帶有自動索引檢查的專用引用類型。與.NET不一樣,Java直接僅支持一個數組維。多維數組是經過嵌套一維數組來模擬的。可是,在初始化過程當中指定全部尺寸時,全部必需的嵌套數組都將隱式分配。例如,new int[3][6]
分配外部數組和六個整數的全部三個嵌套數組,而無需重複new
語句。能夠不指定任何數量的最右邊維,以便之後手動建立一個不規則的數組。
輔助類Arrays提供了許多與數組相關的功能:比較,轉換,複製,填充,哈希碼生成,分區,排序和搜索,以及做爲實例方法Array.toString
沒法實現的人類可讀字符串輸出。Java SE 8添加了幾種parallel…
在可能的狀況下執行多線程操做的方法。
6.琴絃
與C#中同樣,Java字符串是UTF-16 代碼單元的不可變序列,每一個序列適合一個16位char
原語。對於包含任何32位Unicode代碼點(即,現實世界中的字符)的字符串,它們須要兩個 UTF-16代碼單元的替代對,則不能使用普通的char
索引器方法。而是使用各類輔助方法來對代碼點創建索引,Java直接在String類上定義了這些方法。
Java SE 9引入了僅包含ISO-8859-1(Latin-1)字符的字符串的緊湊表示形式。這樣的字符串每一個字符使用8位而不是16位。這是一種自動且純粹是內部的優化,不會影響公共API。
運算符 —與C#不一樣,Java不會==
對字符串的運算符進行特殊區分,所以只會測試引用是否相等。使用String.equals或String.equalsIgnoreCase測試內容是否相等。
Java +
對字符串鏈接運算符進行特殊處理(JLS§15.18.1)。煩人的是,這會執行相似於JavaScript的全部類型的自動轉換爲String
。一旦String
找到一個操做數,就將左側的任何現有中間和與右側的其他全部單個操做數轉換String
並鏈接在一塊兒。與C#中同樣,逐段字符串鏈接也可能效率不高。使用專用的StringBuilder類可得到更好的性能。
7.班級
Java缺乏C#static
類。若要建立僅包含靜態實用程序方法的類,請使用定義私有默認構造函數的老式方法。Java確實具備static
類修飾符,但僅適用於嵌套類且具備很是不一樣的語義。
類對象包含一些簡單但有用的用於任何類型對象的幫助程序方法,包括爲多個對象生成哈希碼以及各類空安全操做。
構造 —構造函數鏈的用法this(…)
與C#和super(…)
基類中的用法相同,但這些調用顯示爲構造函數主體中的第一行,而不是在大括號以前。
Java沒有靜態構造函數,可是提供了靜態和實例變量的匿名初始化程序塊。多個初始化程序塊是能夠接受的,而且將按它們在任何構造函數運行以前出現的順序執行。靜態初始化程序塊在首次加載該類時執行。
銷燬 -Java支持在垃圾回收器銷燬對象以前運行的終結器,可是這些終結器的名稱很合理,finalize
而不是C#誤導性的C ++析構函數語法。終結器的行爲是複雜且有問題的,所以一般應首選try/finally
清理。
Java不只提供能夠隨時收集的弱引用(如C#),還提供僅響應內存壓力而收集的軟引用。
8.繼承
基類稱爲超類,所以用關鍵字super
而不是C#進行引用base
。聲明派生類時,Java extends
(用於超類)和Java (用於implements
接口)指定了相同的繼承關係,C#爲此使用簡單的冒號。
C#virtual
徹底丟失,由於除非明確聲明,不然全部 Java方法都是虛擬的final
。幾乎沒有性能損失,由於JVM Hotspot優化器與至關愚蠢的.NET CLR優化器不一樣,能夠在運行時未檢測到覆蓋時動態內聯虛擬方法。
Java SE 5添加了協變返回類型以支持其類型擦除泛型。協方差是經過編譯器生成的bridge方法實現的,所以請注意子類的版本控制問題。
9.接口
像C#同樣,Java支持單類繼承和多接口繼承。接口名稱沒有I
.NET 前綴,也沒有以任何其餘方式與類名稱區分開。
Java不支持C#擴展方法來將實現從外部附加到接口(或類)上,可是它容許在接口內實現。Java 接口可能包含常量,即public static final
字段。字段值能夠是複雜的表達式,在第一次加載接口時會對其進行評估。
Java SE 8 在接口上添加了靜態方法和默認方法,以及Java SE 9 私有方法。在沒有常規類實現的狀況下使用默認方法。這消除了對抽象默認實現類的需求,而且還容許擴展接口而不會破壞現有客戶端。
Java不支持C#顯式接口實現來從公共視圖中隱藏接口要求的方法。這多是一件好事,由於在涉及多個類時,顯式接口實現的訪問語義衆所周知很容易出錯。
10.嵌套類
使用static
修飾符定義嵌套類,其行爲與C#中的行爲相同。沒有該修飾符的嵌套類是Java的特殊內部類。它們也可能以專用的類名或匿名方式出如今方法內。
內部類 -非靜態嵌套類是內部類,它們對建立它們的外部類實例進行隱式引用,相似於this
實例方法的隱式引用。您還能夠用於關聯特定的外部類實例。內部類能夠訪問其外部類的全部私有字段和方法,能夠選擇使用前綴來消除歧義。嵌套類上的修飾符可防止這種隱式關聯。outerObj.new InnerClass()
OuterClass.this
static
本地類 -內部類可能在方法中顯示爲本地類。除了隱式關聯的外部類實例的全部私有成員以外,局部類還能夠訪問聲明方法中範圍內的全部局部變量,只要它們有效final
。
匿名類 -本地類能夠聲明爲一次性實例,並帶有用於指定超類或接口的初始化器表達式。編譯器在內部生成一個具備隱藏名稱的類,該名稱擴展了超類或實現了接口。這種作法被稱爲匿名類。
在Java SE 8以前,匿名類與lambda表達式等效,儘管它可能包含大多數普通類成員,但它們的功能更爲強大。禁止使用構造函數– 而是使用初始化程序塊。(雙括號初始化習慣用法可能會濫用此功能。)
Java的功能編程版本最終依賴於匿名類。所以,僅定義單個方法的接口稱爲功能接口,而實現它們的匿名類稱爲功能對象。
11. Lambda表達式
Java SE 8添加了lambda表達式做爲功能對象的替代,在語法上更加簡潔,內部實現也更快。語法與C#相同,只是用->
代替而不是=>
做爲功能箭頭。若是不存在,則推斷參數類型。
Java在java.util.function
其餘地方預約義了基本的功能接口。不幸的是,因爲Java的類型擦除泛型以及缺乏值類型,所以預約義類型比.NET的委託庫醜陋且不全面。埃德溫·達洛佐(Edwin Dalorzo)解釋了細節,並警告與已檢查異常的可能衝突。
因爲lambda表達式在語義上等效於匿名類,所以它們隱式地鍵入爲調用者須要的任何接口,例如Comparator <T>。與C#delegate
類型同樣,您可使用函數接口類型將lambda表達式存儲在變量中。此外,lambda表達式能夠訪問外部做用域中任何有效的局部變量,這些局部變量實際上final
使人驚訝地包括for-each循環變量。
方法參考 —除了在須要函數對象的地方定義lambda表達式以外,您還能夠提供對任何現有靜態方法或實例方法的方法參考。使用雙冒號(::
)將類名或實例名與方法名分開。此語法還能夠將超類方法引用爲,將構造函數引用爲。經過將類型化的數組構造函數傳遞給通用方法,能夠建立任何所需類型的數組。super::method
ClassName::new
int[]::new
儘管很是方便,但對實例方法的方法引用與等效的lambda表達式的求值方法有所不一樣,這可能致使使人驚訝的行爲。有關示例,請參見Java方法參考評估。
12.枚舉
Java SE 5引入了類型安全的枚舉,以代替鬆散的整數常量。與C#enum
類型不一樣,Java枚舉是成熟的引用類型。每一個枚舉常量表明一個該類型的命名實例。用戶不能建立除枚舉實例以外的任何新實例,以確保聲明的常量列表是最終的。這種獨特的實現有兩個重要的後果:
- Java枚舉變量能夠爲null,默認爲null。這意味着您沒必要定義單獨的「無值」常量,可是若是確實須要有效的枚舉值,則必須執行空檢查。
- Java枚舉類型支持任意字段,方法和構造函數。這使您能夠將任意數據和功能與每一個枚舉值相關聯,而無需外部幫助程序類。(在這種狀況下,每一個枚舉值都是一個匿名子類實例。)
兩個專門的集合EnumMap和EnumSet提供帶有或不帶有關聯數據的枚舉值的高性能子集。EnumSet
在將C#枚舉與[Flags]
屬性一塊兒使用時使用。內部實現其實是相同的,即位向量。
13.值類型
Java的一個重大缺陷是缺乏用戶定義的值類型。在還沒有肯定的將來版本中發佈時,Valvala 項目應提供具備泛型支持的.NET樣式值類型- 有關更多詳細信息,請參見建議值狀態和最小值類型。目前,Java提供的惟一值類型是其原語,它們徹底位於類層次結構以外。本節簡要描述了對語義,性能和泛型的影響。
語義 -值類型具備兩個重要的語義屬性:它們不能爲null(即具備「無值」),而且它們的所有內容在每次分配時複製,從而使全部副本在未來的變異方面彼此獨立。當前,對於Java中的用戶定義類型而言,第一個屬性是沒法實現的,只能經過頻繁的空檢查來近似。
使人驚訝的是,第二個屬性可有可無,由於值類型不管如何都應該是不變的,由於微軟發現了難題。默認狀況下,.NET中的值類型是可變的,而且因爲隱式複製操做而不會致使模糊錯誤的出現。如今,標準建議是使全部值類型都是不可變的,而且對於相似值的Java類(例如)也是如此BigDecimal
。可是,一旦對象成爲不可變的,則突變的理論效果就可有可無了。
性能 -值類型將其內容直接存儲在堆棧上或嵌入在其餘對象中,而無需引用或其餘元數據。這意味着它們須要的內存要少得多,前提是內容不比元數據大不少。並且,減小了垃圾收集器的工做量,而且不須要解引用步驟來訪問內容。
Oracle的Server VM很是擅長優化 C#將實現爲值類型的小對象,所以計算性能沒有太大差別。可是,額外的元數據不可避免地會膨脹大量的小對象。您須要複雜的包裝器類來解決此問題,例如,參見Java中的緊湊堆外結構/堆棧。
泛型 —如Valhalla:Goals項目中所述,基元不是類這一事實意味着它們不能做爲泛型類型實參出現。您必須改用等效的類包裝器(例如Integer
用於int
),從而致使昂貴的裝箱操做。避免這種狀況的惟一方法是專用於原始類型參數的通用類的硬編碼變體。Java庫充斥着此類專業知識。在此以前,沒有更好的解決方案,直到Valvala項目提供將原語集成到類層次結構中的值類型。
14.封裝和模塊
Java包在很大程度上等效於C#名稱空間,但有一些重要區別。從Java SE 9開始,模塊提供了其餘功能,用於依賴性檢查和訪問控制。
儲存格式
Java類加載器須要一個目錄結構,該目錄結構複製聲明的包結構。幸運的是,Java編譯器能夠自動建立該結構(-d .
)。此外,每一個源文件只能包含一個公共類,而且必須具備該類的名稱,包括確切的大小寫。
這些限制帶來了意想不到的好處:Java編譯器具備集成的「 mini-make」功能。因爲全部目標文件的位置和名稱均已明確規定,所以編譯器能夠自動檢查哪些文件須要更新,而僅從新編譯這些文件。
爲了分發,一般將已編譯的Java類文件的整個目錄結構以及元數據和任何所需的資源放置在Java歸檔(JAR)中。從技術上講,這只是一個普通的ZIP文件。.jar
可執行文件和庫的擴展名都相同。前者在內部被標記爲具備主要階級。
與.NET程序集不一樣,JAR文件是徹底可選的,沒有語義意義。全部訪問控制都是經過程序包和(在更大程度上)模塊聲明來實現的。在這方面,Java程序包和模塊結合了.NET名稱空間和程序集的功能。
配套
Java 包是組織類的基本方法。它們對於大型項目(如JDK自己)的表達能力還不夠高,這致使開發了Java SE 9的新模塊系統。可是,非模塊化軟件包仍然受到支持,而且對於較小的應用程序就足夠了。
聲明 — Java package
語句等效於C#namespace
塊,但隱式適用於整個源文件。這意味着您不能在單個源文件中混合軟件包,可是與C#格式相比,它確實消除了一種毫無心義的縮進。
Java import
等同於C#using
進行名稱空間導入,但始終引用單個類。使用.*
導入包中的全部類。該形式import static
等效於using static
(C#6),而且容許使用無限制的靜態類成員(Java SE 5)。可是,沒有類名別名。
存儲 -包含程序包源代碼的目錄可能包含package-info.java
僅用於文檔說明的可選文件。在非模塊化應用程序中,同一包的目錄樹能夠在不一樣的子項目中屢次出現。全部可見事件的內容都將合併。
可見性 —類,方法和字段的默承認見性是程序包內部的。這大體等效於C#,internal
但指的是聲明的包(C#名稱空間),而不是物理部署單元(JAR文件或.NET程序集)。所以,外部代碼只需聲明本身是同一包的一部分,就能夠訪問部署單元中全部默承認見的對象。若是您想防止這種狀況,則必須明確地密封 JAR文件,不然請使用模塊(Java SE 9)。
沒有爲默認的公開程度沒有專門的關鍵字,因此它暗示,若是沒有public
,private
也不protected
是存在的。C#程序員必須特別注意將私有字段標記爲private
避免此默認值!並且,Java protected
等效於C#internal protected
,即對派生類和同一包中的全部類可見。您不能將可見性僅限於子類。
最後,Java軟件包沒有「朋友」訪問(InternalsVisibleTo
屬性)的概念,該概念能夠提升對特定其餘軟件包或類的可見性。任何其餘軟件包都應可見的軟件包成員必須爲public
或protected
。
模組
Java SE 9引入了將任意數量的軟件包與顯式依賴項和可見性聲明結合在一塊兒的模塊。從技術上講,如今全部代碼都在模塊中運行,可是爲了向後兼容,將任何非模塊化代碼都視爲依賴於全部現有模塊並導出其全部包的模塊。
Oracle當前不提供有關模塊的簡明文檔。您能夠瀏覽Mark Reinhold的連接聲明,查閱Java語言規範的第7章,或爲不耐煩的人購買Cay Horstmann的Core Java 9。如下概述並不詳盡。
聲明和存儲 —每一個模塊對應一個具備(任意)模塊名稱的目錄,其中包含module-info.java
全部包含的軟件包的文件和子目錄樹。這些包被照常聲明。全部模塊聲明都位於中module-info.java
,使用僅在此處有效的特殊Java關鍵字。
依賴性 - requires
聲明當前模塊所需的任何模塊。(可選)transitive
將必需模塊設爲任何使用當前模塊的人的隱含要求。不須要的模塊對於當前模塊不可用,即便它們存在於模塊路徑中也是如此。
可見性 - exports
聲明全部導出的軟件包以供使用,並opens
聲明全部能夠對外反射的軟件包。(可選)exports/opens
能夠將可見性限制爲給定的命名模塊列表。其餘模塊看不到任何不可見的軟件包。所以,public
未導出程序包的internal
成員等效於C#成員。
儘管模塊的名稱可能與軟件包的名稱相同,可是應用程序中的全部模塊名稱和全部可見的軟件包名稱都必須是惟一的。所以,不可能擴充在另外一個模塊中聲明的包,從而解決Java包的奇怪漏洞。
15.例外
Java因其檢查的異常而臭名昭著,即,若是方法拋出但未捕獲它們,則必須在throws
子句中指定異常類型。長期以來,基於程序員的心理(檢查編譯器錯誤經過吞下異常使編譯器錯誤靜音,這比不處理異常更糟糕)和組件交互的緣由,人們一直在爭論檢查異常的價值。
例如,無心義的throws
子句可能在最壞的狀況下擴散開,或者在不適當的位置處理異常以阻止這種擴散。在設計C#時,Anders Hejlsberg著名地拒絕了檢查異常。一些程序員只是經過將檢查異常包裝在未檢查異常中而徹底避免了它們,儘管Oracle不喜歡這種作法。
可是,從概念上講,檢查異常很是簡單:檢查全部異常,除非源自Error
(嚴重的內部錯誤)或RuntimeException
(一般是編程錯誤)。一般的懷疑是在正常操做期間可能會發生的I / O錯誤,必須進行相應的處理。
不然,Java異常處理與C#很是類似。Java SE 7 在一個塊中添加了多種異常類型catch
,而且該try
版本複製了C#using
。在嘗試與-資源聲明依賴於對(Auto)Closeable
接口,就像C#using
依賴IDisposable
。Java SE 9也容許try-with-resources 有效地使用final變量。
的斷言錯誤 -對全部運行時錯誤Java的基類是不 Exception
做爲.NET而是Throwable
從雙方Exception
和Error
派生。不幸的是AssertionError
,因爲assert
失敗Error
而拋出的Java 是一個而不是一個Exception
。所以,若是您但願處理斷言錯誤以及異常(例如在後臺線程上),則必須捕獲Throwable
而不是Exception
。有關詳細信息和連接,請參見捕獲Java斷言錯誤。
跳轉和finally
—與C#中同樣,在finally
子句中引起的異常會丟棄關聯try
塊中先前引起的異常。不像C#,即禁止跳下finally
,只是返回從Java finally
條款也放棄之前的全部例外!這樣作的緣由使人驚訝的行爲是全部跳轉語句(break
,continue
,return
)列爲在一樣的意義爲「忽然結束」 throw
。該finally
條款的忽然結束丟棄該try
塊之前的忽然結束。
儘管實際上不太可能發生,但更奇怪的結果是,跳出finally
會覆蓋return
關聯try
塊中的普通語句。有關示例和更多信息,請參見最終跳出Java。啓用編譯器選項-Xlint:finally
以檢查此陷阱。
16.泛型
在Microsoft將它們添加到.NET 2的兩年前, Java SE 5引入了泛型。儘管兩個版本的源代碼看起來都類似,可是底層實現卻大不相同。爲了確保最大的向後兼容性,Sun選擇了類型擦除,該類型擦除在運行時消除類型變量,並用非泛型等效項替換全部泛型類型。這確實容許與遺留代碼(包括預編譯的字節碼)進行無縫互操做,但要付出新開發的巨大代價。
C#泛型簡單,高效且幾乎是萬無一失的。Java泛型相似於C ++模板,它們傾向於生成難以理解的編譯器錯誤,可是甚至不支持將非裝箱的原語做爲類型參數!若是要使用Java有效地調整大小的整數集合,則不能使用List<T>
etc的任何實現,由於這將對全部元素形成浪費的裝箱。
相反,您必須定義本身的非通用集合,int
並將其硬編碼爲元素類型,就像在普通C或.NET 1的糟糕年代同樣。(固然,您也可使用多個第三方庫之一)。)泛型中的基元計劃做爲Valhalla項目的一部分–參見上面的「 值類型」和Ivan St. Ivanov的文章系列「 泛型中的基元」(第2 部分,第3部分)。
我沒有試圖解釋Java和C#泛型之間的複雜區別,而是將您引到上面引用的資源以及Angelika Langer極其全面的Java泛型FAQ中。在本節的其他部分,我將僅介紹一些值得注意的要點。
構造 — Java缺乏C#new
約束,可是仍然容許使用類文字技巧來實例化泛型類型參數。Class<T> c
爲T
方法提供所需類型參數的類文字,而後在方法內使用c.newInstance()
來建立type的新實例T
。
從Java 8開始,您還可使用對方法的引用,這些方法是與lambda表達式一塊兒引入的,並在該部分中進行了介紹。
靜態字段 -靜態字段在通用類的全部類型實例化之間共享。這是類型擦除的結果,該類型擦除將全部不一樣的實例摺疊爲一個運行時類。C#進行相反的操做,並爲每一個泛型類型實例化的全部靜態字段分配新的存儲。
Java不容許靜態字段和方法使用任何泛型類型變量。類型擦除將Object
在共享的運行時類上使用(或一些更特定的非泛型類型)產生單個字段或方法。因爲類型擦除,對於來自不一樣類型實例化的不一樣類型實參,只有實例字段和方法纔是類型安全的。
類型界限 — 通用類型變量的可選界限(JLS§4.4)與C#類似,但語法不一樣。邊界由一種主要類型(類或接口)和零個或多個接口邊界組成,並附加&
。例如,<T extends C & I>
等效於C#<T> where T: C, I
。這確保了實際類型T
是一些亞型C
也實現了接口I
,其C
自己並無實現。有趣的是,Java還容許類型轉換表達式中的接口邊界(JLS§15.16)。
無效(Void) -正如沒法將基元指定爲泛型類型參數同樣,也沒法指定關鍵字void
。將Void類用於實現類不使用的通用接口的任何類型參數。
通配符 -從未引用的任何泛型類型參數均可以簡單地指定爲?
所謂的通配符。通配符也能夠用或限制。這容許像C#這樣的協變和矛盾,但不只限於接口。要引用通配類型參數,請使用聲明命名類型參數的單獨方法捕獲它。extends
super
in/out
有一個與通配符有關的巧妙技巧。若是容器包含帶有通配符的某些常規元素類型,例如TableView <S> .getColumns返回的集合,則能夠將具備不一樣具體類型的通配符實例放入同一容器中。在C#中,不一樣的具體類型參數產生不兼容的類,這是不可能的。
17.館藏
在Java集合框架(教程)大大優於其至關於.NET設計。集合變量一般是從豐富的接口層次結構中鍵入的。它們幾乎與其實現類同樣強大,所以後者僅用於實例化。所以,大多數集合算法均可以在具備適當語義的任何符合框架的集合上工做。這包括各類可組合的包裝方法,例如動態子範圍和只讀視圖。
接口方法和具體實現的某些組合可能效果不佳,例如索引鏈表。Java傾向於公開一個可能緩慢的操做,而不是根本不公開該操做,這一般是.NET限制性更強的收集接口的狀況。
迭代器 -Java容許在迭代其元素時對集合進行變異,但只能經過當前的迭代器進行。Java還具備專門的ListIterator,能夠返回其元素索引。使用集合迭代器時,.NET既不容許進行突變也不容許進行索引檢索。
Java SE 5添加了一個等效於C#語句的for-each循環foreach
,但沒有專用關鍵字。此循環未公開Java集合迭代器的變異和索引檢索功能。與C#中同樣,數組上的for-each循環是特殊狀況,以免建立迭代器對象。
流 -Java SE 8添加了流和管道,這些流和管道連接方法要求累積操做,例如基於方法的C#LINQ版本。能夠從常規集合建立流,也能夠從生成器函數或外部文件建立流。管道僅在須要時獲取新元素,而且能夠順序或並行處理它們。Lambda表達式用於自定義管道操做。最後,終端操做將結果轉換爲另外一個常規Java對象或集合。
18.註釋
Java SE 5引入了與.NET屬性大體等效的註釋。註釋使用任意元數據標記程序元素,以供之後由庫方法或編程工具提取。除了語法上的差別外,還有一些C#開發人員值得注意的要點:
- 註釋不能更改帶註釋的程序元素的語義。特別是,它們沒法像
[Conditional("DEBUG")]
.NET斷言那樣徹底抑制方法調用。 - @FunctionalInterface驗證接口僅包含單個方法,所以能夠經過lambda表達式或方法引用來實現。
- @Override替換
override
了Java語言中莫名其妙缺乏的C#。 - @SuppressWarnings和特定形式@SafeVarargs至關於C#
#pragma warning
。一般將它們與Java的類型擦除泛型一塊兒使用。
Java SE 8 除了類型聲明外,還容許註釋類型用法。可是,您須要外部工具才能今後類註釋中受益。
19.評論
與C#同樣,Java 爲類和方法上的代碼註釋定義了一種標準格式,這些註釋能夠提取爲格式化的HTML頁面。與C#不一樣,JDK附帶的Javadoc處理器直接執行輸出格式化,所以您不須要外部格式化程序,例如NDoc或Sandcastle。
儘管Javadoc缺少編譯器檢查的方式來引用註釋文本中的參數,但功能類似。語法有很大不一樣,而且更加簡潔,由於Javadoc主要依靠隱式格式和緊湊@
標籤。HTML標籤僅在不@
存在適當標籤的狀況下使用。
若是您須要將大量的C#XML註釋轉換爲Javadoc格式,則應查看個人註釋轉換器,它能夠爲您完成大部分機械翻譯。
摘要 -默認狀況下,Javadoc註釋的第一句話會自動視爲其摘要。Java SE 10引入了標籤{@summary … }
以顯式定義摘要,該摘要等效於<summary>
C#XML註釋的元素。