1. 程式人生 > >TIJ學習筆記(二)——基本型別

TIJ學習筆記(二)——基本型別

這是Thinging in Java學習筆記的第二篇,主要是關於基本型別的。
不足之處,歡迎斧正。

        不少人(包括我自己)學到了變數之後會很奇怪Java不是面嚮物件語言嗎,為什麼有int這種極具C語言(面向過程語言)氣息的變數?         既然有int那就直接用int就好了嘛,為什麼還要裝箱(Boxing)和拆箱(Unboxing)?

int -> 面對物件?

        前一篇部落格中提到了面嚮物件語言的一個特性 “Everything is an Object.”         從前文也可以推斷出這個特性並不是絕對的,在everything之外也是有特例的,這個特例就是基本型別(Primitive Types)。         在程式設計中經常會使用到一系列型別,它們需要被特殊對待,我們可以把它們想象成“基本”(primitive)型別。之所以特殊對待,是因為new將物件儲存在“堆”裡,故用new建立一個物件——特別是小的、簡單的變數,往往不是很有效。

        這麼高深的話像我等俗人是理解不來的,所以我把它翻譯了一下就是我如果用個簡簡單單的int還要去創造一個類,去new出來,然後再去賦值太麻煩了,人累、編譯器也累。         當然這個原因並不能支援一門純粹的面嚮物件語言去保留面向過程語言的特性,我覺得它還有其他的原因。在解釋這些原因之前我們需要先了解一下這些型別所佔用的記憶體空間大小。

Primitive Type Memory Required(bytes) Wrapper Type
boolean 1 Boolean
byte 1 Byte
short 2 Short
char 2 Character
int 4 Integer
float 4 Float
long 8 Long
double 8 Double

        Reference型別在32位系統上每個佔用4bytes, 在64位系統上每個佔用8bytes。         因為Java的Object將基本的資料都包裝了一遍,導致我們在使用這些包裝類的時候佔用了更多的記憶體以及使用其中的資料的時候佔用了更多的時間。         下面是關於時間的測試程式碼:

public class Main {
    public static void main(String[] args) {
        a
(); b(); } public static void a(){ long startTime=System.nanoTime(); for(int i=1;i<10000;i++){ int a = 1,b = 2; int c = a + b; } long endTime=System.nanoTime(); System.out.println("程式執行1時間: "+(endTime-startTime)+"ns"); } public static void b(){ long startTime=System.nanoTime(); for(int i=1;i<10000;i++){ Integer a = 1,b = 2; Integer c = a + b; } long endTime=System.nanoTime(); System.out.println("程式執行2時間: "+(endTime-startTime)+"ns"); } } /* * 程式1執行時間: 105223ns * 程式2執行時間: 1464137ns */

        可以看出,在相同次數的操作下Integer的所用的時間是int的14倍以上。         當然,會造成的問題不僅僅如此,比如下面這個例子。

Integer i1 = 500;
Integer i2 = 500;
System.out.println( i1==i2 );  //false

        emmm,500不是500。當然這個並不是一個大問題,換成equal就行了,至於原因和JVM虛擬機器有關。

總結一下為什麼存在這些基本型別的原因:

  • 記憶體佔用問題
  • 速度問題
  • 變數比較問題
  • 開發效率問題

拋棄基本型別

        從上面的原因看來好像如果為了面向物件的純粹也是可以拋棄基本型別也是可以接受的,但事實上,並沒有這麼簡單。         首先,我們如果全部淘汰了基本型別,基本上所有的舊程式都不能允許了,這個代價並不是哪一方可以承受的。         其次,為了支援這個新特性,需要把重寫整個JVM虛擬機器。         最後,這個特性用了這麼久了,即沒出現什麼大的問題,還可以減輕這麼多開發的代價,為什麼要吃力不討好去拋棄它呢。

裝箱(Boxing)和拆箱(Unboxing)

        既然選擇了保留基本型別這一特性,那麼避免不了的這些基本型別得去向其他的物件(Object)發信息,可是兩個世界的東西要怎麼傳送資訊呢?         這時,裝箱和拆箱的作用就體現出來了。裝箱可以將int等基本型別包裝成物件,使其和其他物件一樣也有狀態和行為;類似的,拆箱就是將被包裝好的Integer等物件拆成基本的int等型別。         得益於Java的自動裝箱和自動拆箱,我們可以很輕鬆等進行轉換。

int i1 = 500;

Integer i2 = i1;
執行上面那句程式碼的時候,系統為我們執行了: 
Integer i2 = Integer.valueOf(i1);

int i3 = i2;
執行上面那句程式碼的時候,系統為我們執行了: 
Integer i2 = i2.intValue();;

這裡需要注意的是:

Integer i4 = new Integer(10);

已經是過時的了,Integer.java 裡面的說明是:

@deprecated It is rarely appropriate to use this constructor. Use {@link #parseInt(String)} to convert a string to a {@code int} primitive, or use {@link #valueOf(String)} to convert a string to an {@code Integer} object.

參考資料

《Thinking in Java》 Fourth Edition ——Bruce Eckel