我们经常看到这个句话:
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个值类型变量,
这个过程那个就是自动拆箱了.
/