1. 程式人生 > >Java裡的容器存放的元素必須是1個物件.

Java裡的容器存放的元素必須是1個物件.

我們經常看到這個句話:

Java裡存放的容器只能是1個物件.

一. 值型別和物件型別.

實際上, java裡的變數可以分為兩種型別, 一種是值型別. 一種是物件型別.

1.1 值型別變數

所謂值型別的變數就是內容(值)直接儲存在stack(棧區)或靜態區的變數.

例如

 int i = 10; 


這個i就是值型別變數.   這個變數的內容(值)存放在記憶體的棧區.

如上圖, 紅色部分就是值型別變數i所佔的記憶體, 共4個位元組.

在java中, 一共有八種值型別. 它們分別是

byte, short, int, long, float, double, char, boolean

可以見這些值型別變數的值要麼是數字, 要麼就是字元(char)

也就是說, 這8中型別之類的變數都是物件型別.

1.2 物件型別變數

所謂物件型別變數的內容存(成員的值)放在heap(堆區)(String物件除外), 然後在stack區或static區儲存這個物件內容heap區記憶體的地址.

假如Student是1個類, 它有兩個成員id 和 age.

那麼例項化1個物件

Student s = new Student(1,20);

上面的s就是1個物件型別的變數.

它的資料是這樣存放在記憶體中的.

1.它成員Id 和 name 的值會存放在heap區.

2.變數s本身會存放1個地址, 這個地址就是它的成員在heap區記憶體的頭部地址.  

如下圖:

紫色的部分才是物件型變數s的真正內容,

而變數s本身存放的是其真正內容在heap區記憶體的地址.

二. 為什麼java容器只存放物件型別.

2.1 c語言陣列只能存放單種元素


我們知道, Java是c/c++ 發展而來的.

我們回憶一下c語言對於陣列的特性.

1. 陣列在連續的一塊記憶體空間記憶體儲

2. 陣列內的元素必須是相同型別的.

例如 int[] 陣列只存放int 型別元素,  char[] 陣列只存放char型別元素.

為什麼呢.

原因很簡單, 因為在c語言陣列連續的記憶體中,

如果要判斷陣列內的其中1個地址記憶體是屬於第幾個元素的,

則:

1. 知道陣列第1塊記憶體的地址.

2. 計算當前地址與第1塊記憶體地址的距離

3. 知道每1個元素所佔的記憶體長度.

4.利用記憶體除以單個元素記憶體長度則可以求出當前記憶體屬於第幾個元素.

也就是說, 陣列內的每個元素所佔的記憶體必須是一樣的.  這樣才可以求出陣列的某個地方屬於第幾個元素.

2.2 Java的陣列可以存放多種屬於不同類的物件

而Java裡的陣列為何能存放多種種類的元素呢?

例如下面程式碼是合法的:

ArrayLIst Arr = new ArrayList(0;
Arr.add(new Student(1,20);
Arr.add(new School(1,"No.5 school","address");
Arr.add(new City(1,"Canton","Guangdong","China");

上面程式碼在1個數組容器內添加了3個物件.

這3個物件分別屬於Student, School 和City類,  這3個物件明顯不是屬於同1個類,而且所佔的記憶體大小很明顯是不同的.

但是能存放在同1個數組容器中.

原因就是

上面三個物件的內容(成員的值)所佔的記憶體是不同的,  這些記憶體都存放在Heap區內.

但是, 陣列容器並不是直接存放上面3個物件的內容, 而只是儲存這3個物件內容在Heap區的頭部地址.

而無論這個三個物件所佔的heap區記憶體相差多大, 它們的頭部地址所佔的長度都是一樣的4byte(32位系統)

所以實際上陣列內的元素都是同1種類型, 就是記憶體地址型別, 它們的長度都一樣啊.

如下圖:


上圖3塊紅色記憶體就是連續的, 它們都屬於存放在陣列Arr內.

所以我們講: Java裡的容器只存放物件型別元素, 但是實際上是存放物件內容的頭部地址.

三. 自動裝箱(boxing) 和 自動拆箱(unboxing)

但是實際上, 我們往往在容器裡直接新增值型別變數.

例如下面的程式碼是合法的:

ArrayList arr = new ArrayList();
arr.add(123);
arr.add("Jack");
int i;
String s;
i = arr.get(i);
s = arr.get(2);
System.out.printf("%d, %s\n",i,s);

上面我們直接往1個ArrayList容器添加了值型別物件123 和 "Jack"? 不是跟上面所說的矛盾嗎?

3.1 "Jack" 是1個字串物件型別, 而不是值型別.

本文在上面提過, java只有8中值型別.

而"Jack" 是1個字串常量,  它的內容儲存在static區,  它是1個物件而不是值型別.

其實物件型別和值型別的最大區別就是, 物件型別具有成員.

可以通過 ".屬性名" 或 ".方法名()"來呼叫物件的屬性or方法.

下面的程式碼就是合法的, 它輸出了1個字串常量物件的長度:

System.out.printf("%d\n","Jack".length());


3.2 什麼是裝箱(boxing)

雖然我們執行了程式碼

arr.add(123);

但是如果陣列內直接存放數值123的值就違反了java容器只存放物件的原則了.

實際上, java裡, 對於值型別來講, 都有1個對應的物件型別.

例如 int是1個整形值型別,  而Interger是1個整形類

Interger裡有1個成員屬性, 用於存放1個整形值型別的值.

還包含很多對整形值操作的方法.

而int 整形型別本身是沒有任何成員方法的.

所以有時我們會用1個Integer物件將1個int變數包起來.

例如

int i = 123;
Integer io =  new Integer(int i);
System.out.printf("%d\n", io.intValue());

上面的i就是1個整形值型別變數

而io就是1個Integer物件.

這個過程就叫做裝箱.

對於容器來講,

如果將1個值型別直接放入容器, java會將其裝箱後再放入容器.

這個過程就叫做自動裝箱.

所以下面兩句程式碼是等價的.

arr.add(123);
arr.add(new Integer(123));

所以實際上並沒有違反java容器只存放物件的原則.

3.3 什麼是拆箱

這個也很簡單, 就是基於1個物件型別返回1個值型別就是拆箱了.

例如:

integer io = new Integer(123);
int i = io.intValue()'
System.out.printf("%d\n", i);

上面程式碼中我們用值型別變數i 獲得 Integer物件io所存放的值, 這個過程就是拆箱

如果我們利用容器的get()方法來返回1個值型別.

例如:

int i = arr.get(1);

我們知道容器裡存放的都是物件, 但是java會先將其拆箱再返回給1個值型別變數,

這個過程那個就是自動拆箱了.



/