Java中只有按值传递,没有按引用传递

最近在看Java核心技术的时候,遇到以前遇到的一个问题,就是Java除了值传递以外,到底有没有引用传递。网上众说纷纭,我看了具有代表性的10几篇文章,结合书中以及自己的举例,终于得出,Java只有按值传递,没有按引用传递。或者可以说为Java只有副本传递,为什么这么说呢?请看我的论证。

基本数据类型分析

12345678910
public static void main(String[] args) {	/** 基本数据类型 **/    int a = 2;    p.foo(a);  // 传入基本数据类型,是一个值拷贝    System.out.println(a);  // 2}

private void foo(int value) {    value = 100;}

Java中所有的基本数据类型结果都一样,在这里用int做一下实验。
可以看出结果并没有改变a的值,因为Java对基本数据类型的传递,首先为拷贝的值开辟一个内存,让value指向拷贝的那个值,所以这是值传递。下面是内存分析图:

basicType

包装数据类型分析

123456789101112
/** 包装数据类型 **/Integer integer = 2;p.foo(integer); // 包装类作为参数传递时,仍是值传递System.out.println(integer);    // 2

Integer integer1 = new Integer(2);p.foo(integer1);    // 包装类作为参数传递时,仍是值传递System.out.println(integer1);   // 2

private void foo(Integer value) {        value = 100;}

Java中所有的包装数据类型和基本数据类型之间都会完成隐式的互相转换,这也是为什么list可以add基本数据类型的原因。
同样这个例子和基本数据类型的结果是一样的,传递的也是值的拷贝。内存分析和上图一样。

String字符串

1234567891011121314151617181920
/** String **/String b = "mac";p.fooString(b);   // 没有提供改变自身方法的引用类型System.out.println(b);  // mac

String b2 = new String("mac");p.fooString2(b2);System.out.println(b2); // mac

String b3 = "mac";b3.replaceAll("m", "M");System.out.println(b3); // mac

private void fooString(String value) {   value = "windows";}

private void fooString2(String value) {   value.concat(" computer");}

字符串由于其特殊性(字符串是不可变的),实际上方法是对原来的对象进行了一个拷贝,之后的操作都是在拷贝对象里进行的。所以不影响原来对象的值。
b和b2对象都是在fooString方法里面进行了一次拷贝,然后value的操作不影响b和b2的值。
b3也是传递了一个拷贝值到fooString2这个方法,然后concat是对这个拷贝值进行操作。

String

系统对象

12345678910111213141516
/** 系统对象 **/StringBuffer sb = new StringBuffer("iPhone");p.foo(sb);  // 提供了改变自身方法的引用类型,但是方法内部拿到地址的引用指向了新的地址.原来sb指向的地址并没有改变System.out.println(sb); // iPhone

StringBuilder sb2 = new StringBuilder("iPhone");p.foo(sb2); // 提供了引用地址的copy,改变指向地址对象的值.System.out.println(sb2);    // iPhone5s

private void foo(StringBuffer buffer) {    buffer = new StringBuffer("iPad");}

private void foo(StringBuilder builder) {    builder.append("5s");}

其实前面的还好去理解传递是值传递,那么对象这块如何去理解呢?
很多人都在这里认为传递是引用传递,我们来进行一下分析。

object

上图的sb对象指向了对象StringBuffer,它有个值“iPhone”,传入方法后,buffer其实拿到的不是原来sb的引用,只不过是一个副本而已。尽管它也指向iPhone的内存地址。
第一个例子里面,buffer在方法里面重新指向了一个新的StringBuffer对象的内存地址,放着一个值”iPad”,所以原来sb指向的值并没有改变。

object2

第二个例子里面,builder拿到了原来对象的引用拷贝,但是指向的地址不变,所以可以修改内存地址中的对象的属性,并没有新的引用。

object2

所以可以看出来,我们并没有拿到引用去操作内存地址中存在的值,只不过是一个引用的拷贝,如果这个例子说的还不够信服的话,下面的例子应该会让人信服。

自定义对象

123456789101112131415161718192021222324
/** 自定义对象 **/Employee e1 = new Employee("tom", 7000);Employee e2 = new Employee("jerry", 4000);p.swap1(e1, e2);System.out.println(e1);	// tom 7000System.out.println(e2);	// jerry 4000

p.swap2(e1, e2);System.out.println(e1); // caroline 7000System.out.println(e2); // benjamin 4000

private void swap1(Employee x, Employee y) {    Employee temp = x;    x = y;    y = temp;}

private void swap2(Employee x, Employee y) {    Employee temp = x;    x = y;    y = temp;    x.setName("benjamin");    y.setName("caroline");}

Employee实体类:

12345678910111213141516171819202122
package JavaCoreTechnology;

/** * Created by benjamin on 1/14/16. */public class Employee {    private String name;    private int salary;

public Employee(String name, int salary) {        this.name = name;        this.salary = salary;    }

public String toString() {        return this.name + " " + this.salary;    }

public void setName(String name) {        this.name = name;    }}

o4

上图是swap1的交换的实现,这也正说明了为什么输出的值并没有改变。因为原来对象的引用并没有改变,改变的只有这个引用的副本。
这也正说明了为什么swap2是改变的交换的对象的值。

总结:
在Java中并不像c++的那样通过指针操作变量,虽然在Java的最底层仍然使用的是指针,但是Java的开发者已经帮我们封装好了,让我们接触不到而已。基本数据类型的值传递的方式还是和c++的方式一样,是一个值的拷贝。操作的是拷贝的值不会对原来的值造成影响。
而对象的操作则是引用的拷贝,其实也是一个值,是内存地址的值而已。它没有完全把引用传递。所以翻转它的时候不会对原来的对象造成影响,只不过他们指向的都是同一个地址,会改变内存地址中的值罢了。

所以!Java只有按值传递!

时间: 2024-09-10 09:04:34

Java中只有按值传递,没有按引用传递的相关文章

java中数据的传递方式到底是怎样的!

今天早上我了一道有关java的题.主要考点是考java中值得传递方式. 之前我在javaoo里总结的是:基本数据类型中保存的是实际的值,引用数据类型保存的是被引用的内存地址,那么基本数据类型就是按值传递,引用数据类型就是按地址来传递的.(难道我一开始就错了) 题是真样的: 1 public class SendValue{ 2 public String str="6"; 3 public static void main(String[] args) { 4 SendValue sv

Java参数按值传递?按引用传递

有时候在想,java在调用方法时候究竟是按值传递还是按引用传递,之前有人说是基本数据类型按值传递,引用类型按引用传递.一时间,似乎都有道理. 笔者在此不追究字眼上的辨别识字能力,把自己对这个问题的理解阐述一下,笔者不想说这是按值传递还是按引用传递,自己理解就好了吧,毕竟java会用才是王道. 先看一下下面的代码: package shb.java.testmemory;    public class TestMeo {        /**测试基本数据类型以及引用类型参数按值传递       

JavaScript中的值是按值传递还是按引用传递问题探讨_javascript技巧

最近遇到个有趣的问题:"JS中的值是按值传递,还是按引用传递呢?"   在分析这个问题之前,我们需了解什么是按值传递(call by value),什么是按引用传递(call by reference).在计算机科学里,这个部分叫求值策略(Evaluation Strategy).它决定变量之间.函数调用时实参和形参之间值是如何传递的.  按值传递 VS. 按引用传递 按值传递(call by value)是最常用的求值策略:函数的形参是被调用时所传实参的副本.修改形参的值并不会影响实

在java中实现C#语法里的按引用传递参数的方法_java

在C#中,在次函数中调用时改变了其中的数值,主函数中也将发生改变 ref 关键字使参数按引用传递.其效果是,当控制权传递回调用方法时,在方法中对参数所做的任何更改都将反映在该变量中.若要使用 ref 参数,则方法定义和调用方法都必须显式使用 ref 关键字 out 关键字会导致参数通过引用来传递.这与 ref 关键字类似,不同之处在于 ref 要求变量必须在传递之前进行初始化.若要使用 out 参数,方法定义和调用方法都必须显式使用 out 关键字. Java里面不像C#那样,Java只有对象类

java中的 by value or by reference

java中的基础类型直接存储在栈中,复合类型采用引用类型,把引用也存储在栈中,而对应的对象存储在堆中.因此java中把内存分堆内存和栈内存,在函数中定义的一些基本类型或引用都分配栈内存.堆内存用来存放由new创建的对象和数组,或是static(类装载信息).在堆中分配的内存,由jvm的gc管理.程序只能控制引用的生存期,对象的生存期是jvm控制的. 在java应用程序中,当对象的引用是传递方法一个参数时,传递的时该引用的一个副本,(按值传递).而非引用本身,调用方法的对象引用和副本都是指向同一个

java中为什么x的输出为20 而 i 的输出为10

问题描述 java中为什么x的输出为20 而 i 的输出为10 public class Pass { static int j=20; public static void main(String []args){ int i=10; Pass p=new Pass(); p.method(i); System.out.println(i); System.out.println(j); } public void method(int x){ x=x*2; j=j*2; System.out

JS是按值传递还是按引用传递?

原文:JS是按值传递还是按引用传递? 最近遇到个有趣的问题:"JS中的值是按值传递,还是按引用传递呢?" 在分析这个问题之前,我们需了解什么是按值传递(call by value),什么是按引用传递(call by reference).在计算机科学里,这个部分叫求值策略(Evaluation Strategy).它决定变量之间.函数调用时实参和形参之间值是如何传递的. 按值传递 VS. 按引用传递 按值传递(call by value)是最常用的求值策略:函数的形参是被调用时所传实参

JS是按值传递还是按引用传递_javascript技巧

按值传递 VS. 按引用传递 按值传递(call by value)是最常用的求值策略:函数的形参是被调用时所传实参的副本.修改形参的值并不会影响实参.   按引用传递(call by reference)时,函数的形参接收实参的隐式引用,而不再是副本.这意味着函数形参的值如果被修改,实参也会被修改.同时两者指向相同的值.   按引用传递会使函数调用的追踪更加困难,有时也会引起一些微妙的BUG.   按值传递由于每次都需要克隆副本,对一些复杂类型,性能较低.两种传值方式都有各自的问题.   我们

理解Java中的引用传递和值传递

关于Java传参时是引用传递还是值传递,一直是一个讨论比较多的话题,有论坛说Java中只有值传递,也有些地方说引用传递和值传递都存在,比较容易让人迷惑.关于值传递和引用传递其实需要分情况看待,今天学习和分析一下,着急可以先看最后的结论. >>基本类型和引用类型在内存中的保存 Java中数据类型分为两大类,基本类型和对象类型.相应的,变量也有两种类型:基本类型和引用类型. 基本类型的变量保存原始值,即它代表的值就是数值本身: 而引用类型的变量保存引用值,"引用值"指向内存空间