《On Java进阶卷》- 笔记-2-对象传递和返回

传递对象就是在传递它的引用

  • java不带指针,但java中所有对象标识(除基本类型)都是指针,同时编译和运行时严格限制和保护这些指针
  • java的指针即引用,java没有指针运算,是安全的指针

2.1 传递引用

把引用传递给方法后,该引用仍指向原来的对象

java 复制代码
public class PassReferences {
    public static void f(PassReferences h) {
        System.out.println("h = " + h);
    }

    public static void main(String[] args) {
        PassReferences p = new PassReferences();
        System.out.println("p=" + p);
        f(p);
        //p=com.edfeff.ch02.PassReferences@723279cf
        //h = com.edfeff.ch02.PassReferences@723279cf
    }
}

引用别名 多个引用指向同一个对象。

  • 有引用写,有引用读,就会很容易导致意外情况发生。
  • 所以在同一个作用域内,尽量不要使用多个别名,最好只用一个,减少理解和调试难度
  • 方法的参数传递其实也是自动命名引用别名了,也导致可以修改外部对象
  • 方法一般是为了获取返回值,或者改变方法调用者的状态,改变传递参数对象的状态是一个副作用,会带来隐患,最好避免在方法内修改参数的状态。
java 复制代码
public class Alias1 {
    private int i;

    public Alias1(int i) {
        this.i = i;
    }

    public static void f(Alias1 ref) {
        ref.i++;
    }

    public static void main(String[] args) {
        Alias1 x = new Alias1(7);
        Alias1 y = x;
        System.out.println("x=" + x.i);
        System.out.println("y=" + y.i);
        x.i++;
        System.out.println("x=" + x.i);
        System.out.println("y=" + y.i);
        f(x);
        System.out.println("x=" + x.i);
        System.out.println("y=" + y.i);
    }
}

java中的参数传递都是通过传递引用实现的。

  • 引用别名在传递参数时自动发生
  • 并没有本地对象,只有本地引用
  • 引用有作用域,对象没有

2.2 创建本地副本

由于参数对象是传递引用,并且java并没有提供防止对象被修改的相关支持(比如const),所以有时为了避免引用别名的副作用,需要传递一个副本。

2.2.1 值传递

值传递的两种理解

  1. java传递的都是值。
    • 传递基本类型就是单独的副本
    • 传递引用就是引用的副本
  2. java传递基本类型是值,传递对象是引用
    • 引用是对象的别名,传入方法时,并没有得到对象的副本,所以传递的不是对象的值

上面的观点都很有意思,但重要的是,要理解传递引用会使得外部对象被意外修改

2.2.2 克隆对象

需要修改对象,但不能影响外部对象,则需要副本。

  • 使用clone()方法
  • ArrayListd的clone方法没有复制内部的每一个对象,只是同一个对象的不同引用别名,称之为浅拷贝
  • 深拷贝,创建所有内容的完整副本
java 复制代码
class Int {
    private int i;

    public Int(int i) {
        this.i = i;
    }

    public void increment() {
        i++;
    }

    @Override
    public String toString() {
        return Integer.toString(i);
    }
}

public class CloneArrayList {
    public static void main(String[] args) {
        ArrayList<Int> v = IntStream
                .range(0, 10)
                .mapToObj(Int::new)
                .collect(Collectors.toCollection(ArrayList::new));
        System.out.println("v=" + v);//v=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

        @SuppressWarnings("unchecked")
        ArrayList<Int> clone = (ArrayList<Int>) v.clone();
        clone.forEach(Int::increment);//v=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

        System.out.println("v=" + v);
    }
}

2.2.3 为类增加可克隆能力

  1. 重写clone方法,修饰符改为public
  2. 实现Cloneable接口,没有实现该接口,调用clone时报错CloneNotSupportedException

2.2.4 成功的克隆

示例如下

  • Object的clone方法返回类型是Object,需要向下转型为Duplo
java 复制代码
class Duplo implements Cloneable {
    private int n;

    public Duplo(int n) {
        this.n = n;
    }

    public Duplo clone() {
        try {
            return (Duplo) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException();
        }
    }

    public int getValue() {
        return n;
    }

    public void setValue(int n) {
        this.n = n;
    }

    public void increment() {
        n++;
    }

    @Override
    public String toString() {
        return Integer.toString(n);
    }
}

public class LocalCopy {

    //    修改外部对象
    public static Duplo g(Duplo v) {
        v.increment();
        return v;
    }

    //    创建本地副本并修改
    public static Duplo f(Duplo v) {
        v = v.clone();
        v.increment();
        return v;
    }

    public static void main(String[] args) {
        Duplo a = new Duplo(11);
        Duplo b = g(a);
        System.out.println("a==b " + (a == b) + " a=" + a + " b=" + b);//a==b true a=12 b=12

        Duplo c = new Duplo(47);
        Duplo d = f(c);
        System.out.println("c==d " + (c == d) + " c=" + c + " d=" + d);//c==d false c=47 d=48
    }
}

2.2.5 Object.clone()的效果

下面的示例中,是一个单向链表,clone之后的s2只有第一段被复制了,也就是浅拷贝。 如果要实现深拷贝,需要在clone中做额外操作。

java 复制代码
public class Snake implements Cloneable {
    private Snake next;
    private char c;

    public Snake(int i, char x) {
        c = x;
        if (--i > 0) {
            next = new Snake(i, (char) (x + 1));
        }
    }

    public void increment() {
        c++;
        if (next != null) {
            next.increment();
        }
    }

    @Override
    public String toString() {
        String s = ":" + c;
        if (next != null) {
            s += next.toString();
        }
        return s;
    }

    @Override
    public Snake clone() {
        try {
            return (Snake) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException();
        }
    }

    public static void main(String[] args) {
        Snake s = new Snake(5, 'a');
        System.out.println("s=" + s);

        Snake s2 = s.clone();
        System.out.println("s2=" + s2);

        s.increment();
        System.out.println("s2=" + s2);
    }
}

2.2.6 克隆组合对象

深拷贝组合对象时,需要所有成员对象中的clone方法都按照顺序对各自的引用执行深拷贝。

  • 这意味着为了正确的深拷贝,需要控制所有类的所有代码。

基础类

java 复制代码
package com.edfeff.ch02;

public class TemperatureReading implements Cloneable {
    private long time;
    private double temperature;

    public TemperatureReading(double temperature) {
        this.time = System.currentTimeMillis();
        this.temperature = temperature;
    }

    public double getTemperature() {
        return temperature;
    }

    public void setTemperature(double temperature) {
        this.temperature = temperature;
    }

    public TemperatureReading clone() {
        try {
            return (TemperatureReading) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException();
        }
    }

    @Override
    public String toString() {
        return String.valueOf(temperature);
    }
}
typescript 复制代码
public class DepthReading implements Cloneable {
    private double depth;

    public DepthReading(double d) {
        depth = d;
    }

    public DepthReading clone() {
        try {
            return (DepthReading) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }

    public double getDepth() {
        return depth;
    }

    public void setDepth(double depth) {
        this.depth = depth;
    }

    @Override
    public String toString() {
        return String.valueOf(depth);
    }
}

组合类

java 复制代码
package com.edfeff.ch02;

public class OceanReading implements Cloneable {
    private DepthReading depth;
    private TemperatureReading temperature;

    public OceanReading(double tdata, double ddata) {
        temperature = new TemperatureReading(tdata);
        depth = new DepthReading(ddata);
    }

    public OceanReading clone() {
        OceanReading or = null;
        try {
            or = (OceanReading) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException();
        }
        //引用克隆
        or.depth = or.depth.clone();
        or.temperature = or.temperature.clone();
        return or;
    }

    public DepthReading getDepth() {
        return depth;
    }

    public void setDepth(DepthReading depth) {
        this.depth = depth;
    }

    public TemperatureReading getTemperature() {
        return temperature;
    }

    public void setTemperature(TemperatureReading temperature) {
        this.temperature = temperature;
    }

    @Override
    public String toString() {
        return "depth=" + depth + ", temperature=" + temperature;
    }

    public static void main(String[] args) {
        OceanReading reading = new OceanReading(33.9, 100.5);

        //
        OceanReading clone = reading.clone();
        TemperatureReading tr = clone.getTemperature();
        tr.setTemperature(tr.getTemperature() + 1);
        clone.setTemperature(tr);

        DepthReading dr = clone.getDepth();
        dr.setDepth(dr.getDepth() + 1);
        clone.setDepth(dr);

        System.out.println("reading=" + reading);
        System.out.println("clone=" + clone);
        // reading=depth=100.5, temperature=33.9
        // clone=depth=101.5, temperature=34.9
    }
}

2.2.7 深拷贝ArrayList

深拷贝ArrayList,需要克隆内部的每一个元素

java 复制代码
class Int2 implements Cloneable {
    private int i;

    Int2(int i) {
        this.i = i;
    }

    public void increment() {
        i++;
    }

    @Override
    public String toString() {
        return Integer.toString(i);
    }

    @Override
    public Int2 clone() {
        try {
            return (Int2) super.clone();
        } catch (CloneNotSupportedException e) {
            throw new RuntimeException();
        }
    }
}

//继承不会移除可克隆性
class Int3 extends Int2 {
    private int j;

    Int3(int i) {
        super(i);
    }
}

public class AddingClone {
    public static void main(String[] args) {
        Int2 x = new Int2(10);
        Int2 x2 = x.clone();
        x2.increment();
        System.out.println("x=" + x + " x2=" + x2);//x=10 x2=11
        //
        Int3 x3 = new Int3(7);
        x3 = (Int3) x3.clone();

        ArrayList<Int2> v = IntStream.range(0, 10).mapToObj(Int2::new)
                .collect(Collectors.toCollection(ArrayList::new));
        System.out.println("v=" + v);//v=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

        ArrayList<Int2> v2 = (ArrayList<Int2>) v.clone();
        //克隆每个元素
        IntStream.range(0, v.size()).forEach(i -> v2.set(i, v.get(i).clone()));
        //递增v2
        v2.forEach(Int2::increment);
        System.out.println("v2=" + v2); //v2=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
        System.out.println("v=" + v);   //v=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    }
}

2.2.8 通过序列化进行深拷贝

对一个对象先进行序列化,再将其反序列化,那么它实际上就是被克隆了。

  • 序列化至少比克隆慢一个数量级
java 复制代码
class Thing1 implements Serializable {}
class Thing2 implements Serializable {
    Thing1 t1 = new Thing1();
}

class Thing3 implements Cloneable{
    @Override
    public Thing3 clone() throws CloneNotSupportedException {
        return (Thing3) super.clone();
    }
}

class Thing4 implements Cloneable {
    Thing3 t3 = new Thing3();
    @Override
    public Thing4 clone() throws CloneNotSupportedException {
        Thing4 t4 = (Thing4) super.clone();
        t4.t3 = t3.clone();
        return t4;
    }
}

public class Compete {
    static final int SIZE = 1000000;

    public static void main(String[] args) throws IOException, ClassNotFoundException, CloneNotSupportedException {
        Thing2[] a = new Thing2[SIZE];
        for (int i = 0; i < SIZE; i++) {
            a[i] = new Thing2();
        }

        Thing4[] b = new Thing4[SIZE];
        for (int i = 0; i < SIZE; i++) {
            b[i] = new Thing4();
        }
        long start = System.currentTimeMillis();
        ByteArrayOutputStream buf = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(buf);
        for (Thing2 a1 : a) {
            oos.writeObject(a1);
        }
        ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream((buf.toByteArray())));
        Thing2[] c = new Thing2[SIZE];
        for (int i = 0; i < SIZE; i++) {
            c[i]= (Thing2) in.readObject();
        }
        System.out.println("serial time: " + ((System.currentTimeMillis() - start)));

        //克隆时间测试
        start = System.currentTimeMillis();
        Thing4[] d = new Thing4[SIZE];
        for (int i = 0; i < SIZE; i++) {
            d[i] = b[i].clone();
        }
        System.out.println("clone time: " + ((System.currentTimeMillis() - start)));
//        serial time: 1155
//         clone time: 25
    }
}

2.2.9 在继承层次结构中增加可克隆性并向下覆盖

默认对象是不具备克隆性的,只有实现了Cloneable接口并重写clone方法后,该类及其子类就具有了可克隆性

java 复制代码
class Person{}
class Hero extends Person{}
class Scientist extends Person implements Cloneable{
    @Override
    public Scientist clone() throws CloneNotSupportedException {
        return (Scientist) super.clone();
    }
}
class MadScientist extends Scientist{}

public class HorrorFlick {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person p = new Person();
        Hero h = new Hero();
        Scientist s = new Scientist();
        MadScientist ms = new MadScientist();
        s = s.clone();
        ms = (MadScientist) ms.clone();
    }
}

2.2.10 为什么用这种奇怪的设计

Object的clone方法一开始是public的,由于被互联网广泛应用后,安全问题突出,任意复制与安全相关的对象是危险的。所以clone变成了protected,必须实现cloneable接口和重写clone方法。

2.3 控制可克隆性

2.4 不可变类

创建只读的不可变对象,外部引用不会修改该对象的任何状态。

2.4.1 创建不可变类

data 是private的,不提供public的方法来修改该data

  • multiply并没有修改data,而是直接返回一个新对象
csharp 复制代码
public class Immutable1 {
    private int data;

    public Immutable1(int data) {
        this.data = data;
    }

    public int read() {
        return data;
    }

    public boolean nonzero() {
        return data != 0;
    }

    public Immutable1 multiply(int value) {
        return new Immutable1(data * value);
    }

    public static void f(Immutable1 i) {
        Immutable1 quad = i.multiply(4);
        System.out.println("i=" + i.read());
        System.out.println("quad=" + quad.read());
    }

    public static void main(String[] args) {
        Immutable1 x = new Immutable1(47);
        System.out.println("x=" + x.read());
        f(x);
        System.out.println("x=" + x.read());
        //x=47
        //i=47
        //quad=188
        //x=47
    }
}

2.4.2 不可变性的缺点

  • 创建新对象的性能开销
  • 更频繁的GC

解决方法是创建可修改的伴生类,使用伴生类修改,完成后切换回不可变类

  • 需要不可变类,同时需要频繁修改操作,或者创建不可变对象成本高
csharp 复制代码
class Mutable {
    private int data;

    public Mutable(int data) {
        this.data = data;
    }

    public Mutable add(int x) {
        data += x;
        return this;
    }

    public Mutable multiply(int x) {
        data *= x;
        return this;
    }

    public Immutable2 makeImmutable() {
        return new Immutable2(data);
    }

}

public class Immutable2 {
    private int data;

    public Immutable2(int data) {
        this.data = data;
    }

    public int read() {
        return data;
    }

    public Immutable2 add(int x) {
        return new Immutable2(data + x);
    }

    public Immutable2 multiply(int x) {
        return new Immutable2(data * x);
    }

    public Mutable makeMutable() {
        return new Mutable(data);
    }

    public static Immutable2 modify1(Immutable2 y) {
        Immutable2 val = y.add(12);
        val = val.multiply(3);
        val = val.add(11);
        val = val.multiply(2);
        return val;
    }

    public static Immutable2 modify2(Immutable2 y) {
        //        切换到可变伴生类
        Mutable val = y.makeMutable();
        val = val.add(12)
                .multiply(3)
                .add(11)
                .multiply(2);
        //        切换到不可变类
        return val.makeImmutable();
    }

    public static void main(String[] args) {
        Immutable2 i2 = new Immutable2(47);
        Immutable2 r1 = modify1(i2);
        Immutable2 r2 = modify2(i2);
        System.out.println("i2=" + i2.read());
        System.out.println("r1=" + r1.read());
        System.out.println("r2=" + r2.read());
        //i2=47
        //r1=376
        //r2=376
    }
}

2.4.3 String很特殊

String本质是不可变的。修改String会返回新String对象。

相关推荐
魔尔助理顾问6 分钟前
系统整理Python的循环语句和常用方法
开发语言·后端·python
程序视点23 分钟前
Java BigDecimal详解:小数精确计算、使用方法与常见问题解决方案
java·后端
愿你天黑有灯下雨有伞29 分钟前
Spring Boot SSE实战:SseEmitter实现多客户端事件广播与心跳保活
java·spring boot·spring
你的人类朋友31 分钟前
❤️‍🔥微服务的拆分策略
后端·微服务·架构
Java初学者小白1 小时前
秋招Day20 - 微服务
java
狐小粟同学2 小时前
JavaEE--3.多线程
java·开发语言·java-ee
AI小智2 小时前
后端变全栈,终于可以给大家推出我的LangChain学习小站了!
后端
KNeeg_2 小时前
Spring循环依赖以及三个级别缓存
java·spring·缓存
lkf197113 小时前
商品中心—1.B端建品和C端缓存
开发语言·后端·缓存
我的ID配享太庙呀3 小时前
Django 科普介绍:从入门到了解其核心魅力
数据库·后端·python·mysql·django·sqlite