Java引用传递解析

0.前言

因为之前有同学刚好问我一个关于Java引用传递的问题,为什么将ArrayList中拿到的一个元素,赋给一个对象,对象修改了属性,ArrayList中的对应的元素属性也会修改,这就牵扯出Java中一直讨论的一个问题:Java传参时是引用传递还是值传递。 在这篇文章里我们主要会对一下几个问题进行讨论。

  • 1.基本类型和引用类型在内存中的保存
  • 2.变量的基本类型和引用类型的区别
  • 3.引用传递和值传递

1.基本类型和引用类型在内存中的保存

Java中数据类型分为两大类,

  • 基本类型:`byte, short, int, long, char, float, double, Boolean,不过,Java字节码中还有额外的两种基础数据类型,分别是reference type与returnAddress type;
  • 对象类型:类,接口和数组。
    相应的,变量也有两种类型:
  • 基本类型:基本类型的变量保存原始值,即它代表的值就是数值本身;
  • 引用类型:引用类型的变量保存引用值,"引用值"指向内存空间的地址,代表了某个对象的引用,而不是对象本身。

2.变量的基本类型和引用类型的区别

2.1 基本类型的初始化

基本数据类型是存储在栈内存中,当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的作用域后,java会自动释放掉为该变量分配的内存空间,该内存空间可以立刻被另作他用。

举个例子:

int a = 3;

首先它会在栈中创建一个变量为a的引用,然后查找有没有字面值为3的地址,没找到,就开辟一个存放3这个字面值的地址,然后将a指向3的地址。

题外话:不是所有的基本数据类型都存在java栈里,为了反驳观点" Java的基本数据类型都是存储在栈的 ",我们也可以随便举出一个反例,例如:

int[] array=new int[]{1,2};

由于new了一个对象,所以new int[]{1,2}这个对象时存储在堆中的,也就是说1,2这两个基本数据类型是存储在堆中,这也就很有效的反驳了基本数据类型一定是存储在栈中~~

2.2 引用类型的初始化

image.png

上图表示的是在JVM中,Java对象的访问方式。从上图可以看出,对象的引用变量是存储在栈内存中的。我们通过一个例子来分析下这个过程。

Object obj = new Object();
  • 第一步:new Object() :JVM将在GC堆中开辟一定大小的空间用以存放此Object,此Object是引用数据类型(注意不是reference类型,reference类型保存在Java栈中的本地变量表里)。另外,在JAVA堆中还必须包含能查找到此对象类型数据(如对象类型、父类、实现接口、方法等)的地址信息,这些类型数据存储在方法区中。
  • 第二步: Object obj:JVM将在虚拟机栈中此线程栈开辟出一个slot用以存放obj,注意此处obj为reference type。(slot可以理解为用以存放基础数据类型的线程私有的空间)
  • 第三步:=:reference type 的obj被赋值为先前new Object() 指令所产生的引用数据类型的一个对象。

分析完不同类型的变量在内存中的储存形式之后,我们就可以来分析下到底什么时候是值传递,什么时候是引用传递。

3.引用传递和值传递

  • 值传递:
    方法调用时,实际参数把它的值传递给对应的形式参数,函数接收的是原始值的一个copy,此时内存中存在两个相等的基本类型,即实际参数和形式参数,后面方法中的操作都是对形参这个值的修改,不影响实际参数的值。

  • 引用传递:
    也称为传地址。方法调用时,实际参数的引用(地址,而不是参数的值)被传递给方法中相对应的形式参数,函数接收的是原始值的内存地址;在方法执行中,形参和实参内容相同,指向同一块内存地址,方法执行中对引用的操作将会影响到实际对象

举例:

public class Demo0 {
  
  private User user = new User("Jack", 1l);
  
  private int num;
  
  private String string ="";

  //提供改变自身方法的引用类型
  void fun0(User user){
    user.setName("Lucy");
  }

  //基本类型作为参数传递时,是传递值的拷贝,无论你怎么改变这个拷贝,原值是不会改变的
  void fun1(int value) {
    value = 100;
  }
  
  //没有提供改变自身方法的引用类型
  void fun2(String text) {
    text = "windows";
  }

  //在Java中对象作为参数传递时,是把对象在内存中的地址拷贝了一份传给了参数。
  public static void main(String[] args) {
    Demo0 demo = new Demo0();
    demo.num = 10;
    demo.string = "Linux";
    
    System.out.println("before name: " + demo.user.getName());
    demo.fun0(demo.user);
    System.out.println("after name: " + demo.user.getName());

    System.out.println("before num: " + demo.num);
    demo.fun1(demo.num);
    System.out.println("after num: " + demo.num);
    
    System.out.println("before string: " + demo.string);
    demo.fun2(demo.string);
    System.out.println("after string: " + demo.string);
    
  }

}

  • 函数fun0运行结果:
before name: Jack
after name: Lucy

user的name属性被修改,原因是:fun0调用时,函数接收的是原始值的内存地址;方法执行setName操作,修改了堆中改内存地址上的User对象的属性。通过这个例子我们可以知道:引用类型传递的是对象的引用,形参和实参指向同一个内存地址(同一个对象),所以对参数的修改会影响到实际的对象。

  • 函数fun1运行结果:
before num: 10
after num: 10

很显然输出的 是10,10。传递的是值得一份拷贝,这份拷贝与原来的值没什么关系。基本数据类型传值,对形参的修改不会影响实参。

  • 函fun2运行结果:
before string: Linux
after string: Linux

这就很有意思了,为什么String是引用类型,为什么不会修改其值呢?原因是:String,以及Integer、Double等几个基本类型包装类,它们都是immutable类型,因为没有提供自身修改的函数,每次操作都是新生成一个对象,所以要特殊对待,可以认为是和基本数据类型相似,传值操作。

现在来看一开始提出的那个问题:为什么从ArrayList中get第一个元素,赋给一个对象,对象修改了属性,ArrayList中的元素属性也会修改?

public class Demo2 {
  public static void main(String[] args) {

    List<User> list = new ArrayList<User>();
    User user1 = new User("name1", 1111l);
    User user2 = new User("name2", 2222l);
    User user3 = new User("name3", 3333l);
    list.add(user1);
    list.add(user2);
    list.add(user3);
    
    System.out.println("--------------Before---------------");
    for (User user : list) {
      System.out.println("name = " + user.getName());
    }
    
    User userTemp = list.get(0);
    userTemp.setName("xxxxxx");
    
    System.out.println("--------------After---------------");
    for (User user : list) {
      System.out.println("name = " + user.getName());
    }
    
  }
}

输出:

--------------Before---------------
name = name1
name = name2
name = name3
--------------After---------------
name = xxxxxx
name = name2
name = name3

原因很简单:userTemp 和list的第一个对象都指向同一块内存地址,方法执行中对引用的操作将会影响到实际对象

4.总结

java中方法参数传递方式是按值传递。

  • 如果参数是基本类型,传递的是基本类型的字面量值的拷贝。
  • 如果参数是引用类型,传递的是该参量所引用的对象在堆中地址值的拷贝
  • String,以及Integer、Double等几个基本类型包装类,它们都是immutable类型,因为没有提供自身修改的函数,每次操作都是新生成一个对象,所以要特殊对待,可以认为是和基本数据类型相似,传值操作。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 从三月份找实习到现在,面了一些公司,挂了不少,但最终还是拿到小米、百度、阿里、京东、新浪、CVTE、乐视家的研发岗...
    时芥蓝阅读 42,854评论 11 349
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 34,811评论 18 399
  • JVM内存模型Java虚拟机(Java Virtual Machine=JVM)的内存空间分为五个部分,分别是: ...
    光剑书架上的书阅读 2,782评论 2 26
  • 机缘巧合的机会,我参加了趁早的百日理财训练营。作为一只理财小白,从没想过从中能收获什么,只是默默地跟着组织...
    solarxue阅读 954评论 0 0
  • 在市场经济环境下,任何理论、方法、技术、产品或服务的发扬光大,都离不开“营销”。“营销”的基本套路为:传播信息——...
    木青观察阅读 587评论 0 0

友情链接更多精彩内容