Java 运算符重载(Operator Overloading)原理

2020-12-04 13:40:25 +08:00
 Braisdom

Java 语言的出现是为了降低 C++ 的开发成本和学习难,但也将 C++ 一些非常有价值的特性搞丢了,运算符重载就是一个非常优秀的特性,但 Java 搞丢了,虽然运算符重载不是一个常用特性,但随着数据分析领域的发展,表达式的编译也就有了强烈的需求。

首先,我解释一下为什么需要运算符重载,Java 是一个严谨的逻辑性编程语句,有丰富的工程化集成能力,通常 Java 编译后的代码只是 JVM 中运行,但也会在其它的可执行单元中运行,常见的有:数据库(关系型、对象型、KV 型和预计算引擎等)、也可通过是一次远程服务调用。倘若这类可执行单元的协议中存在表达式(算术、比较和逻辑),这也就意味着在 Java 中需要通过字符串的形式表现这类表达式,此时就会出现下列的代码:

目标输出协议:

"(sum(order.no) + 100) > 1000 and order.id < 999"

构造协议的 Java 代码:

and(gt(plus(funCall("sum", "order.no"), 100)), lt(fieldCall("order", "id"), 999));

对比一下将计算表达式转换为 Java 中的方法调用,变得极难理解,但这种方式也是目前大都 ORM 框架所使用的方式,这样做的方式可以使得构造过程动态化,而不是简单的字符串拼接,当然其它内部原理还是字符串拼接。

在这样的使用场景下,如果 Java 的表达式支持重载则会变的非常简单,下面是 ObjectiveSQL 的代码:

Order.Table order = Order.asTable();

Expression expr = (sum(order.no) + 100) > 1000 && order.id < 999;

其中的Expression 是实现了 Java 运算符重载,内部有种数学计算、比较和逻辑计算的相关方法,最终也是通过拼接字符串的形式输出上述目标协议。

详细可参考: https://github.com/braisdom/ObjectiveSql

如果认可项目,请点个赞,欢迎交流...

4918 次点击
所在节点    程序员
48 条回复
Braisdom
2020-12-04 14:59:26 +08:00
@no1xsyzy 不同的思考维度,肯定不是某一个,或某一些原因。

我们生活的世界有两个,一个是真实运行的世界,还有一个是脑子里的世界(也就是“应该”的世界),但两都往往偏差很大
Braisdom
2020-12-04 15:00:22 +08:00
@Vedar 同意,我很敬佩 C++ 的设计者,他们能解决我们“不知道我们不知道的事情”
ximigou007
2020-12-04 15:02:19 +08:00
当你熟练掌握一个东西你就想要灵活性,当你不熟练的时候灵活性就是灾难
no1xsyzy
2020-12-04 15:02:59 +08:00
@lewis89 所以我都标了 “(ry” 了……
Perl 也火了好一段…… 这可是经典的 TIMTOWTDI 语言。
重载是 DSL-ish 做法,你这么说的话,其实所有 DSL 都可以下岗了。
ximigou007
2020-12-04 15:03:13 +08:00
重载不重载本质上是设计哲学的冲突,总会有 trade off
no1xsyzy
2020-12-04 15:15:33 +08:00
@Braisdom #19 路线预测我就不清楚了。

所以我称为 DSL-ish 。 #0 举的例子确实发生了范式转移( paradigm shift ),从命令式的代码转换成了描述式的代码。保持 Java 的思维粗略地一看会彻底误解这段代码的作用。

#21 外部因素对语言成否的影响更大,这有统计上的实证,C - Unix,Python - Data Science,JavaScript - Web,至于 Java 跨平台方面始终不温不火,直到 Android 。
和理论上的解释,见 #18
cpstar
2020-12-04 16:38:39 +08:00
个人认为对于 OO 概念来讲,加减乘除运算符也不应该有,而是 Number.Add/Minus/Multiply/Divide,所以,重载,睡了吧
FireFoxAhri
2020-12-04 16:42:01 +08:00
可以了解一下 Scala 。。运算符也可以定义成函数
Braisdom
2020-12-04 17:29:48 +08:00
@ximigou007 我觉得是个人偏好的问题,只是 Java 的创始人不喜欢运算符重载,但它想的不够远
Braisdom
2020-12-04 17:33:29 +08:00
@cpstar 这是 OO 里最传统的想法,不能为了 OO 而去 OO,过于理论化的思考,而不考虑实际。

一个同环比的计算是最常见的:(sum(current) - sum(last)) / sum(last) * 100
这样的表达式用函数做就太复杂了
aguesuka
2020-12-04 18:04:17 +08:00
中缀表达式有 Monoid 之类的讲究,但是 java 没有静态检验的能力,社区也没有 plt 的气氛。java 一切皆对象,java 中的运算符都是纯函数,重载的运算符不管是实例方法还是静态函数都很尴尬。
kotlin 有中缀表达式,但是表达能力和 "实例.方法(参数,参数)"没有区别。我觉得你的需求应该自己定义语法,用 idea 的 language inject 做代码提示和静态分析。
Braisdom
2020-12-04 18:08:15 +08:00
@aguesuka 是的,所以我采用的是比较迂回的方法,先解析表达式,然后在编译期覆盖代码实现的

期待 JDK 后续变化,我提个 issue 给 JDK
wysnylc
2020-12-04 18:10:10 +08:00
lambda 可以实现运算符重载的一切功能
Braisdom
2020-12-04 18:11:07 +08:00
@wysnylc 语法太复杂,我需要的是最简单的表达式,没必要绕那么多弯
namelosw
2020-12-04 18:26:31 +08:00
运算符和方法本质区别不大, 像 Ruby Scala Haskell 之类的设计其实比较优雅. 比较尴尬的是 C++那种有限重载.

a + b 就是 a.+(b) 就是 a.add(b)

运算符在命名方面不会让语言可读性变得更差, 反而是该用运算符非得用字母比如.eq()之类的可读性不好.

但是运算符最大的问题是优先级容易乱. 像 LISP 之类强制括号的话, 其实无所谓是运算符还是字母.
secondwtq
2020-12-04 18:42:43 +08:00
这贴感觉开错了 ... 这标题就注定了下面必然会以 OT 为主 ...
优先级是个老问题,个人偏向于放弃优先级,强制括号
S-expr 也行,不过我习惯看中缀是改不过来了,我喜欢 S-expr 是因为——目前基本所有中缀运算符的语法,都必须硬点一部分符号给运算符用(就不说还要钦定优先级了),S-expr 只占括号和空格,其他符号都可以解放出来,甚至包括 hyphen

C++、Java 的运算符重载(以及函数重载)的问题很大程度也是钦定的东西太多了
Braisdom
2020-12-04 21:04:37 +08:00
@namelosw
@secondwtq

两位没有仔细看我帖子的内容,使用 运算符计算 和函数计算 在简单的时候没有区别,关键,如果一个表达式过于复杂会是个什么样子,例如:(1+1) * 1 / 1 > 1 && (2 + 2) * 2 /2 < 2 你们可以试一下这样一眼能看清的表达式通过函数什么是个什么样子
ximigou007
2020-12-04 21:04:42 +08:00
@Braisdom 问题是可以随意重载运算符的话,如果使用不当会有不可预知的风险,如果不可重载,所有结果是可以预期的
Braisdom
2020-12-04 21:05:55 +08:00
@ximigou007 不是随意重载,有兴趣看一下我的代码: https://github.com/braisdom/ObjectiveSql

我只是翻译
TheCure
2020-12-04 21:11:10 +08:00
本人 Python 程序员, 极度讨厌运算符重载

从这里面的代码看, string interpolation 可以解决一部分问题, 可以试试 kotlin

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/732083

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX