Java中调用Runnable对象方法无法影响外部对象问题:StringBuffer对象交换失败原因排查
问题核心:Java的引用传递是「值传递」,你交换的是副本引用
嘿,我来帮你理清这个问题的本质——这和有没有get/set方法完全没关系,你原来的交换方式从一开始就不可能影响到main方法里的原始引用,原因在于Java的参数传递规则:
为什么你的交换没生效?
当你把main里的_i1和_i2传给Swapper的构造方法时,Swapper类里的成员变量_i1、_i2拿到的是原始引用的副本。也就是说:
- Swapper的
_i1和main的_i1都指向同一个StringBuffer("1")对象,但这两个变量本身是独立的; - 你在run方法里交换Swapper的
_i1和_i2,只是把这个副本的指向互换了,main方法里的原始_i1、_i2完全不受影响,它们依然指向原来的对象。
而你之前看到的用get/set成功交换的示例,本质是修改对象内部的状态(比如修改包装类里的成员变量),而不是交换引用副本。比如如果你的Swapper是修改StringBuffer的内容(比如_i1.setLength(0); _i1.append("2");),那main里的输出会变化,但交换引用副本的操作对外部的原始引用毫无作用。
怎么实现真正的引用交换?
要让main方法里的引用被交换,你需要把StringBuffer放在一个可修改的容器里(比如数组、自定义包装类),然后操作容器内的元素——因为容器的引用副本指向的是同一个容器对象,修改容器内的元素,外部就能看到变化。
方案1:用数组包装
public class SwapDemo { public static void main(String[] args) throws InterruptedException { StringBuffer[] bufferArr = {new StringBuffer("1"), new StringBuffer("2")}; Thread t = new Thread(new Swapper(bufferArr)); System.out.println(bufferArr[0] + ", " + bufferArr[1]); // 输出 1, 2 t.start(); t.join(); System.out.println(bufferArr[0] + ", " + bufferArr[1]); // 输出 2, 1 } } class Swapper implements Runnable { private StringBuffer[] bufferArr; Swapper(StringBuffer[] bufferArr) { this.bufferArr = bufferArr; } @Override public void run() { synchronized (bufferArr) { try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } // 交换数组内的引用,外部main的数组会同步看到变化 StringBuffer tmp = bufferArr[0]; bufferArr[0] = bufferArr[1]; bufferArr[1] = tmp; } } }
方案2:用自定义包装类
// 自定义一个包装类,用来持有StringBuffer的引用 class BufferWrapper { private StringBuffer buffer; public BufferWrapper(StringBuffer buffer) { this.buffer = buffer; } public StringBuffer getBuffer() { return buffer; } public void setBuffer(StringBuffer buffer) { this.buffer = buffer; } } public class SwapDemo { public static void main(String[] args) throws InterruptedException { BufferWrapper w1 = new BufferWrapper(new StringBuffer("1")); BufferWrapper w2 = new BufferWrapper(new StringBuffer("2")); Thread t = new Thread(new Swapper(w1, w2)); System.out.println(w1.getBuffer() + ", " + w2.getBuffer()); // 输出 1, 2 t.start(); t.join(); System.out.println(w1.getBuffer() + ", " + w2.getBuffer()); // 输出 2, 1 } } class Swapper implements Runnable { private BufferWrapper w1; private BufferWrapper w2; Swapper(BufferWrapper w1, BufferWrapper w2) { this.w1 = w1; this.w2 = w2; } @Override public void run() { synchronized (w1) { try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (w2) { // 通过包装类的set方法修改内部引用,外部能看到变化 StringBuffer tmp = w1.getBuffer(); w1.setBuffer(w2.getBuffer()); w2.setBuffer(tmp); } } } }
总结
不管有没有get/set方法,你原来的交换方式都无法改变main方法里的原始引用——因为你操作的是Swapper类内部的引用副本。之前的get/set示例能成功,是因为它们操作的是对象内部的状态(或包装类的成员引用),而非直接交换方法参数的引用副本。
内容的提问来源于stack exchange,提问作者Aaron




