04. Java学习补充 -- 值传递和引用传递、对象的拷贝

04. Java学习补充 – 值传递和引用传递、对象的拷贝



前言

值传递:对形参的修改不会影响到实参
引用传递:对实参的修改会影响到实参


一、值传递与引用传递

在了解值传递与引用传递前需要先明白两个概念:实参和形参。

实参和形参

  • 实参:传递给方法的实际参数,在方法外面
  • 形参:在方法定义时声明的参数,在方法里面
// 例子:
public class PassByValue {
    public static void main(String[] args) {
        int number = 10;	// 实参
        change(number);		
    }

    public static void change(int number){
        System.out.println(number);		// 形参
    }
}

接下来看两个例子:

// 例子1:
public class PassByValue {
    public static void main(String[] args) {
        int number = 10;
        change(number);		// 11
        System.out.println(number);     // 10
    }

    public static void change(int number){
        number += 1;
        System.out.println(number);
    }
}
// 例子2:
public class PassByReference {
    public static void main(String[] args) {
        Cat cat = new Cat();

        cat.setName("三花");
        cat.setColor("黑白黄");

        changeCat(cat);     // Cat{name='大橘', color='橘色'}
        System.out.println(cat);    // Cat{name='大橘', color='橘色'}
    }

    public static void changeCat(Cat cat){
        cat.setName("大橘");
        cat.setColor("橘色");

        System.out.println(cat);
    }
}

public class Cat {
    private String name;
    private String color;

    public String getName() {
        return name;
    }

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

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

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

两个结果的差异就涉及到了值传递和引用传递。

用最通俗易懂的说法,值传递和引用传递的区别就是,形参的修改会不会影响到实参。

值的传递(基本数据的传递 包括字符串)。它传递的并不是值的本身。传递的只是值的副本(拷贝)。
将这个副本传递给形式参数之后,修改的只是值的副本内容,并不是值的本身。
引用传递传递的是引用类型数据的内存地址。将内存地址传递给changeCar方法的形式参数里面之后,changeCar方法内部修改的数据,其实就是对当前内存地址指向的对象进行修改。所以修改之后,数据会发生变化。

再来看第一个例子。
在Java中基本数据类型和局部变量存放在栈中,在传参的时候将数据创建了一个副本传到了方法中,方法在修改的时候只修改了副本中的内容,并不是值的本身。
在这里插入图片描述
而第二个例子中。
Java中引用数据类型对象存储在堆中,栈中存储的是指向堆的地址。而传参时传递的副本其实是堆的地址,只不过副本和值本身都指向了同一个堆地址,changeCat方法内部修改的数据,其实就是对当前内存地址指向的对象进行修改。所以修改之后,数据会发生变化。
在这里插入图片描述


在学习这部分内容的时候我在网上看到了一个观点“Java没有引用传递,只有值传递”。但是,我并不是很理解这个观点。

用来证明的例子为下面这个:

public class Test {
    public static void main(String[] args) {
        A a1 = new A("hello", 22);
        System.out.println(a1);     // A{name='hello', age=22}
        change(a1);
        System.out.println(a1);     // A{name='hello', age=22}
    }

    public static void change(A a2) {
        a2 = new A("world", 23);
    }
}

根据我查的资料,Java、C和C#对于引用传递的描述大致意思是相同的,都是:引用传递时,系统不是将实参本身的值复制后传递给形参,而是将其引用值(即地址值)传递给形参。因此,形参所引用的该地址上的变量与传递的实参相同,方法体内相应形参值得任何改变都将影响到作为引用传递的实参。

但是,在这个例子中,a1先将地址传递给了a2,而在change中又给a2重新new了一个新对象,这时a1和a2肯定就不指向同一个地址了,这样打印a1一定不会变。
我把同样的代码用C#尝试了一下,结果和Java相同,难道是因为C#也没有引用传递吗?我不太明白。

internal class Program
{
    static void Main(string[] args)
    {
        A a1 = new A("hello", 22);
        Console.WriteLine(a1);     // name: hello age:22
        change(a1);
        Console.WriteLine(a1);     // name: hello age:22
    }

    public static void change(A a2)
    {
        a2 = new A("world", 23);
    }
}

二、对象的拷贝

**浅拷贝:**只复制指向某个对象的地址,而不是复制对象本身,新旧对象共享同一块内存
**深拷贝:**复制并创建一个一模一样的对象,不共享内存,修改新对象,旧对象保持不变

1. 浅拷贝

如果我们要使用浅拷贝,拷贝涉及到的对象,必须要实现Cloneable接口

1.1. 浅拷贝的实现

浅拷贝的实现方式:
1、在所涉及的类上面实现Cloneable接口
2、在需要被克隆的对象所属的类里面重写clone方法(访问修饰符必须使用public)
3、调用clone方法,实现对象的克隆

// 测试浅拷贝:
public class ShallowCopy {
    public static void main(String[] args) throws CloneNotSupportedException {
        Cat cat = new Cat("大橘", 2);
        Person person = new Person(1001, "L", cat);

        // 克隆Person对象
        Person clonePerson = (Person) person.clone();
        System.out.println("原对象:" + person);         // 原对象:Person{id=1001, name='L', cat=Cat{name='大橘', age=2}}
        System.out.println("克隆对象:" + clonePerson);  // 克隆对象:Person{id=1001, name='L', cat=Cat{name='大橘', age=2}}
        System.out.println(person == clonePerson);    // false

        // 修改克隆对象
        cat.name = "三花";
        cat.age = 1;
        clonePerson.id = 1002;
        clonePerson.name = "Z";
        clonePerson.cat = cat;
        System.out.println("原对象:" + person);        // 原对象:Person{id=1001, name='L', cat=Cat{name='三花', age=1}}
        System.out.println("克隆对象:" + clonePerson);  // 克隆对象:Person{id=1002, name='Z', cat=Cat{name='三花', age=1}}
    }
}

// Cat
public class Cat implements Cloneable {
    public String name;
    public int age;

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

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

// Person
public class Person implements Cloneable {
    public int id;
    public String name;
    public Cat cat;

    public Person(int id, String name, Cat cat) {
        this.id = id;
        this.name = name;
        this.cat = cat;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return (Person) super.clone(); // super.clone()  调用父类的clone方法;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", cat=" + cat +
                '}';
    }
}

总结:

  • 1、克隆出来的对象和原来的对象并不是同一个对象
  • 2、如果我们对克隆对象的属性进行修改,如果修改的基本类型的属性和字符串类型的属性,对原对象的基本类型的属性和字符串类型的属性是没有影响的。如果我们修改克隆对象的引用对象类型的属性,会影响原对象的对象类型属性的属性值

2. 深拷贝

深拷贝所涉及到的类必须定义构造函数。除拷贝涉及到的对象需要实现 Cloneable 接口外,在对象中引用的其他对象也需要实现 Cloneable 接口,并重写 Object 类的 clone() 方法

2.1. 深拷贝的实现
// 测试深拷贝:
public class DeepCopy {
    public static void main(String[] args) throws CloneNotSupportedException {
        Cat cat = new Cat("大橘", 2);
        Person person = new Person(1001, "L", cat);

        // 通过深拷贝,创建克隆对象
        Person clonePerson = (Person) person.clone();
        System.out.println(person == clonePerson);  // false
        System.out.println(person);     // Person{id=1001, name='L', cat=Cat{name='大橘', age=2}}
        System.out.println(clonePerson);    // Person{id=1001, name='L', cat=Cat{name='大橘', age=2}}

        // 修改克隆对象
        clonePerson.id = 1002;
        clonePerson.name = "Z";
        clonePerson.cat.name = "三花";
        clonePerson.cat.age = 3;
        System.out.println(person);     // Person{id=1001, name='L', cat=Cat{name='大橘', age=2}}
        System.out.println(clonePerson);    // Person{id=1002, name='Z', cat=Cat{name='三花', age=3}}
    }
}

// Cat
public class Cat implements Cloneable {
    public String name;
    public int age;

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

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }

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

// Person
public class Person implements Cloneable {
    public int id;
    public String name;
    public Cat cat;

    public Person(int id, String name, Cat cat) {
        this.id = id;
        this.name = name;
        this.cat = cat;
    }

    /*@Override   // 浅拷贝
    protected Object clone() throws CloneNotSupportedException {
        return (Person) super.clone(); // super.clone()  调用父类的clone方法;
    }*/

    @Override   // 重写clone方法
    protected Object clone() throws CloneNotSupportedException {
        Person clonePerson = (Person) super.clone();
        clonePerson.cat=(Cat) this.cat.clone();
        return clonePerson;
    }

    @Override
    public String toString() {
        return "Person{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", cat=" + cat +
                '}';
    }
}

总结:
深拷贝克隆出来的对象,如果对象的属性发生变化(不管是基本类型数据的值还是引用类型数据的值),都不会影响原对象的属性值

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值