You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

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

火山引擎 最新活动