🌸 Java中如何实现swap函数?

C/C++与swap

在C/C++中,若要实现两个变量或对象的交换,只需要写一个swap函数。例如,在C语言中,只需要把变量或对象实例的地址传入swap函数中,即可实现模拟引用传递:

void swap(Type *a, Type *b) {
    Type t = *a;
    (*a) = (*b);
    (*b) = t;
}

C++更为简单,只需要将待交换对象的引用传入即可:

void swap(Type &a, Type &b) {
    Type t = a;
    a = b;
    b = t;
}

上述代码还可用template泛化,使其支持各种数据对象。

那么,在Java中能否实现上述简单通用的swap函数呢?

void swap(Object obj1, Object obj2) {
    ... // 如何交换obj1和obj2?
}

为了解答这个问题,我们需要先探究Java的参数传递机制。

Java的参数传递机制

Java的参数传递分为两种,采用哪种是根据参数类型决定的:

一番讨论之后,我们得出的结论竟是:Java不能实现swap函数。这对于先前熟悉Pascal、C/C++或Python等语言的Java初学者来说,简直是不可思议的,但事实的确如此。究其原因,是Java放弃了指针

我查阅到如下资料,

事实上,Java的设计思想就是「安全第一」, 将大部分工作转移到堆上, 严格限制指针的使用(甚至连指针这个名字都不用),加上GC(Garbage Collection,垃圾回收)机制,进一步确保其安全性。

但是Java中还是有指针的,当我们创建一个对象实例时,也就同时声明了一个指向该实例的堆存储空间入口地址的指针,这个指针就是所谓的「引用」。引用可以指向其他相关类型的实例存储空间,但是只能指向这些存储空间的入口地址,不能任意指向其中的存储区域。Java实际上就是将管理内存的指针操作都封装在JVM中,让程序员能够更多地关注于方案的设计和优化。

替代方案探索

常规方案解决不了,有没有替代方案呢?在特定情境下,是有的。

数组排序中的swap

例如,给数组写排序算法时,常常需要交换数组中的两个元素。这时,我们就可以利用「数组和类是按引用传递的」这一特性,写出如下的swap代码:

void swap(int[] data, int a, int b) {  
      int t = data[a];  
      data[a] = data[b];  
      data[b] = t;  
 }

类成员变量之间的swap

类成员变量之间的交换,我们可以反过来利用这些「不方便」的特性,用两条语句轻松解决:

void swap(int a, int b) {
	this.a = b;
	this.b = a;
}

相同类的两个实例的swap

如果一定要写某个类的swap,可以在这个类外面再套一层Wrapper类,把原类变成Wrapper类的成员,间接实现swap功能:

// Car类,两个int型成员变量model和no
class Car {
    int model, no;

    // 构造方法
    Car(int model, int no) {
        this.model = model;
        this.no = no;
    }

    // 打印信息 
    void print() {
        System.out.println("no = " + no +
                ", model = " + model);
    }
}

// 为实现swap而创建的Wrapper类 
class CarWrapper {
    Car c;

    // Constructor 
    CarWrapper(Car c) {
        this.c = c;
    }
}

// 主类
class Main {
    // 通过Wrapper交换两个Car实例 
    public static void swap(CarWrapper cw1,
                            CarWrapper cw2) {
        Car temp = cw1.c;
        cw1.c = cw2.c;
        cw2.c = temp;
    }
	
    // 主程序
    public static void main(String[] args) {
        Car c1 = new Car(101, 1);
        Car c2 = new Car(202, 2);
        CarWrapper cw1 = new CarWrapper(c1);
        CarWrapper cw2 = new CarWrapper(c2);
        swap(cw1, cw2);
        cw1.c.print();
        cw2.c.print();
    }
}

以上代码的输出为:

no = 2, model = 202
no = 1, model = 101

但这个解决方案严格意义上已经是在「偷梁换柱」,改变了交换的对象。

探索int的通用swap

那么,能否针对两个int变量的交换,写一个通用的swap函数呢?如前所述,int等基本类型都是按值传递的,不能直接交换。那么,能否转化成Integer类之后再调用Integer的API进行交换呢?

我们先调查一下Integer有没有这种能修改值的API。在IDE中查看Integer类的代码,我们无法找到类似setValue这类方法。事实上,可以发现,Integer的值value带有final属性,如下图所示。也就是说,Integer类的实例一旦初始化,其值就不能再修改。

image-20200225182712334

有一个小困惑是,类似a = 1这样的Integer的赋值语句为何合法?那是因为对Integer的赋值本质上是用赋值号右侧的值创建了一个新的Integer实例,用它替代了原先的实例,期间并没有发生值的改变。

看来,转化成Integer这条路也行不通。我又查阅了一些资料,有人认为通过反射等机制可以实现交换功能,但都过于复杂且涉及许多底层原理和高级特性,这里就先挖个坑吧。

拓展材料

[1] 知乎 - 怎么用Java实现一个swap函数?

[2] CSDN - 【Java】Java中的swap

[3] GeeksforGeeks - How to swap or exchange objects in Java?

[4] GeeksforGeeks - Collections swap() method in Java with Examples

[5] segmentfault - java 如何实现swap功能??

Powered by Jekyll and Theme by solid