浅尝Java

Java引用传递解析

2018-01-26  本文已影响11人  SYFHEHE

0.前言

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

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

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

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();

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

3.引用传递和值传递

举例:

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);
    
  }

}

before name: Jack
after name: Lucy

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

before num: 10
after num: 10

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

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中方法参数传递方式是按值传递。

上一篇下一篇

猜你喜欢

热点阅读