Java-02对象传递和返回
Java-02对象传递和返回
当你在“传递”一个对象的时候,你实际上是在传递它的引用
1引用
1.1传递引用
当你将一个引用传给方法后,该引用指向的仍然是原来的对象:
/** * @Author Coder_Pans * @Date 2022/11/20 10:14 * @PackageName:org.example.onjava.senior.example02ref * @ClassName: PassRefences * @Description: TODO 传递引用 * @Version 1.0 */ public class PassRefences { /** * 当你将一个引用传给方法后,该引用指向的仍然是原来的对象 */ public static void f(PassRefences h){ System.out.println("h inside f(): " + h); } public static void main(String[] args) { PassRefences p = new PassRefences(); System.out.println("p inside main(): " + p); f(p); } }
1.2引用别名
引用别名指的是不止一个引用被绑定到了同一个对象上的情况。
别名导致的问题主要发生在有人对对象进行写操作时。如果该对象的其他引用的持有者并不希望对象发生变更,那么结果将使其大感意外。
/** * @Author Coder_Pans * @Date 2022/11/20 10:18 * @PackageName:org.example.onjava.senior.example02ref * @ClassName: Alias1 * @Description: TODO 引用别名 * @Version 1.0 */ public class Alias1 { private int i; public Alias1(int ii) { this.i = ii; } public static void main(String[] args) { Alias1 x = new Alias1(7); Alias1 y = x; System.out.println("x.i = " + x.i); System.out.println("y.i = " + y.i); System.out.println("Incrementing x"); x.i++; System.out.println("x.i = " + x.i); System.out.println("y.i = " + y.i); } } /** * put: * x.i = 7 * y.i = 7 * Incrementing x * x.i = 8 * y.i = 8 */
为了避免上面的情况,解决方案如下:
/** * @Author Coder_Pans * @Date 2022/11/20 10:18 * @PackageName:org.example.onjava.senior.example02ref * @ClassName: Alias1 * @Description: TODO 引用别名,解决方案 * @Version 1.0 */ public class Alias2 { private int i; public Alias2(int ii) { this.i = ii; } public static void f(Alias2 ref){ ref.i++; } public static void main(String[] args) { Alias2 x = new Alias2(7); Alias2 y = x; System.out.println("x.i = " + x.i); System.out.println("y.i = " + y.i); System.out.println("Calling f(x_x)"); f(y); System.out.println("x.i = " + x.i); System.out.println("y.i = " + y.i); } }
2创建本地副本
Java中所有参数传递都是通过传递引用实现的。也就是说,当你传递“一个对象”时,你实际上传递的是存活于方法外部的指向这个对象的一个引用。因此,如果你通过该引用执行了任何修改,你也将修改外部的对象。
- 引用别名会在传递参数时自动修改
- 并没有本地对象,只有本地引用
- 引用是有作用域的,对象则没有
- Java中的对象生命周期从来就不是个问题
2.1值传递
得到一个本地的副本。
- Java传递任何事物时,都是在传递该事物的值。
- Java在传递基本类型时,传递的是值,但在传递对象时,传递的则是引用。
2.2克隆对象
创建对象的本地副本,最可能的原因时你要修改该对象,但并不想修改调用者的原有对象。这个时候就可以使用clone()方法了。
/** * @Author Coder_Pans * @Date 2022/11/20 10:38 * @PackageName:org.example.onjava.senior.example02ref * @ClassName: CloneArrayList * @Description: TODO clone()操作 * @Version 1.0 */ 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); @SuppressWarnings("unchecked") ArrayList<Int> v2 = (ArrayList<Int>)v.clone(); // Increment all v2's elements: v2.forEach(Int::increment);// 对v2中的元素进行自增 // See if it changed v's elements: System.out.println("v: " + v);// 这时会发现v中的元素都+1 } }
clone()方法生成了一杯Object,然后该Object必须被转换为合适的类型。本例演示了ArrayList的clone()方法如何不去自动尝试克隆ArrayList中的每个对象——原有的ArrayList和克隆的ArrayList都是同一个对象的不同引用别名。这是一种浅拷贝(Shallow copy),因为只复制了对象的“表层”部分。实际对象的组成部分包括该“表层(引用)”、该引用指向的所有对象,以及所有这些对象所指向的所有对象,以此类推。
这通常称为“对象网络”。创建所有这些内容的完整副本,称为“深拷贝(deep copy)”
2.3为类增加可克隆能力
并不是所有类自动具备克隆能力,为了让某个类具备克隆能力,就必须专门添加代码来使其具备克隆的能力:
- 利用protected的技巧
- 调用super.clone()
- 将你的克隆操作设为public
- 实现Cloneable接口
2.4成功的克隆
了解了实现clone()方法的细节,就能够创建出可轻松复制的类,以提供生成本地副本的能力
/** * @Author Coder_Pans * @Date 2022/11/20 11:00 * @PackageName:org.example.onjava.senior.example02ref * @ClassName: LocalCopy * @Description: TODO 用clone()创建本地副本 * @Version 1.0 */ class Duplo implements Cloneable{ private int n; public Duplo(int n) { this.n = n; } @Override public Duplo clone() {// 为了使cloen能被访问,设置为public try { return (Duplo) super.clone();// 上面说到的调用super.clone(),协变返回类型。 } catch (CloneNotSupportedException e) { throw new AssertionError(); } } 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); // Reference equivalence, not object equivalence: // 引用相等,并不是对象相等 System.out.println("a == b: " + (a == b) + "\na = " + a + "\nb = " + b); System.out.println("a.equals(b) = " + a.equals(b)); Duplo c = new Duplo(47); Duplo d = f(c);// 克隆c赋值给d System.out.println("c == d: " + (c == d) + "\nc = " + c + "\nd = " + d); System.out.println("c.equals(d) = " + c.equals(d)); } } /** * Output: * a == b: true * a = 12 * b = 12 * a.equals(b) = true * c == d: false * c = 47 * d = 48 * c.equals(d) = false */
首先,为了使clone()能被访问,必须将它设为public的。其次,在你的clone()操作的开始部分,调用基类版本的clone()。这里调用的clone()即在Object中预先定义好的那个clone(),你可以调用该方法,是因为它是protected的,可以在子类中被访问。
Object.clone()会检测出该对象有多大,并为新对象创建足够的内存空间,然后将旧对象所有的二进制都复制到新对象中。这称为按位复制。在这个方法执行之前,会先检查(在继承层次结构中)是否有类是Cloneable的,也就是它是否实现了Cloneable接口。如果没有,Object.clone()会抛出CloneNotSupportedException异常,表示无法进行克隆。
2.5Object.clone()的效果
根类中的clone方法负责创建正确大小的存储空间,并执行了从原始对象中的所有二进制位到新对象存储中的按位复制。
也就是说,该方法并不只是创建存储空间和复制一个Object,它实际上会判断要复制的实际对象(不只是基类对象,还包括派生对象)的大小,然后再进行复制。
所有这些都依靠在根类中定义的clone()方法的代码来实现,而根类并不知道具体哪个对象会被继承,在这个过程中用到了反射来确定要克隆的实际对象。
这样,clone()方法就可以创建大小合适的存储空间 ,并正确地对该类型执行按位复制。
/** * @Author Coder_Pans * @Date 2022/11/20 11:25 * @PackageName:org.example.onjava.senior.example02ref * @ClassName: Snake * @Description: TODO * @Version 1.0 */ public class Snake implements Cloneable{ private Snake next;// 单向链表 private char c; public Snake(int i, char x) { this.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 { Snake clone = (Snake) super.clone(); // TODO: 在此处复制可变状态,因此克隆无法更改原始状态的内部 // TODO: copy mutable state here, so the clone can't change the internals of the original return clone; } catch (CloneNotSupportedException e) { throw new AssertionError(); } } 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( "after s.increment, s2 = " + s2); } }
2.6克隆组合对象
在你试图深拷贝组合对象时,会遇到一个问题。你必须假设所有成员对象中clone()方法都会按顺序对各自的引用执行深拷贝,并照此进行下去。这一点是必须确保的。它实际上意味着为了正确执行深拷贝,你要么控制所有类的所有代码,要么至少对深拷贝涉及的所有类都足够了解,以确定它们都能正确地执行各自的深拷贝。
定义一个 DepthReading;
package org.example.onjava.senior.example02ref.clone_combined; /** * @Author Coder_Pans * @Date 2022/11/20 13:20 * @PackageName:org.example.onjava.senior.example02ref.clone_combined * @ClassName: DepthReading * @Description: TODO 克隆组合对象01 * @Version 1.0 */ public class DepthReading implements Cloneable{ private double depth; public DepthReading(double depth) { this.depth = depth; } public double getDepth() { return depth; } public void setDepth(double depth) { this.depth = depth; } @Override public DepthReading clone() { try { DepthReading clone = (DepthReading) super.clone(); // TODO: copy mutable state here, so the clone can't change the internals of the original return clone; } catch (CloneNotSupportedException e) { throw new AssertionError(); } } @Override public String toString() { return String.valueOf(depth); } }
定义一个 TemperatureReading:
package org.example.onjava.senior.example02ref.clone_combined; /** * @Author Coder_Pans * @Date 2022/11/20 13:22 * @PackageName:org.example.onjava.senior.example02ref.clone_combined * @ClassName: TemperatureReading * @Description: TODO 克隆组合对象02 * @Version 1.0 */ public class TemperatureReading implements Cloneable{ private long time; private double temperature; public TemperatureReading(double temperature) { time = System.currentTimeMillis();// 获取当前系统时间 this.temperature = temperature; } @Override public TemperatureReading clone() { try { TemperatureReading clone = (TemperatureReading) super.clone(); // TODO: copy mutable state here, so the clone can't change the internals of the original return clone; } catch (CloneNotSupportedException e) { throw new AssertionError(); } } public long getTime() { return time; } public void setTime(long time) { this.time = time; } @Override public String toString() { return String.valueOf(temperature); } public double getTemperature() { return temperature; } public void setTemperature(double temperature) { this.temperature = temperature; } }
在定义一个拷贝上面两个对象的类,这个类在构造方法中初始化上面两个类
package org.example.onjava.senior.example02ref.clone_combined; /** * @Author Coder_Pans * @Date 2022/11/20 13:23 * @PackageName:org.example.onjava.senior.example02ref.clone_combined * @ClassName: OceanReading * @Description: TODO * @Version 1.0 */ public class OceanReading implements Cloneable{ private DepthReading depth; private TemperatureReading temperature; public OceanReading(double tdata, double ddata) { this.depth = new DepthReading(ddata); this.temperature = new TemperatureReading(tdata); } /** * 这里必须对 《构造方法》 中的克隆对象进行 克隆引用 * @return */ @Override public OceanReading clone() { OceanReading or = null; try { or = (OceanReading) super.clone(); } catch (CloneNotSupportedException e) { throw new AssertionError(); } // 必须克隆引用: or.depth = (DepthReading)or.depth.clone(); or.temperature = (TemperatureReading)or.temperature.clone(); return or; } public DepthReading getDepth() { return depth; } @Override public String toString() { return "depth=" + depth + ",temperature=" + temperature; } public void setDepth(DepthReading depth) { this.depth = depth; } public TemperatureReading getTemperature() { return temperature; } public void setTemperature(TemperatureReading temperature) { this.temperature = temperature; } }
下面对拷贝组合对象进行测试:
class DeepCopyTest { @Test public void testClone(){ 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); assertEquals(reading.toString(),"depth=100.5,temperature=33.9"); assertEquals(clone.toString(),"depth=101.5,temperature=34.9"); } }
DepthReading和TemperatureReading很相似,它们都只包含基本类型。因此,clone()方法可以很简单:调用super.clone(),然后返回结果。这两者的clone是完全相同的。
OceanReading是由DepthReading和TemperatureReading对象组合而成的,因此,要实现深拷贝,OceanReading的clone()就必须克隆OceanReading内部的所有引用才行。要完成这项任务,super.clone()的结果必须转型为OceanReading对象(这样才可以访问depth和tempterature的引用)
2.7深拷贝ArrayList
对ArrayList进行深拷贝:
package org.example.onjava.senior.example02ref.clone_arraylist; import java.util.ArrayList; import java.util.stream.Collectors; import java.util.stream.IntStream; /** * @Author Coder_Pans * @Date 2022/11/20 13:54 * @PackageName:org.example.onjava.senior.example02ref.clone_arraylist * @ClassName: AddingClone * @Description: TODO 深拷贝ArrayList * @Version 1.0 */ class Int2 implements Cloneable{ private int i; public Int2(int i) { this.i = i; } public void increment(){ i++; } @Override public String toString() { return Integer.toString(i); } @Override public Int2 clone() { try { Int2 clone = (Int2) super.clone(); // TODO: copy mutable state here, so the clone can't change the internals of the original return clone; } catch (CloneNotSupportedException e) { throw new AssertionError(); } } } // 继承不会移除可克隆性 class Int3 extends Int2{ private int j;// 自动创建了副本 public Int3(int i) { super(i); } } public class AddingClone { @SuppressWarnings("unchecked") public static void main(String[] args) { Int2 x = new Int2(10); Int2 x2 = x.clone(); x2.increment(); System.out.println( "x = " + x + ", x2 = " + x2); // 继承出的任何事物同样也是可克隆的 Int3 x3 = new Int3(7); x3 = (Int3) x3.clone(); System.out.println("x3 = " + x3); Int2 clone = x3.clone(); System.out.println("clone = " + clone); ArrayList<Int2> v = IntStream.range(0, 10) .mapToObj(Int2::new) .collect(Collectors.toCollection(ArrayList::new)); System.out.println("v = " + v); ArrayList<Int2> v2 = (ArrayList<Int2>) v.clone(); // 现在克隆每个元素 IntStream.range(0, v.size()) .forEach(i -> v2.set(i, v.get(i).clone())); v2.forEach(Int2::increment); // 一旦克隆了一个对象,你就可以对副本进行修改,而原本对象不会受到影响 System.out.println("v2 = " + v2); // 查看v中的元素是否发生改变 System.out.println("v = " + v);// 没有改变,复制成功 } } /** * output: * x = 10, x2 = 11 * x3 = 7 * clone = 7 * v = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] * v2 = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] * v = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] */
Int3继承自Int2,并增加了一个新的基本类型成员int j。在调用Int3的clone()时,其内部所调用的Int2的clone(),实际上调用的是Object.clone(),它检测到此处起作用的是Int3,并对Int3执行了按位复制。只要你不增加需要克隆的引用,对Object.clone()的一次调用便可以执行所有必要的复制,不论clone定义在继承层次结构中多么深的位置。
要深拷贝ArrayList,需要这么做:在克隆ArrayList后,还需要进一步克隆ArrayList所指向的每一个对象。如果要深拷贝入HashMap这样的内容,你也需要执行类似的操作。
一旦克隆了一个对象,你就可以对副本进行修改,而原本对象不会受到影响
2.8通过序列化进行深拷贝
如果一个对象先进行序列化,再将其反序列化,那么它实际上就是被克隆了。
接下来就使用序列化来实现深拷贝:
package org.example.onjava.senior.example02ref.clone_serializable; import org.example.onjava.onjavaUtils.Timer; import java.io.*; /** * @Author Coder_Pans * @Date 2022/11/20 14:27 * @PackageName:org.example.onjava.senior.example02ref.clone_serializable * @ClassName: Compete * @Description: TODO 序列化实现深拷贝 * @Version 1.0 */ class Thing1 implements Serializable { } class Thing2 implements Serializable { Thing1 t1 = new Thing1(); } class Thing3 implements Cloneable { @Override public Thing3 clone() { try { return (Thing3) super.clone(); } catch (CloneNotSupportedException e) { throw new AssertionError(); } } } class Thing4 implements Cloneable { private Thing3 t3 = new Thing3(); @Override public Thing4 clone() { Thing4 t4 = null; try { t4 = (Thing4) super.clone(); } catch (CloneNotSupportedException e) { throw new AssertionError(); } //TODO 对字段也要进行克隆 t4.t3 = t3.clone(); return t4;// Thing4对象所包含的所有内容克隆完之后才能够返回。 } } public class Compete { public static final int SIZE = 10000; public static void main(String[] args) { 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(); } Timer timer = new Timer(); try (ByteArrayOutputStream buf = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(buf)) { for (Thing2 a1 : a) { oos.writeObject(a1); } // Now get copies: try (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(); } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } catch (IOException e) { throw new RuntimeException(e); } System.out.println("Duplication via serialization: " + timer.duration() + " Milliseconds"); // Now try cloning: timer = new Timer(); Thing4[] d = new Thing4[SIZE]; for (int i = 0; i < SIZE; i++) d[i] = b[i].clone(); System.out.println("Duplication via cloning: " + timer.duration() + " Milliseconds"); } }
Thing2和Thing4包含了成员对象,所以需要进行一些深拷贝操作。Serializable类很容易构建,但是需要大量额外操作来复制它们。另外,在克隆所需要的操作中,类的构建工作更多,但是实际的对象复制操作相对简单
序列化比克隆慢的多得多!
Duplication via serialization: 115 Milliseconds Duplication via cloning: 3 Milliseconds
posted on 2022-11-20 14:14 JavaCoderPan 阅读(53) 评论(1) 编辑 收藏 举报
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性