Java/數據類型

維基教科書,自由的教學讀本

Java > 數據類型


上下文鏈接[編輯]

數據類型[編輯]

數據類型是程序設計語言描述事物、對象的方法。Java數據類型分為內置類型擴展類型兩大類。內置類型就是Java語言本身提供的基本數據類型,比如,整型數,浮點數,字符,布爾值等等。而擴展類型則是Java語言根據基本類型擴展出的其他類型,Java要求所有的擴展類型都必須包括在類定義裡面,這就是Java被叫做面向對象編程語言的原因。不同於像SmallTalk一樣的純粹的面向對象語言,Java保留了基本數據類型,但這主要是為滿足Java高效率執行的需求。JRE(Java Runtime Environment)提供了豐富實用的擴展類型供程序員使用,程序員也可以根據需要通過繼承機制自己擴展。

內置類型[編輯]

Java的內置類型包括如下幾種:

類型名稱 類型定義 類型取值
boolean 布爾值,作二元判斷 true, false
byte 8位有符號整數 最小值-128,最大值127
short 16位有符號整數 最小值-32768,最大值32767
int 32位有符號整數 最小值-2147483648(-231),最大值2147483647(231-1)
long 64位有符號整數 -263~(263-1)
float 32位浮點數 1.4E-45~3.4028235E38
double 64位浮點數 4.9E-324~1.7976931348623157E308
char 32位Unicode字符(UTF-16) 不適用(可以與int互相轉換)

內置類型也稱作基本類型(Primitive Types),是其他類型的基礎。所有的其他類型(包括Java核心庫和用戶自定義類型)都是通過基本類型擴展而來的。這些類型是我們描述對象最基本的方式。比如:判斷是否使用boolean;衡量大小、長度使用各種整型(包括short, int, long)和浮點數(floatdouble);表達字符使用char;而處理二進制數據塊使用byte。Java也提供了基本類型的類包裹(Wrapper),這些類包裹都包括在了java.lang包裡面作為Java的語言基礎,比如對int的類包裹是Integer類。提供這些類包裹的原因主要是為了在某些接口上提供和類(對象)一致的接口,比如在泛型設計上的數據模板等等。而且相應的包裹類中還提供了對基本數據類型的操作,比如int Integer.parseInt(String s, int radix)將字符串轉化為相應進制的整型數。

浮點數[編輯]

浮點值的精度[編輯]

浮點值與我們通常所使用的小數是不同的。在使用中,它往往是難以確定的。常見的問題是定義了一個浮點數,經過一系列的計算,在數學上,它本來應該等於某個確定值 。

但是使用相等運算符(==)進行判斷時,往往得出的結論是不等;或者本來應該大於/小於某個特定值,結果卻可能剛好相反。因此,對於浮點值比較時,不能簡單地使用==, >, <等運算符。

我們做計算時,往往都有一個精度,或由於客觀事實,或由於我們的需要。這樣我們比較兩個浮點數時,就不是簡單的使用比較運算符了,而是在允許的精度之內作比較。比如:比較兩個浮點數(f1, f2)是否相等,精度為p

| f1 - f2 | < p   ----------  ''true''
| f1 - f2 | > p   ----------  ''false''

上面的||表示絕對值運算,java.lang.Math類對絕對值運算提供了支持。 同樣,比較f1是否大於f2,如下:

( f1 - p ) > f2   ----------  ''true''

這種方法適用於大多數數據處理情況,但也有時候,必須是完全精確計算,這樣就不能使用這種方法了,而應該採用java.math.BigDecimal和java.math.BigInteger來計算。

關於浮點數精度問題還可以參看您的小數點到哪裡去了

值類型與引用類型[編輯]

不同於C/C++語言,Java中是看不到指針概念的,這是因為Java認為對指針的誤操作會引起內存的外溢,而且對指針的管理應該是虛擬機本身的事情,而和程序無關。Java虛擬機會對引用對象的次數做出管理,並決定對那些引用次數變為零的對象在適當的時候做出回收釋放內存的操作,這裡稱之為「垃圾回收」。比如:

String text = "World";
text = "Hello " + text;

在上段程序中,先行賦值了的text在下一段代碼中會被賦予新值,做為舊值的對象"World"因為沒有別的引用而不可能再被使用到,所以它將進入垃圾回收階段。所以,大家可以看出,在Java中,對象的引用其實就類似於指針,只是少了C/C++中的「*」號,而且因為有垃圾回收機制,所以不用額外delete掉舊數據。對於熟知C/C++的朋友可以知道,在調用函數的時候,傳遞基本類型,傳遞指針和傳遞引用是有區別的,這裡將示例如下:

int x = 10;

void func1(int a){
    a++; return;
}

void func2(int* p_a){
    (*p_a)++; 
    p_a++; 
    return;
}

void func3(int& a){
    a++; return;
}
int main(){
    func1(x);//x=10
    func2(x);//x=11
    func3(x);//x=12
    return 0;
}

如果將x傳遞分別傳遞到三個函數的結果是不一樣的。對於func1來說,在傳遞的時候x將被賦值一遍給a,所以執行完a++以後,x的值是不變的還是10。而func2的參數是一個指針,傳遞的時候先取x的地址賦給p_a,執行完(*p_a)++之後因為p_a是指向x的,所以x本身會被加一,但是之後的p_a++只是將指針挪到下一個int數據上,並不會改變x的地址。最後,func3是傳遞引用,也就是說函數內的a和函數外的x本質上是同一個數,所以a++使得x本身加一。

看完C/C++的例子,我們來看Java的情況。因為Java分基本類型和對象類型兩種,對於基本類型在調用函數的時候,Java都是傳遞值的,類似上例中的func1。而對於對象類型,Java是傳遞引用的,也就是說函數內和函數外是同一個對象。舉個例子:

public class Test {
    public static void main(String[] args){
        StringBuffer buffer = new StringBuffer();
        for(String s : args){
            strcat(buffer, s);
        }
        System.out.println(buffer.toString());

        int x = 10;
        increaseOne(x);
        System.out.println(x);
    }

    public void strcat(StringBuffer sb, String s){
        sb.append(s);
    }

    // 错误示例
    public void increaseOne(int x){
        x++;
    }
}

上面的程序將命令行參數連接在一起並輸出,其中strcat函數起到了連接作用,而我們用StringBuffer來構造新的字符串。在這裡,buffer是在main函數裡新建的StringBuffer,並傳遞給了strcat。注意,因為是引用傳遞,所以在strcat函數裡的append操作其實就是main里的buffer,最後的輸出的是連接起來的字符串。這裡還給出了一個錯誤示範increaseOne,因為是傳值調用,所以increaseOne執行完返回後,main函數裡的x仍然是10。