值与引用
值类型
值类型默认存放在栈中,如一些原始数据类型的局部变量和对象的引用(String, 数组)不存放对象内容
引用类型
引用类型存放在堆中,准确说堆中存放的是 new 创建的对象,而指向对象的引用是存放在栈中。
特殊情况
字符串是一个特殊包装类,其引用是存放在栈里的,而对象内容必须根据创建方式不同决定(常量池和堆)。有的是编译期就已经创建好,存放在字符串常量池中,而有的是运行时才被创建,使用new关键字,存放在堆中。
栈和堆
- 栈使用一级缓存,由系统自动分配,容量较小,调用完立刻释放,所以用于存储容量小的内容(比如基本数据类型的局部变量和引用)
- 堆使用二级缓存,由垃圾回收器回收,并不是一成为孤儿对象就会被回收,所以可以存放容量大点的内容。
值传递和引用传递
- 按值调用(call by value)表示方法接收的是调用者提供的值
// 方法不能修改基本类型的参数
private static void swap(int a, int b){
int temp = a;
a = b;
b = temp;
}
public static void main(String[] args) {
int a = 0;
int b = 1;
swap(a,b);
System.out.println("a:"+a+"; b:"+b);
int temp = a;
a = b;
b = temp;
System.out.println("a:"+a+"; b:"+b);
}
结果是:
a:0; b:1
a:1; b:0
基本数据类型无法修改,那么 String 不是基本数据类型能不能通过方法实现交换呢?
private static void swap(String a, String b){
String temp = a;
a = b;
b = temp;
}
public static void main(String[] args) {
String a = "0";
String b = "1";
swap(a,b);
System.out.println("a:"+a+"; b:"+b);
String temp = a;
a = b;
b = temp;
System.out.println("a:"+a+"; b:"+b);
}
结果仍然是:
a:0; b:1
a:1; b:0
这是为什么呢?
原因很简单,我们打开源码看看 String 类型
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
}
String 被 final 修饰,同时 value[] 也被 final 修饰,所以不可以修改 String 对象。
那么新的问题来了?为什么我们日常操作的时候经常改变 String 的值有是怎么实现的呢?很简单看源码
public String substring(int beginIndex) {
if (beginIndex < 0) {
throw new StringIndexOutOfBoundsException(beginIndex);
}
int subLen = value.length - beginIndex;
if (subLen < 0) {
throw new StringIndexOutOfBoundsException(subLen);
}
// 正常情况下,可以看出 subString 的结果是重新 new 了一个 String 对象返回,与原有的 String 对象不同
return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}
相信通过上面这些例子,你已经简单认识到了什么是 call by value 了,也对 String 这个有了初步认识,接下来我们介绍引用调用
- 按引用调用(call by reference)表示方法接收的是调用者提供的变量地址
private static void swap(int[] a, int x, int y){
int temp = a[x];
a[x] = a[y];
a[y] = temp;
}
public static void main(String[] args) {
int[] a = {0,1};
// 调换第 0 和第 1 的位置
swap(a,0,1);
System.out.println("a[0]:"+a[0]+"; a[1]:"+a[1]);
}
结果显然是:a[0]:1; a[1]:0
这里扩展一一下关于数组的小知识,数组为什么从 0 开始计数
数组用连续的内存空间存储一组具有相同类型的线性数据结构。
同时数组有个最大的杀手锏也就是 按照下标随机访问。其实原理很简单,我们把首个数组对象的地址记录下来,比如 int[] a = {0,1}
那么首地址也就是 a[0], 所以 a[1] = a[0]+ 1*size;
更便于公式计算。当然这只是一种说法,更多的是因为约定俗成,大家都从 0 开始了,行有行规自然都从 0 开始了。
Java 对对象采用的不是引用传递,实际上是按值传递
一个方法不能修改一个基本类型的参数,而对象引用作为参数就不同。方法得到的对象引用的拷贝,对象引用及其他拷贝同时引用同一个对象。
所以严格意义上讲 Java 都是按值传递,不同的是对象传入的是引用,引用在调用方法前后都没有发生变化。
如果觉得我的文章不错的话,可以关注一下我的微信公众号 搜索“ Fenmu”,或者扫一扫下面的二维码