19.java基础-值传递

★JAVA是值传递

值传递:会创建副本

引用传递:不会创建副本

值传递和引用传递的误区

错误理解一:值传递和引用传递,区分的条件是传递的内容,如果是个值,就是值传递。如果是个引用,就是引用传递。

错误理解二:Java是引用传递。

错误理解三:传递的参数如果是普通类型,那就是值传递,如果是对象,那就是引用传递。

我们都知道,在Java中定义方法的时候是可以定义参数的。比如Java中的main方法:

public static void main(String[] args)

这里面的args就是参数。参数在程序语言中分为形式参数和实际参数。

形式参数:是在定义函数名和函数体的时候使用的参数,目的是用来接收调用该函数时传入的参数。

实际参数:在调用有参函数时,主调函数和被调函数之间有数据传递关系。

在主调函数中调用一个函数时,函数名后面括号中的参数称为“实际参数”。

```java public static void main(String[] args) { ParamTest pt = new ParamTest(); String parameter = "Hollis"; //实际参数为 parameter pt.sout(parameter); }

//形式参数为 name public void sout(String name) { System.out.println(name); } ```

实际参数是调用有参方法的时候真正传递的内容,而形式参数是用于接收实参内容的参数。

值传递(pass by value)是指在调用函数时将实际参数parameter复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。

**引用传递(pass by reference)是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。**

img

值传递:你有一把钥匙,当你的朋友想要去你家的时候,你复刻了一把新钥匙给他,自己的还在自己手里,这就是值传递。这种情况下,他对这把新钥匙做什么都不会影响你手里的这把钥匙。

引用传递你有一把钥匙,当你的朋友想要去你家的时候,如果你直接把你的钥匙给他了,这就是引用传递。这种情况下,如果他对这把钥匙做了什么事情,比如他在钥匙上刻下了自己名字,那么这把钥匙还给你的时候,你自己的钥匙上也会多出他刻的名字。

钥匙就是栈内地址,房子就是堆中的对象。

但是,不管上面那种情况,你的朋友拿着你给他的钥匙,进到你的家里,把你家的电视砸了。

那你说你会不会受到影响?

而我们在pass方法中,改变user对象的name属性的值的时候,不就是在“砸电视”么。

什么是形参?什么是实参?

```java package base03_值传递;

public class ValueTransfer {

private String name;
private int age;

public ValueTransfer(String name, int age) {
    this.name = name;
    this.age = age;
}

@Override
public String toString() {
    return "ValueTransfer{" +
            "name='" + name + '\'' +
            ", age=" + age +
            '}';
}

public static void main(String[] args) {
    String name1 = "a";
    pass(name1);
    System.out.println(name2);
    ValueTransfer v = new ValueTransfer("a", 1);
    pass(v);
    System.out.println(v);
    ValueTransfer v2 = new ValueTransfer("a", 1);
    pass2(v2);
    System.out.println(v2);
}


public static void pass(String name2) {
    //相当于 new String("b");
    //这个可以通过pass2验证
    //和这个方法是同样的结果,不会影响实际参数。
    //然后再把这个新创建的字符串的引用交给name。
    //这个时候 main方法中name字符串指向的是 a
    //这个时候 main方法中name字符串指向的是 b
    name2 = "b";
    //方法执行完毕name被回收
}

public static void pass(ValueTransfer v) {
    v.name = "b";
    v.age = 2;
}

public static void pass2(ValueTransfer v) {
    v = new ValueTransfer("c", 3);
     v.age=4;
}

} /* 结果: a ValueTransfer{name='b', age=2} ValueTransfer{name='a', age=1} */

/* 如果是值传递 假设name2的地址值是 0x123456 假设name1的地址值是 0x456789 那么形参和实参的地址值是不是指向了同一个对象? 答案:是,因为形参是实参地址的拷贝 形参和实参的地址值可能不一样,但是指向的对象是同1个。

对于方法pass(String name2) String类的存储是通过final修饰的char[]数组来存放结果的,不可更改。一旦定义就是最终形态,任何试图改变String值的操作都只能重新开辟地址。 故当外部一个String类型的引用name1传递到方法内部时,只是把“外部String实例对象”的引用传递给了方法参数变量name2,使得外部String类型变量name1和方法参数变量name2都是实际char[]数组的引用而已。 当我们在方法体中改变name2时,因为char[]数组是不可变的,故每次修改都会导致创建一个新的String实例对象,而方法体中的方法参数name2就会指向这个新创建的String实例对象,而非指向原来外部的String实例对象了。故从方法执行前到方法执行后,外部String类型的引用name1始终指向原String实例对象。

但是对于方法pass(ValueTransfer v),对象的引用也有2个,同时指向了同一个内存地址的堆内存里的对象,当堆内存中的数据发生改变, 也就意味着这2个引用指向的堆内存里的对象发生了改变。

但是对于方法pass2(ValueTransfer v),对象的引用也有2个,同时指向了同一个内存地址的堆内存里的对象,当执行代码: v = new ValueTransfer("c", 3); 意味着这2个引用指向的堆内存里的对象已经不是同1个对象了。 所以即使我们执行 v.age=4; 也不会影响到外部的实际参数对象。

*/ ```

```java package study;

public class User { String gender; String name;

public void setGender(String gender) {
    this.gender = gender;
}

public String getGender() {
    return gender;
}

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

public String getName() {
    return name;
}

@Override
public String toString() {
    return "User{" +
            "gender='" + gender + '\'' +
            ", name='" + name + '\'' +
            '}';
}

}

```

```java package study;

/* * 值传递(pass by value)是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。 * 引用传递(pass by reference)是指在调用函数时将实际参数的地址直接传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。 */ public class 值传递还是引用传递 { public static void main(String[] args) { 值传递还是引用传递 pt = new 值传递还是引用传递();

System.out.println(" ------------基本类型------------------ ");
    int i =20;
    System.out.println("print in main , i is " + i);
    pt.pass(i);
    System.out.println("print in main , i is " + i);

    System.out.println(" -------------引用类型1----------------- ");
    String name = "Hollis";
    System.out.println("print in main , name is " + name);
    pt.pass(name);
    System.out.println("print in main , name is " + name);

    System.out.println(" --------------引用类型2---------------- ");
    User hollis = new User();
    hollis.setName("Hollis");
    hollis.setGender("Male");
    System.out.println("print in main , hollis is " + hollis);
    pt.pass(hollis);
    System.out.println("print in main , hollis is " + hollis);


    System.out.println(" --------------引用类型3---------------- ");
    User hollis2 = new User();
    hollis2.setName("Hollis");
    hollis2.setGender("Male");
    System.out.println("print in main , tyrant is " + hollis2);
    pt.passAndNew(hollis2);
    System.out.println("print in main , tyrant is " + hollis2);

    System.out.println(" --------------引用类型4---------------- ");
    User hollis3 = new User();
    hollis3.setName("Hollis");
    hollis3.setGender("Male");
    System.out.println("print in main , tyrant is " + hollis3);
    pt.pass(hollis3);
    System.out.println("print in main , tyrant is " + hollis3);
}

public void pass(int j) {
    j=100;
    System.out.println("print in pass , j is " + j);
}

public void pass(String name) {
    //因为String是不可变对象
    //这里相当于new了一个String赋值给了形参
    //形参和实参指向的不是一个对象
    //修改不会影响实参属性

    //可以理解为name这个形参 和 实参指向了同1个对象 a
    //但是给name赋值的时候 ,由于String是不可变对象
    //因此 name = ("hollischuang"); 相当于  name = new String("hollischuang");
    //此时 name 相当于指向了 1个新对象 b
    name = ("hollischuang");
    System.out.println("print in pass , name is " + name);
}



public void passAndNew(User user) {
    //这个和String一样
    //形参 实参指向的不是同1个对象的引用
    //不会影响实参的值
    user = new User();
    user.setName("hollischuang");
    user.setGender("female");
    System.out.println("print in pass , user is " + user);
}

public void pass(User user) {
    //形参 实参指向1个对象的引用
    //修改会影响实参属性
    user.setName("hollischuang");
    System.out.println("print in pass , user is " + user);
}

}

```

java ------------基本类型------------------ print in main , i is 20 print in pass , j is 100 print in main , i is 20 -------------引用类型1----------------- print in main , name is Hollis print in pass , name is hollischuang print in main , name is Hollis --------------引用类型2---------------- print in main , hollis is User{gender='Male', name='Hollis'} print in pass , user is User{gender='Male', name='hollischuang'} print in main , hollis is User{gender='Male', name='hollischuang'} --------------引用类型3---------------- print in main , tyrant is User{gender='Male', name='Hollis'} print in pass , user is User{gender='female', name='hollischuang'} print in main , tyrant is User{gender='Male', name='Hollis'} --------------引用类型4---------------- print in main , tyrant is User{gender='Male', name='Hollis'} print in pass , user is User{gender='Male', name='hollischuang'} print in main , tyrant is User{gender='Male', name='hollischuang'}

以引用类型2为例

img

在参数传递的过程中,实际参数的地址0X1213456被拷贝给了形参,在这个方法中,并没有对形参本身进行修改,而是修改的形参持有的地址中存储的内容。即堆内存中的内容。

所以,值传递和引用传递的区别并不是传递的内容。而是实参到底有没有被复制一份给形参。在判断实参内容有没有受影响的时候,要看传的的是什么,如果你传递的是个地址,那么就看这个地址的变化会不会有影响,而不是看地址指向的对象的变化。就像钥匙和房子的关系。钥匙就是栈内地址,房子就是堆中的对象。

以引用类型1为例

既然这样,为啥上面同样是传递对象,引用类型1和引用类型2的结果不一样呢?即传递的String对象和User对象的表现结果不一样呢?

一开始形参和实参指向的是同一个对象。

String name = "Hollis";

在调用方法时,因为String是不可变对象

name = ("hollischuang");

这里相当于new了一个String赋值给了形参

这时形参和实参指向的不是一个对象,所以实参对象的值还是Hollis

我们在引用类型1中的pass方法中使用name = "hollischuang";

试着去更改name的值,阴差阳错的直接改变了形参name的引用的地址。

因为这段代码,会new一个String,在把引用交给name。

name = "hollischuang";等价于name = new String("hollischuang");

而原来的那个”Hollis”字符串还是由实参持有着的,所以,并没有修改到实际参数的值。

img

以引用类型3为例

img

稍微解释下这张图,当我们在main中创建一个User对象的时候,在堆中开辟一块内存,其中保存了name和gender等数据。然后hollis持有该内存的地址0x123456(图1)。当尝试调用pass方法,并且hollis作为实际参数传递给形式参数user的时候,会把这个地址0x123456交给user,这时,user也指向了这个地址(图2)。然后在pass方法内对参数进行修改的时候,即user = new User();,会重新开辟一块0X456789的内存,赋值给user。后面对user的任何修改都不会改变内存0X123456的内容(图3)。

上面这种传递是什么传递?肯定不是引用传递,如果是引用传递的话,在user=new User()的时候,实际参数的引用也应该改为指向0X456789,但是实际上并没有

通过概念我们也能知道,这里是把实际参数的引用的地址复制了一份,传递给了形式参数。所以上面的参数其实是值传递,把实参对象引用的地址当做值传递给了形式参数。

所以说,Java中其实还是值传递的,只不过对于对象参数,值的内容是对象的引用。

还有一个问题就是等号左边的内容是栈内存中的内容。也就是说 实参和形参指向了同一个内存的地址。

``` 在引用类型1中,String被重新赋值,相当于new String 赋值给了形参。但是实际参数的指针还是指向原来的String。所以经过pass方法不会改变。

在引用类型2中,User的name属性被重新赋值。实参和形参同时指向的是同一个内存的地址。因此影响到了实参的内容。

在引用类型3中,新创建了一个User。实参和形参同时指向的不是同一个内存的地址。因此影响不到实参的内容。 ```

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值