Java实现操作符重载
操作符重载,就是把已经定义的、有一定功能的操作符进行重新定义,来完成更为细致具体的运算等功能。
从面向对象的角度说,就是可以将操作符定义为类的方法,使得该操作符的功能可以用来代表对象的某个行为。
从DSL的角度说,就是用操作符来代替部分语法,提高DSL的可理解性与可读性。
背景
我们来考虑实现这样的功能:使用 BigInteger 来计算(a^2 + b^2)
常规写法:
BigInteger res = a.multiply(a).add(b.multiply(b));假设可以对 *、+ 进行操作符重载,那可以直接这样写:
BigInteger res = a * a + b * b;所以,对于非原始类型的数值运算,如果能够进行操作符重载,至少有 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<String,Integer> map = new HashMap<>();
map["a"] = 1;
map["b"] = 2;
map["c"] = 3;后记
实际上,Manifold的定位是一个编译工具,通过编译来减轻Java繁琐的语法或实现语法糖,可用之处不仅仅是操作符重载,它还支持扩展方法、代理等其他功能。
那么为什么不考虑学习一下Kotlin或者Scala呢?