Java 方法在 kotlin 中错误地自动重载

Java methods wrongly get automatically overloaded in kotlin

给定一个 Java 库,其中包含以下内容(精简)class:

public class Vector2f {
    public float x;
    public float y;

    public Vector2f div(Vector2f other) {
        x /= other.x;
        y /= other.y;
        return this;
    }

    public Vector2f div(Vector2f other, Vector2f dest) {
        dest.x = x / other.x;
        dest.y = y / other.y;
        return dest;
    }

    /* ... */
}

由于kotlin会自动将合适的方法名转换为重载运算符,我可以这样写

val v0 = Vector2f(12f, 12f)
val v1 = Vector2f(2f, 2f)

val res = v0 / v1

println(v0)
println(v1)
println(res)

res.x = 44f
println()

println(v0)
println(v1)
println(res)

...完全出乎意料的结果是 v0 被中缀除法操作变异了。而且,此外,存储在 res 中的引用指向与 v0 输出相同的对象:

Vector2f(6.0, 6.0)
Vector2f(2.0, 2.0)
Vector2f(6.0, 6.0)

Vector2f(44.0, 6.0)
Vector2f(2.0, 2.0)
Vector2f(44.0, 6.0)

由于库还提供了将结果写入另一个向量的重载,我想知道我是否可以 'tell' kotlin 不使用提供的 Vector2f.div(Vector2f) 方法。

我已经尝试为 Vector2f 提供扩展方法,但它被真正的成员隐藏了:

operator fun Vector2f.div(other: Vector2f): Vector2f = this.div(other, Vector2f())
                      ^~~ extension is shadowed by a member: public open operator fun div(other: Vector2f!): Vector2f!

理想情况下,您可以更改 Java class 以使其运算符函数更好地遵守约定。如果您无法修改原始 class,您将始终可以在使用 Kotlin 时使用 / 运算符。

因为它在原件中 class,您将无法用扩展函数覆盖它,原件将始终优先于它,无论扩展在哪里声明或如何导入.

您要么必须接受可用的语法,要么如果您真的不能使用它,您可以围绕 Vector2f 创建一个包装器 class,它没有该名称的功能公开可用。

我正在 glm 端口上工作 here

对于您的示例,相关代码是 here

operator fun div(b: Float) = div(Vec2(), this, b, b)
operator fun div(b: Vec2) = div(Vec2(), this, b.x, b.y)

fun div(bX: Float, bY: Float, res: Vec2 = Vec2()) = div(res, this, bX, bY)
fun div(b: Float, res: Vec2 = Vec2()) = div(res, this, b, b)
fun div(b: Vec2, res: Vec2 = Vec2()) = div(res, this, b.x, b.y)

fun div_(bX: Float, bY: Float) = div(this, this, bX, bY)
infix fun div_(b: Float) = div(this, this, b, b)
infix fun div_(b: Vec2) = div(this, this, b.x, b.y)

背后的逻辑很简单,参考你的样本,shortest/simplest代码:

val v0 = Vec2(12f)
val v1 = Vec2(2f)

val res = v0 / v1

总是 创建一个新实例。这在某种程度上遵循了同样写在 Kotlin docs 上的指南。 inc()dec() 部分仍然存在(同时进行了修改)。

也是尽可能少出错的形式(个人经验..)

对于性能关键的场景,当你不想分配一个新的实例时,妥协是放弃运算符重载并使用函数形式:

v0.div(v1, res)

也就是说,用v0除以v1,结果放在res

如果您希望接收方对象直接改变和容纳结果:

v0 div_ v1

这背后的想法是利用下划线 _ 和等号 = 背后的相似性,并将 div_ 解释为 /=