當前位置:開發者網絡 >> 技術教程 >> JSP教程 >> 資料/其它 >> 內容
精彩推薦
分類最新教程
分類熱點教程
    
Java中鮮為人知的缺點(上)
作者:未知
日期:2003-07-26
人氣:
投稿:Andy.m(轉貼)
來源:未知
字體:
收藏:加入瀏覽器收藏
以下正文:
2003/01/29 http://china.nikkeibp.co.jp/china/news/image/space.gif [url]http://china.nikkeibp.co.jp/china/news/logo_img/nby.gif[/url]
  【日經BP社報道】 Java是當今使用最廣泛的編程語言之一。自1995年發佈以來,一直被用戶高度評價為「消除了C++缺點的優秀編程語言」。不過,隨著它的廣泛使用,其缺點也在逐步地表現出來。

  Java的缺點公認有如下三點:(1)存在非對象的數據類型;(2)不能夠用一種描述方法來表達各種類(Class);(3)無法繼承2個以上的類的裝配。雖然也有人認為編程語言應該是一個什麼樣子會因人而異,不應該算成缺點。不過,上述三點卻可以導致編程人員使用混亂,降低源碼的可讀性及程序的可維護性。

存在非對象的數據類型

[url]http://china.nikkeibp.co.jp/china/img_data/030129java1.jpg [/url]
表1●Java的原始類型(Primitive)。原始類型包括表示真假的布爾型(Boolean)、字符型和數值型等(點擊放大)


  第一缺點是指雖說Java是面向對象的編程語言,但卻存在非對象的數據類型。

  「面向對像」的定義雖然有很多種,但無論何種定義,其最基本的概念都是利用包含數據和步驟的「對像」來表達系統。即便在Java領域,也會使用名為類的模型生成對象,並通過調用它的方法組織程序。

  但是,其中卻混雜著非對象的內容。原始類型又被稱為基本數據類型(表1)。用於處理文字的char(字符型)、表示真假的boolean(布爾型)、int以及float等數值型就屬於這種數據類型。

原始類型的內存管理方法不同

[url]http://china.nikkeibp.co.jp/china/img_data/030129java2.jpg [/url]
圖1●Java的內存管理方法。Java內存區包括保存本地變量的內存堆棧區(stack)和保存對象的數據的內存堆區(heap)。堆棧區中存放的是用於引用(reference)對像時所需的信息(點擊放大)


  原始類型和對像型的內存管理方法不同。Java虛擬機所管理的內存區包括內存堆棧區和內存堆區(圖1)。內存堆棧區用於存放本地變量的數據。按堆出的順序保存本地變量的數據。一旦脫離變量的有效範圍,該數據立刻就被釋放。

  而內存堆區則用於存放對像本身。生成對像型的變量後,首先在內存堆棧區中為其準備存放位置。然後利用new運算符在這個位置生成新的對象後,對象及其數據就被存放到內存堆區。接著,內存堆區中的對象位置就會作為對像型的變量數據而被寫入內存堆棧區。由於這些是引用對像時所用的信息,因此對像型變量被稱為「引用型」。

  而實際數據本身被寫入內存堆棧區的是原始類型。採用這種內存管理方法的類型稱為「數值型」。

  引用型變量改變以後,就會引用保存內存堆區中的實際的對象數據,重新改寫數據。比如,在方法的引數(arguments)中描述對像型變量時傳遞給方法的就是存放在這個位置中的信息。所以在方法內追加的變更還會被反映到調用的原始對像上。另一方面,數值型變量傳遞的是它的值。即便在方法內部進行了變更,也不會反映到原始變量中。

無法使用對像所具有的功能

LIST 1●將數值數據保存在Java的矢量類中的程序。生成Integer類,然後封裝(Wrap)數值(點擊放大)
[url]http://china.nikkeibp.co.jp/china/img_data/030129java3.jpg [/url]
LIST 1●將數值數據保存在Java的矢量類中的程序。生成Integer類,然後封裝(Wrap)數值(點擊放大)


  由於原始類型與對像型的內存管理方法不同,因此就無法生成統一兩種數據的類庫。比如,如果只是對像型數據就能夠構築包含任意數據的類庫。

  可變長的數組類就是其中的一個例子。它是作為名為java.util.Vector的類而生成的。可以將任意的對象追加到數組中,還可以提取或刪除。能夠以此為引數指定任意的對象。但是,由於原始類型數據不是對象,因此無法直接引入。

  因此在Java中還存在相當於原始類型的類。比如int型變量就可以使用java..lang.Integer類。重新生成Integer類,然後保存數據,就可以追加到Vector矢量類中(LIST 1)。

  但是稍微想一想就會明白,這種方法並不是很靈活的做法。由於加入了多餘的代碼,因此看起來感覺比較亂。而且還會浪費內存空間。原來的值暫且不說,還必須確保新建對像所需的內存。不僅存在表面上的問題,還存在實質上的問題。就是說無法保證數據的同一性。作為對像型保存的值與作為原始類型而保存的值完全不同。即便改變了原始類型的值,也不會反映到原來的int型數據。

C#利用Boxing(裝箱)解決的只是一部分

  這一問題並非是Java特有的。比如,作為與Java類似的語言為用戶熟知的C#也存在相同的問題。C#利用稱為Boxing的方法部分地解決了這個問題。但是所解決的也只是可以不寫多餘代碼的部分。內存問題和同一性問題仍舊存在。

  即便C#,int、double和char等數據類型也無法作為對像進行處理。這些數據類型與Java的原始類型相同,也是數值型變量。

  C#可以將其值代入到對像中。LIST 2中顯示了具體的代碼。已經將int型的值代入了對象型變量。此時先進行裝箱,之後就開始悄悄地把基本數據類型的數據轉換成對像型數據。在內存堆區中確保相應的內存,然後將數值型數據保存這裡(圖2)。對像型變量引用的就是這些數據。

[url]http://china.nikkeibp.co.jp/china/img_data/030129java4.jpg [/url][TR][TD]LIST 2●執行裝箱的C#代碼。將數值直接代入對像中。運行代碼後,輸出0和1。也就是說變量a和o沒有同一性(點擊放大)


[TR][TD][url]http://china.nikkeibp.co.jp/china/img_data/030129java5.jpg [/url][TR][TD]圖2●C#中的裝箱法。對存放於內存堆棧區中的int型結構體(structs)裝箱時,就會悄悄地在內存堆區中生成對象。因此就無法確保與初始值的匹配性。(點擊放大)


  筆者利用裝箱法,用C#試著寫了一段與在Java的Vector矢量類中保存數值類似的代碼(LIST 3)。雖然ArrayList類要引數中提取對像型變量,但這裡由於通過直接int型變量,因此代碼非常整潔。

  不過,並沒有解決多餘的內存消耗和數值的同一性問題。因為只是單純地實現了自動向對象的轉換(圖3)。

[TR][TD][url]http://china.nikkeibp.co.jp/china/img_data/030129java6.jpg [/url][TR][TD]LIST 3●與LIST 1起相同作用的C#代碼。由於具有裝箱法,因此可以直接向ArrayList中追加數值(點擊放大)


[TR][TD][url]http://china.nikkeibp.co.jp/china/img_data/030129java7.jpg [/url][TR][TD]圖3●利用Java和C#,將int型變量轉換成對象的方法。儘管內部處理基本相同,但C#的特點是隱式轉換(點擊放大)


如果考慮到實用,也算得上是優點

  從上述所講來看,就會生出這樣的疑問:為什麼最新的Java和C#語言還存在著這樣的問題呢?實際上這是因為對其性能的重視。

  由於原始數據型數據在編程時使用得最多,因此利用能夠對其進行快速處理的原始類型,性能就會提高。而對像型數據在生成對象,以及使用位置信息去引用內存堆區中的數據時則會產生一定的開銷。另一個問題是內存堆區。如果全部是對像型,比如,只要執行簡單的for循環語句,就會在內存堆區中生成大量的對象。由於內存堆區的消耗速度就會急劇上升,並且頻繁地進行資源回收處理,因此性能就會降低。

  Java和C#是考慮到性能問題才生成原始數據型數據的。因此並不能說是「純粹的」面向對像語言。也許可以說是考慮到實用性的穩妥做法吧。(記者:大森 敏行、八木 玲子) 
相關文章: