浅谈Java值传递和引用传递

值与引用

值类型

值类型默认存放在栈中,如一些原始数据类型的局部变量和对象的引用(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”,或者扫一扫下面的二维码

Fenmu

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值