1. 程式人生 > >Java資料型別及各種型別的儲存方式

Java資料型別及各種型別的儲存方式

Java 的兩大資料型別:
1、內建資料型別
2、引用資料型別
Java語言提供了八種基本型別。六種數字型別(四個整數型,兩個浮點型),一種字元型別,還有一種布林型。

資料型別 預設值 佔用空間 取值範圍
byte 0 8位 2
7 2 7 1 -2^7-2^7-1
short 0 16位 2 15 2
15 1 -2^{15}-2^{15}-1
int 0 32位 2 31 2 31 1 -2^{31}-2^{31}-1
long 0L 64位 2 63 2 63 1 -2^{63}-2^{63}-1
float 0.0F 32位 3. 4 38 3. 4 38 -3.4^{38}-3.4^{38}
double 0.0D 64位 1.7 9 308 2 1 . 7 9 308 -1.79^{308}-2^1.79^{308}
char 空字元,’\u0000’ 16位 0-65535間的整數
boolean false 8位 true \ false

引用型別
在Java中,引用型別的變數非常類似於C/C++的指標。引用型別指向一個物件,指向物件的變數是引用變數。這些變數在宣告時被指定為一個特定的型別。變數一旦聲明後,型別就不能被改變了。物件、陣列都是引用資料型別。
所有引用型別的預設值都是null。一個引用變數可以用來引用任何與之相容的型別。
記憶體中的儲存方式
基本資料型別
java的基本資料型別儲存在棧中,基本資料型別變數定義是通過諸如int a = 3;long b = 255L;的形式來定義的。如int a = 3;這裡的a是一個指向int型別的引用,指向3這個字面值。這些字面值的資料,由於大小可知,生存期可知(這些字面值定義在某個程式塊裡面,程式塊退出後,欄位值就消失了),出於追求速度的原因,就存在於棧中。
另外,棧有一個很重要的特殊性,就是存在棧中的資料可以共享。比如: 我們同時定義:
int a=3; int b=3;
編譯器先處理int a = 3;首先它會在棧中建立一個變數為a的引用,然後查詢有沒有字面值為3的地址,沒找到,就開闢一個存放3這個字面值的地址,然後將a指向3的地址。接著處理int b = 3;在建立完b這個引用變數後,由於在棧中已經有3這個字面值,便將b直接指向3的地址。這樣,就出現了a與b同時均指向3的情況。
定義完a與b的值後,再令a = 4;那麼,b不會等於4,還是等於3。在編譯器內部,遇到時,它就會重新搜尋棧中是否有4的字面值,如果沒有,重新開闢地址存放4的值;如果已經有了,則直接將a指向這個地址。因此a值的改變不會影響到b的值。
引用資料型別
在java中,建立一個物件包括物件的宣告和例項化兩步,下面用一個例題來說明物件的記憶體模型。假設有類Rectangle定義如下:

public class Rectangle {
double width;
double height;
public Rectangle( double w, double h){
width = w;
height = h;
}
}
(1)宣告物件時的記憶體模型 用Rectangle rect;宣告一個物件rect時,將在棧內存為物件的引用變數rect分配記憶體空間,但Rectangle的值為空,稱rect是一個空物件。空物件不能使用,因為它還沒有引用任何”實體”。 (2)物件例項化時的記憶體模型 當執行rect=new Rectangle(3,5);時,會做兩件事:在堆記憶體中為類的成員變數width,height分配記憶體,並將其初始化為各資料型別的預設值;接著進行顯式初始化(類定義時的初始化值);最後呼叫構造方法,為成員變數賦值。返回堆記憶體中物件的引用(相當於首地址)給引用變數rect,以後就可以通過rect來引用堆記憶體中的物件了。

c)建立多個不同的物件例項

一個類通過使用new運算子可以建立多個不同的物件例項,這些物件例項將在堆中被分配不同的記憶體空間,改變其中一個物件的狀態不會影響其他物件的狀態。例如:

Rectangle r1= new Rectangle(3,5);
Rectangle r2= new Rectangle(4,6);
此時,將在堆記憶體中分別為兩個物件的成員變數 width 、 height 分配記憶體空間,兩個物件在堆記憶體中佔據的空間是互不相同的。如果有:

Rectangle r1=new Rectangle(3,5);
Rectangle r2=r1;
則在堆記憶體中只建立了一個物件例項,在棧記憶體中建立了兩個物件引用,兩個物件引用同時指向一個物件例項,改變了其中一個的值,另一個的值也改變了。
包裝類

基本型別都有對應的包裝類:如int對應Integer類,double對應Double類等,基本型別的定義都是直接在棧中,如果用包裝類來建立物件,就和普通物件一樣了。例如:int i=0;i直接儲存在棧中。Integer i(i此時是物件)= new Integer(5);這樣,i物件資料儲存在堆中,i的引用儲存在棧中,通過棧中的引用來操作物件。
String

String是一個特殊的包裝類資料。可以用以下兩種方式建立:String str = new String(“abc”);String str = “abc”; 第一種建立方式,和普通物件的的建立過程一樣; 第二種建立方式,java內部將此語句轉化為以下幾個步驟: (1)先定義一個名為str的對String類的物件引用變數:String str; (2)在棧中查詢有沒有存放值為”abc”的地址,如果沒有,則開闢一個存放字面值為”abc” 地址,接著建立一個新的String類的物件o,並將o的字串值指向這個地址,而且在棧 這個地址旁邊記下這個引用的物件o。如果已經有了值為”abc”的地址,則查詢物件o,並 回o的地址。 (3)將str指向物件o的地址。 值得注意的是,一般String類中字串值都是直接存值的。但像String str = “abc”;這種 合下,其字串值卻是儲存了一個指向存在棧中資料的引用。 為了更好地說明這個問題,我們可以通過以下的幾個程式碼進行驗證。

String str1=“abc”;
String str2=“abc”;
System.out.println(s1==s2);//true
注意,這裡並不用 str1.equals(str2);的方式,因為這將比較兩個字串的值是否相等。==號,根據JDK的說明,只有在兩個引用都指向了同一個物件時才返回真值。而我們在這裡要看的是,str1與str2是否都指向了同一個物件。 我們再接著看以下的程式碼。

String str1= new String(“abc”);
String str2= “abc”;
System.out.println(str1==str2);//false
建立了兩個引用。建立了兩個物件。兩個引用分別指向不同的兩個物件。 以上兩段程式碼說明,只要是用new()來新建物件的,都會在堆中建立,而且其字串是單獨存值的,即使與棧中的資料相同,也不會與棧中的資料共享。

陣列

當定義一個數組,int x[];或int[] x;時,在棧記憶體中建立一個數組引用,通過該引用(即陣列名)來引用陣列。x=new int[3];將在堆記憶體中分配3個儲存 int型資料的空間,堆記憶體的首地址放到棧記憶體中,每個陣列元素被初始化為0。

靜態變數

用static的修飾的變數和方法,實際上是指定了這些變數和方法在記憶體中的”固定位置”-static storage,可以理解為所有例項物件共有的記憶體空間。static變數有點類似於C中的全域性變數的概念;靜態表示的是記憶體的共享,就是它的每一個例項都指向同一個記憶體地址。把static拿來,就是告訴JVM它是靜態的,它的引用(含間接引用)都是指向同一個位置,在那個地方,你把它改了,它就不會變成原樣,你把它清理了,它就不會回來了。

那靜態變數與方法是在什麼時候初始化的呢?對於兩種不同的類屬性,static屬性與instance屬性,初始化的時機是不同的。instance屬性在建立例項的時候初始化,static屬性在類載入,也就是第一次用到這個類的時候初始化,對於後來的例項的建立,不再次進行初始化。

我們常可看到類似以下的例子來說明這個問題:

class Student{
static int numberOfStudents =0;
Student()
{
numberOfStudents ++;
}
}
每一次建立一個新的Student例項時,成員numberOfStudents都會不斷的遞增,並且所有的Student例項都訪問同一個numberOfStudents變數,實際上intnumberOfStudents變數在記憶體中只儲存在一個位置上