我們經常看到這個句話:Java裡存放的容器只能是1個對象。
實際上, java裡的變量可以分為兩種類型, 一種是值類型. 一種是對象類型。
所謂值類型的變量就是內容(值)直接保存在stack(棧區)或靜態區的變量.
例如
int i = 10;
這個i就是值類型變量. 這個變量的內容(值)存放在內存的棧區.
如上圖, 紅色部分就是值類型變量i所占的內存, 共4個字節.
在java中, 一共有八種值類型. 它們分別是
byte, short, int, long, float, double, char, boolean
可以見這些值類型變量的值要麼是數字, 要麼就是字符(char)
也就是說, 這8中類型之類的變量都是對象類型.
所謂對象類型變量的內容存(成員的值)放在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是c/c++ 發展而來的.
我們回憶一下C語言對於數組的特性.
1. 數組在連續的一塊內存空間內存儲
2. 數組內的元素必須是相同類型的.
例如 int[] 數組只存放int 類型元素, char[] 數組只存放char類型元素.
為什麼呢.
原因很簡單, 因為在c語言數組連續的內存中,
如果要判斷數組內的其中1個地址內存是屬於第幾個元素的,
則:
1. 知道數組第1塊內存的地址.
2. 計算當前地址與第1塊內存地址的距離
3. 知道每1個元素所占的內存長度.
4.利用內存除以單個元素內存長度則可以求出當前內存屬於第幾個元素.
也就是說, 數組內的每個元素所占的內存必須是一樣的. 這樣才可以求出數組的某個地方屬於第幾個元素.
而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裡的容器只存放對象類型元素, 但是實際上是存放對象內容的頭部地址.
但是實際上, 我們往往在容器裡直接添加值類型變量.
例如下面的代碼是合法的:
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"? 不是跟上面所說的矛盾嗎?
本文在上面提過, java只有8中值類型.
而"Jack" 是1個字符串常量, 它的內容保存在static區, 它是1個對象而不是值類型.
其實對象類型和值類型的最大區別就是, 對象類型具有成員.
可以通過 ".屬性名" 或 ".方法名()"來調用對象的屬性or方法.
下面的代碼就是合法的, 它輸出了1個字符串常量對象的長度:
System.out.printf("%d\n","Jack".length());
雖然我們執行了代碼
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容器只存放對象的原則.
這個也很簡單, 就是基於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個值類型變量,
這個過程那個就是自動拆箱了.