程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Why Java Sucks and C# Rocks(2):基礎類型與面向對象

Why Java Sucks and C# Rocks(2):基礎類型與面向對象

編輯:關於JAVA

既然已經談過這次語言比較的意義與目的,而完整的幻燈片和錄音也已經放出,那麼接下來自然是詳細討論了。在這篇文章中,我會對 兩個語言的基本特征進行簡單描述,並主要討論兩者對於基礎類型的處理方式。在我看來,Java語言對於基礎類型的處理方式,並不如C# 中值類型般妥當。如果您有任何覺得不妥或是想要補充的意見,請不吝回復。由於C# 1.0發布於2002年,因此本文內容將基於Java 1.4及 C# 1.0的情況。

Java語言簡單描述

Java既是一個完整的平台,也是一門語言。Java語言是1995年由James Gosling在Sun Microsystems公司設計,作為Java平台的組成部 分之一的語言。Java平台除了語言之外,還有兩個組成部分,虛擬機(JVM)和類庫。不過在這一系列文章中,我會將關注裡放在Java語言 這個單獨的方面,偶爾會談到一點JVM,而對於類庫方面則幾乎不會涉及。

Java語言參考了C語言和C++的設計,因此在代碼整體風格上與它們比較類似。不過與C++相比,Java語言設計的更為小巧,簡單和可靠 。 Java的類型分為兩種:類(class)和基本類型(primitive type),並沒有C++中的struct和union類型。同時,Java還提供了boolean 類型,並對布爾類型的定義和使用作出了限制。此外,Java中也不允許開發人員進行運算符重載,但提供如synchronize等進行並發控制的 語言特性。

Java語言設計的一大目標是可靠性,因此在Java中,部分元素的抽象級別被提高了,例如數組在Java中是預定義類的實例,在C++中就 不是。此外,例如在數組訪問中會進行下標范圍檢測,同時也去除了指針等不安全的語言特性。雖然Java中的“引用”也有些“指針”的 意味,但更為安全,例如程序員不可以對指針進行算術運算,這樣便避免了一些容易出錯的做法。

在面向對象類型系統的設計中,Java不允許C++中的多重繼承,因為許多人認為多重繼承所帶了許多復雜性和混亂,可謂弊大於利。不 過Java允許開發人員定義“接口”,即一種“契約”而不包含實現,這在一定程度上也可以帶來部分多重繼承的優點。

總體而言,Java語言去除了C++中大量的復雜或是不安全的特性,這使的Java成為了一門靈活而強大,同時又更為小巧,簡單和可靠的 語言。從現在的角度看,Java語言大大降低了C++本身所帶來的復雜度,讓編程工作變的更為簡單,具有很高的歷史意義。

C#語言簡單描述

C#語言是Anders Hejlsberg為微軟.NET平台設計的一門語言。與Java語言不同的是,C#的定位只是.NET平台上各種語言中的一種——盡 管如此,從現在來看,.NET平台和 Java平台雖然起步不同,但可謂殊途同歸。與Java平台比較類似,.NET平台除了多種語言之外,也有對 應的虛擬機(CLR)及基礎類庫(BCL)。

C#的語言設計同樣是基於C和C++的,但是也包括了一些Delphi和VB的思想。許多人覺得Java語言相比C++的優越之處在於“簡單”,不 過似乎C#的設計人員認為Java語言簡化的有些太過了,因此C#將指針(受到一定限制)、struct、enum、運算符重載及goto等語言特性包 含在C#語言中。不過,C#的設計人員同時也改進了這些特性,例如C#中的enum類型不能同整數類型進行隱式轉換。同樣,在C#的struct中 ,我們也可以為它定義構造函數和成員,並可以實現接口。

C#中還提供了一種數據類型“委托(Delegate)”,它可以被視為是一種類型安全的函數指針。一個委托也是一個對象,它明確定義了 函數的簽名,可以用來保存一個包含了調用時所需的上下文的函數。在.NET程序中,一個回調函數往往會使用委托進行表示,而在Java中 ,則一般會使用接口。

C#還提供了一些方便開發的特性,例如foreach,using,以及使用params定義可變長數組等等。此外,C#還提供了事件(event)及屬 性(property)兩種對象的成員類型。屬性可以簡單理解為帶有讀寫器(也可以只讀或只寫)的字段。Java語言雖然不包含屬性,但很早 便形成了一個約定,會將getXyz/setXyz方法作為屬性處理,許多開發工具和類庫都會以這種方式進行處理。

C#語言的初衷是要成為優越於C++和Java的通用程序設計語言。經過多年的發展,C#在語言設計方面的確遙遙領先於Java語言,這也是 我寫這一系列文章的事實依據。

Java是個純面向對象語言嗎?

Java在誕生於面向對象理論在業界愈發普及和流行的時期(著名的GoF設計模式一書便出版於1994年),在當時Java聲稱自己是個“純 面向對象語言”,但事實果真如此嗎?我不這麼認為,因為有很明顯的一點是,Java語言中的基礎類型不是對象。

在Java中定義了一系列基礎數據類型,例如int,double,boolean等等。這些類型不是對象,它們不包含成員,也不繼承於公共基類 Object,因此Java並不是一門純面向對象語言。

不過,其實我關注的並不是“純面向對象”這個稱號,我更關注Java語言在設計時是如何對待這些類型的。從代碼上看,這個特點的確 造成了諸多不便:

ArrayList list = new ArrayList();
list.add(5); // cannot compile
int i = (int)list.get(0); // cannot compile

int hash = 3.hashCode(); // cannot compile

上面這行代碼有三處會編譯不通過。ArrayList是個用於保存Object類型對象的容器,可以存放任何類型的對象。不過由於int類型的數 值 5不是對象,因此它無法被添加到ArrayList中。同樣,在通過get方法獲取到一個Object類型的對象後,我們也無法將其轉換成int類型 。自然,int類型沒有成員,因此我們也無法通過它調用定義在Object類型上的hashCode方法。

直至Java 1.4,開發人員都必須編寫這樣的代碼:

ArrayList list = new ArrayList();
list.add(Integer.valueOf(5));
int i = ((Integer)list.get(0)).intValue();

int hash = Integer.valueOf(3).hashCode();

在Java類庫中也為每個基礎類型分別指定了封裝類(wrapper class),當遇到一些需要和Object進行互操作的時候,便可以把基礎類 型包裝為一個對象。這些對象繼承於Object類型,自然能夠被添加到 ArrayList中,也擁有hashCode等定義在基類中的方法。只是,在獲 取到Object對象並轉換成封裝對象時,還需要調用對象上的某個方法(如上面的intValue方法)才能重新獲取到基礎類型。

C#中的值類型

在此期間微軟發布了.NET平台和C#語言。在C#語言中並沒有所謂的“基礎類型”,或者說C#的“基礎類型”是作為.NET中的struct類型 統一對待的。在.NET框架中定義了一系列struct類型,如Int32,Boolean,Double等等,在C#語言中使用關鍵字 int,bool,double與之 對應,這便有了一些“基礎類型”的意味。自然,在C#代碼中我們也可以直接使用那些類型的名稱,完全等價。

開發人員可以在程序中定義自己的struct類型,並包含構造函數,方法或是屬性等等,並統一繼承於ValueType類型(值類型),而 ValueType也是統一基類Object的子類。因此,在C#中那些“基礎類型”也擁有Object類及自己的成員,並可以直接應用在需要 Object的 地方(即隱式轉換)。因此,在C#中我們可以直接編寫這樣的代碼:

ArrayList list = new ArrayList();
list.Add(5);
int i = (int)list[0];

int hash = 3.GetHashCode();

相信看了這幾行代碼您就能明白了。可以看出,在C#中,值類型和普通的類(也被稱為引用類型)在使用上並沒有任何區別。

當然,既然被稱為“值類型”,它自然和普通的引用類型有所區別。首先,在.NET中值類型在賦值時是“整體拷貝”而引用類型只是復 制一個引用。更重要的是,值類型是分配在方法的調用棧上,而引用類型則是分配在托管堆上。這意味著前者在當前方法退出後會被自動 釋放,而後者則必須等待GC運行時將無用的對象消除。

換句話說,值類型不會對GC造成壓力(除非進行了裝箱),這點很重要。在某些場景中,例如在並行計算時,假如每個線程臨時對象創 建地過於頻繁,則可能導致GC頻率加大。而GC在啟動時會暫停運行中的所有線程,因此並行計算最終的瓶頸可能就落在了單線程的GC上— —此時您投入再多的CPU等運算資源也無濟於事。解決這個問題一般有兩種辦法,首先是啟用並行GC,則為每個CPU分配一個獨立的托管堆 。在執行時,對象會分配在當前CPU的托管堆上,每個托管堆也有一個線程負責GC操作,這樣GC能力也會隨著計算能力加大而提高,因此不 會成為性能瓶頸。另一種,有時候也可能是更為合適的做法,便是將創建的臨時對象設定為值類型,這樣一切便是在調用棧上的讀寫操作 ,便不會對GC造成壓力。

在.NET平台中,一旦將一個值類型的對象用作引用類型時(即轉化為Object類型或接口),運行時便會對它進行“裝箱”:此時運行時 會在堆上創建一個對象,並將值類型的內容復制到對象內部。將一個裝箱後的對象轉化為值類型時,則會將托管堆上的對象內容復制到方 法的調用棧上,這便是所謂的“拆箱”。裝箱和拆箱在.NET中是由運行時負責的,不需要特定的封裝類,支持任意值類型。

在Java中,開發人員無法自定義值類型,因此所有的對象都是分配在托管堆上。不過這些倒也是和平台密切相關的內容,這裡便只作一 提吧,畢竟我們的目標主要還是在語言方面。

Java 1.5中的自動裝箱/拆箱

Java語言基礎類型和封裝類型之間的這種轉化方式,從1995年Java語言出現開始,一直保持到2004年Java 1.5出現才有所改變,將近十 年時間。那麼,Java語言究竟是因為缺少競爭對手而不思進取,還是因為沒有比較就體會不到麻煩呢?無論怎樣,我相信C#在這方面對 Java語言產生的影響是毋庸置疑的。

不管怎麼樣,在Java 1.5中引入了一個新特性:自動裝箱/拆箱(auto boxing/unboxing)。此時,我們便可以編寫這樣的代碼了:

ArrayList list = new ArrayList();
list.add(5); // auto boxing
int i = (Integer)list.get(0); // auto unboxing

在int值5用在Object參數的時候,Java語言的編譯器將自動生成創建Integer對象的bytecode。同樣,將Integer類型的對象賦值給基礎 類型int的時候,Java語言的編譯器也會自動生成intValue等方法的調用。那麼,它和C#中的裝箱和拆箱有什麼不同呢?自然,區別之一在 於C#的裝箱和拆箱是.NET平台已有的功能,C#編譯器只要直接使用即可,而Java的自動裝箱和拆箱完全是編譯器的工作。但我倒認為,這 個區別並不是我這裡特別關注的。

畢竟我現在關注的是語言,也就是通過代碼本身所表現出來的,尤其是可以體現出兩種語言在設計理念上有所不同的區別。

如果我們仔細觀察代碼的話,就可以發現,Java編譯器其實是將Integer對象與int值互轉,例如在上面的第3行代碼中,我們先將 Object對象轉化成Integer類型,然後再隱式地轉化至int基本類型。而在C#中,int類型是直接和Object類型相互轉化的。我認為,Java的 這種做法,表示Java的設計者依然不希望開發人員將int等基本類型看作是一種對象,他們只是讓編譯器可以幫助開發人員少些一些代碼而 已。換句話說,Java的設計者認為,int可以看作是Integer對象,boolean是Boolean對象,但是int和boolean仍然是基礎類型,而不是對 象。

下面的這行代碼可能更加能夠直接說明這個問題:

int hash = 3.hashCode(); // cannot compile

在Java中,這行代碼是無法編譯通過的,因為Java編譯器並不會將int自動視作Integer對象,基礎類型在這裡依然是基礎類型,不是對 象。

在之前的討論過程中,有朋友說,Java在這裡不做自動裝箱,是因為要在bytecode層面上保持與之前兼容。我不同意這個說法,因為我 想象不到實現如C#這樣的自動裝箱和拆箱會破壞bytecode的兼容性,Java編譯器完全也可以在語言級別將int類型和Integer類型等同起來 ,所以在這方面我認為完全是語言設計理念上的區別。

說實話,我不喜歡Java的思路,我更認同C#這種更為“面向對象”的設計方式。

文章地址:http://blog.zhaojie.me/2010/04/why-java-sucks-and-csharp-rocks-2-primitive-types-and-object- orientation.html

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved