【java按值传递、引用传递理解】

最近看算法这本书,发现很多自己在大学课程中没接触到,所以很多看不懂后百度,也看到很多挺不错的文章,就把他们直接拷贝过来。这里参考了三篇文章

https://www.cnblogs.com/baizhanshi/p/7890481.html

 https://www.cnblogs.com/caiyao/p/4964176.html

https://www.jianshu.com/p/df903886b5a9

https://www.cnblogs.com/SilentCode/p/4858790.html

第一篇

前天在做系统的时候被Java中参数传递问题卡了一下,回头查阅了相关的资料,对参数传递问题有了新的了解和掌握,但是有个问题感觉还是很模糊,就是Java中到底是否只存在值传递,因为在查阅资料时,经常看到有人说Java只有值传递,但有人说既有值传递,也有引用传递,对于两个观点个人觉得应该是站的角度不同而得出两个不同的说法,其实两个说法其中的原理是一样的,只要咱们懂得其中的原理,那么至于叫什么也就无所谓了,下面是我在网上看到的一个帖子,解释的感觉挺全面,就转过来,以供以后学习参考:

       

1:按值传递是什么

指的是在方法调用时,传递的参数是按值的拷贝传递。示例如下:

public class TempTest {  
private void test1(int a){  
//做点事情  
}  
public static void main(String[] args) {  
TempTest t = new TempTest();  
int a = 3;  
t.test1(a);//这里传递的参数a就是按值传递  
}  
}  

按值传递重要特点:传递的是值的拷贝,也就是说传递后就互不相关了。

示例如下:

public class TempTest {  

private void test1(int a){  

a = 5;  

System.out.println("test1方法中的a="+a);  

}  

public static void main(String[] args) {  

TempTest t = new TempTest();  

int a = 3;  

t.test1(a);//传递后,test1方法对变量值的改变不影响这里的a  

System.out.println(”main方法中的a=”+a);  

}  

}  

运行结果是:

test1方法中的a=5  

main方法中的a=3  

2:按引用传递是什么

指的是在方法调用时,传递的参数是按引用进行传递,其实传递的引用的地址,也就是变量所对应的内存空间的地址。

示例如下:

public class TempTest {  

private void test1(A a){  

}  

public static void main(String[] args) {  

TempTest t = new TempTest();  

A a = new A();  

t.test1(a); //这里传递的参数a就是按引用传递  

}  

}  

class A{  

public int age = 0;  

}  

3:按引用传递的重要特点

传递的是值的引用,也就是说传递前和传递后都指向同一个引用(也就是同一个内存空间)。

示例如下:

public class TempTest {  

private void test1(A a){  

a.age = 20;  

System.out.println("test1方法中的age="+a.age);  

}  

public static void main(String[] args) {  

TempTest t = new TempTest();  

A a = new A();  

a.age = 10;  

t.test1(a);  

System.out.println(”main方法中的age=”+a.age);  

}  

}  

class A{  

public int age = 0;  

}  

运行结果如下:

test1方法中的age=20  

main方法中的age=20  

第二篇

通过下面代码解释:

  public class Test {
     public static void main(String[] args ){    
         int var = 1 ;    
         f(var) ;    
          System.out.println(var) ;    
     }
     public static void f(int newVar ){    
         newVar = 2 ;    
    }
 }

执行结果: 

1

分析:

当执行 int var = 1 时,jvm在栈中开辟一块空间存放值---1,同时var变量指向值1所对应的内存空间,也就是var变量也有自己的内存空间,不过它的空间里存放的是值1所对应的内存地址。

当执行到第七行,要将var的值传递进方法f中时,jvm执行的操作是创建一个新的变量newVar,并将var里存放的值(也就是值1的内存地址)复制一份给newVar。

当执行到第八行时,jvm重新开辟值--2所对应的存储空间来存储值2,并修改了newVar里存放的地址值。

方法返回,执行到第五行,输出var所指向的值,当然是值1,因为var中的地址值根本没有变化。

上面示例用的是int,一个基本数据类型,对于引用数据类型同样适用,因为java中没有引用传递,只有值传递。例如自定义的类UserClass:

 class MyClass {
      int var1 ;
  }
  public class Test {
      public static void main(String[] args ){    
          MyClass myClass = new MyClass() ;
          myClass.var1 = 1 ;
          f(myClass) ;    
         System.out.println(myClass.var1) ;    
     }
    public static void f(MyClass newMyClass ){
       myClass.var1 = 100 ;
     }
 }

执行结果:

100

分析:

第六行:在栈中创建一个变量指向在堆中创建的对象。关于堆中对象属性的存储可以理解为每创建一个对象就会在堆中分配一块内存空间给改对象,这块内存空间中记录了该对象属于哪个类,剩下就是存储该对象的属性值。

第七行修改对象属性值:

下面就是关键的第八行值的传递了,jvm会在栈中在开辟一块空间newMyClass,然后把myClass里的内容拷贝进newMyClass,这样在方法f中对newMyClass的修改就是对MyClass所对应内容的修改,因为newMyClass也是指向与myClass同样的一块内存空间。

第十二行中对newMyClass属性的修改同样会影响myClass所指的内容,因为他们俩指的是同一块内存空间。

最后方法返回myClass所指内容同样被修改。

下面说一个比较特殊的类----String,这是java中的一个特殊的引用类型,使用String传递时会发现在方法中修改不会影响到方法外的值,例如下面这段代码:

  public class Test {
      public static void main(String[] args ){    
         String var = "Hello" ;
          f(var) ;
          System.out.println(var) ;
      }
      public static void f(String newVar ){
          newVar = "World" ;
      }
 }

运行结果:

Hello

在分析这段代码之前需要知道String的底层是怎样实现的,其实String是通过char数组来完成其功能,简单的说String就是对char[]的一个封装。还要明白直接“字符串内容”和使用new String(“字符串内容”)效果是一样的,只不过jvm对这两种创建的字符串存储的地方不同而已。对上面这段代码的分析当然还要使用对引用数据传值方法分析。

第三行jvm在静态存储区存放创建的“Hello”字符串,并在栈中开辟var指向Hello的地址(var中存储的值是Hello字符串的地址)。当将var作为参数传递时,jvm会在栈中新创建一个变量newVar,这时newVar是指向"Hello"内存区的,但是在第八行 newVar = “World”却把newVar中存储的“Hello”地址改为一个新创建的“World”String对象的地址,注意,var内存储的地址仍然是“Hello”,所以在方法返回时输出的仍然是Hello。

第三篇

1. 一段有趣但令人困惑的代码

public static void main(String[] args) {
  String x = new String("ab");
  change(x);
  System.out.println(x);
}

public static void change(String x) {
    x = "cd";
}

输出 "ab"

2.经常让人捉摸不透的问题

x 存储了堆中"ab"字符串的引用。因此,当x作为参数传递到change()方法的时候,它仍然堆中的"ab",如下所示:

string-pass-by-reference-.jpeg

因为java是按值传递的,x的值是"ab"的引用。当方法change()被调用的时候,它创建了一个新的字符串对象"cd" ,然后x就指向了"cd" ,如图所示:

这似乎是一个非常合理的解释。他们很清楚,java是按值传递的,但是这里出了什么问题?

3.这段代码到底做了什么?

上面的解释有几处错误。为了更容易理解,我们最好简单的过一下整个流程。

当字符串"cd" 被创建时,java会分配储存字符串所需要的内存量。然后,对象被分配给了变量x,实际上是将对象的引用分配给了变量x。这个引用是对象储存的内存地址。

变量x包含了一个指向字符串对象的引用,x并不是字符串对象本身。它是一个储存了字符串对象'ab'引用的变量。

java是按值传递的。当x被传递给change()方法时,实际上是x的值(一个引用)的一个副本。方法change()被调用后,会创建另一个对象"cd",它有着一个不同的引用。方法内的局部变量x的值变成了"cd"的引用。这里改变的是方法内的局部变量的引用值,而不是改不了原先引用的字符串"ab"。

看图:

4.错误的解释:

从第一个代码片段引发的问题与字符串不可变性没有任何关系。即使String被StringBuilder替换,结果仍然是一样的。关键点是变量存储的是对象引用,而不是对象本身!

5.解决这个问题

如果我们真的需要去改变对象的值,首先,对象应该是可变的,比如StringBuilder。其次,我们需要确保没有创建新对象并将其分配给参数变量,因为Java只是按值传递。

public static void main(String[] args) {
  StringBuilder x = new StringBuilder("ab");
  change(x);
  System.out.println(x);
}

public static void change(StringBuilder x) {
  x.delete(0, 2).append("cd");
}

猜你喜欢

转载自blog.csdn.net/kevin_nan/article/details/86597625