Java实现操作符重载

操作符重载,就是把已经定义的、有一定功能的操作符进行重新定义,来完成更为细致具体的运算等功能。

从面向对象的角度说,就是可以将操作符定义为类的方法,使得该操作符的功能可以用来代表对象的某个行为。

从DSL的角度说,就是用操作符来代替部分语法,提高DSL的可理解性与可读性。

背景

我们来考虑实现这样的功能:使用 BigInteger 来计算(a^2 + b^2)

常规写法:

BigInteger res = a.multiply(a).add(b.multiply(b));

假设可以对 *、+ 进行操作符重载,那可以直接这样写:

BigInteger res = a * a + b * b;

所以,对于非原始类型的数值运算,如果能够进行操作符重载,至少有 2 个好处:

  1. 代码写起来更简单,不容易出错
  2. 代码更容易阅读,不会一堆括号嵌套

操作符重载

算数操作符

Manifold 是将每个算数操作符的重载,映射到特定名称的函数。例如你在某个类 A 中定义了 plus(B) 的方法,那么这个类就可以使用 a + b 代替 a.plus(b) 进行调用。具体的映射关系为:

图片

为了方便举例说明,我们定义一个数值类型 Num:

public class Num {

    private final int v;

    public Num(int v) {
        this.v = v;
    }
    
    public Num plus(Num that) {
        return new Num(this.v + that.v);
    }

    public Num plus(int i) {
        return new Num(v + i);
    }

    public Num minus(Num that) {
        return new Num(this.v - that.v);
    }

    public Num plus(Num that) {
        return new Num(this.v * that.v);
    }
}

示例:

Num a = new Num(1);
Num b = new Num(2);

// 常规算术
Num c = a + b - a;
// 编译后
Num c = a.plus(b).minus(a);

// 支持运算符优先级
Num c = a + a * b - b;
// 编译后
Num c = a.plus(a.times(b )).minus(b);

// 支持运算符重载
Num c = a + 1 + b;
// 编译后
Num c = a.plus(1).plus(b);

对于 +=、-= 这些,Manifold 也支持。

比较操作符

Java中进行比较用的是 Comparable<T>。只需类实现 Comparable<T>接口,就可以直接使用 >、>=、<、<= 这四个比较操作符的重载:

图片

Num 实现 Comparable<Num>:

public class Num implements Comparable<Num> {

    ...
    
    @Override
    public int compareTo(Num that) {
        return this.v - that.v;
    }
}

那么对于这样的代码:

Num a = new Num(1);
Num b = new Num(2);

if (a > b) {
    System.out.println("a");
} else {
    System.out.println("b");
}

运行代码会输出 b,因为代码在被 Manifold 处理之后会被编译为:

if (a.compareTo(b) > 0) {
    System.out.println("a");
} else {
    System.out.println("b")
}

当然 == 和 != 也是支持的,只需要实现ComparableUsing<T>这个接口,并覆写 equals:

public class Num implements ComparableUsing<Num> {

    ...

    @Override
    public boolean equals(Object obj) {
        if (this == obj) { return true; }
        if (obj instanceof Num) {
            Num that = (Num) obj;
            return this.v == that.v;
        }
        return false;
    }
}

则此时我们对 == 和 != 进行了重载,并且使用的是基于 equals 方法的实现。那么对于下面的代码:

Num a = new Num(1);
Num b = new Num(1);

if (a == b) {
    System.out.println("相等");
} else {
    System.out.println("不等");
}

运行代码会打印相等,因为 Manifold 处理之后的代码会编译为:

if (a == b  a != null && b != null && a.compareTousing(b, Operator.EQ)) {
    System.out.println("相等");
}else {
    System.out.println("不等");
}

Warning:ComparableUsing继承于Comparable,则表示该对象在设计时是可比较的。从Java的设计哲学来说,equals()表示两个对象是否相等的意义很抽象,而这两个对象可能是数值,也可能是车、订单或者其他什么东西。使用时一定要谨慎,不要滥用重载==和!=。

索引操作符

因为 List 已经具备了这两个方法,所以有了 Manifold,可以这样写:

List<String> list = Arrays.asList("q","w","e");
//获取第 1个元素
String first = list[0];
// 替换第 1 个元素
list[0] ="A";

而Map则需要手动添加扩展方法来实现:

@Extension
public class MapExt {

    public static <K, V> V set(@This Map<K, V> map, K key, V value) {
        return map.put(key, value);
    }
}

然后我们可以这样写:

Map<StringInteger> map = new HashMap<>();
map["a"] = 1;
map["b"] = 2;
map["c"] = 3;

后记

实际上,Manifold的定位是一个编译工具,通过编译来减轻Java繁琐的语法或实现语法糖,可用之处不仅仅是操作符重载,它还支持扩展方法、代理等其他功能。

那么为什么不考虑学习一下Kotlin或者Scala呢?

参考链接

  1. manifold-Github

Java实现操作符重载
https://note.0moe.cn/Java/2020/12/09/Java实现操作符重载/
作者
Dawn_南风
发布于
2020年12月9日
许可协议