英文:
Equals overloading
问题
Kotlin的等于操作符重载根本不起作用(运算符'!='不能应用于'SettingString'和'String'):
class SettingString(var selected) {
override infix fun equals(other: Any?) = other is String && selected == other
}
fun arbitraryFunction() {
val mode = SettingString("Boost")
mode != "Boost" // 错误
}
英文:
Kotlin's Equals OPERATOR overloading simply doesn't work (Operator '!=' cannot be applied to 'SettingString' and 'String'):
class SettingString(var selected) {
override infix fun equals(other: Any?) = other is String && selected == other
}
fun arbitraryFunction() {
val mode = SettingString("Boost")
mode != "Boost" // error
}
答案1
得分: 3
以下是翻译好的部分:
这是有意为之的:
> Kotlin在编译时检查值相等运算符的适用性,可能会拒绝某些A
和B
类型的组合。具体来说,它使用以下基本原则。
>
>> 如果A
的类型和B
的类型明确不同且没有通过子类型关系相关联,则A == B
是无效的表达式,应该导致编译时错误。
>
>> 非正式地说:这个原则意味着“没有两个不通过子类型关系相关的对象可以被==
视为相等”。
因为SettingString
和String
是完全独立且不相关的类型,编译器甚至不会尝试比较它们。这意味着,如果您试图隐式比较两个完全不同的对象,这将是一个错误。
您始终可以显式地使用equals()
,或者使用不同的关键字编写自己的中缀运算符。或者出于可读性考虑,使用类型别名。值/内联类可能会遇到相同的问题,因为它编译成一个新类型(尽管未来可能会更改?)
英文:
That's by design:
> Kotlin checks the applicability of value equality operators at compile-time and may reject certain combinations of types for A
and B
. Specifically, it uses the following basic principle.
>
>>If type of A
and type of B
are definitely distinct and not related by subtyping, A == B
is an invalid expression and should result in a compile-time error.
>
>>Informally: this principle means “no two objects unrelated by subtyping can ever be considered equal by ==
”.
Because SettingString
and String
are completely separate, unrelated types, the compiler won't even try to compare them. The implication is if you're trying to implicitly compare two completely different objects like this, it's an error.
You can always can equals()
explicitly, or maybe write your own infix operator using a different keyword. Or use a type alias if it's for readability. A value/inline class will probably run into the same problem, since it compiles to a new type (that might change in future though?)
答案2
得分: 3
以下是翻译好的内容:
"equality"操作符周围有一些编译器魔法——只要equals()
的实现表现正常,这就有很好的道理。
首先,请注意,在这种情况下,您可以按名称调用equals()
,它将如预期地使用您的覆盖方法。
然而,尽管它编译通过,但它的行为不正确:您的equals()
方法不满足其合同。
首先,它不是_自反的:a.equals(a)
应该始终返回true,但您的方法始终返回false。
其次,它不是_对称的:a.equals(b)
应该始终产生与b.equals(a)
相同的结果,但在您的示例中,mode.equals("Boost")
返回true,而"Boost".equals(mode)
返回false。(毕竟,系统String类对您的代码一无所知。它怎么可能知道呢?)
不遵循equals()
合同可能导致各种奇怪的问题和意外的行为。(例如,您可能在集合或映射中获得重复的值,它们的元素可能会看似随机地消失和重新出现,或者迭代器可能永远不会完成。)此类错误可能非常难以追踪。
通常情况下,允许不同类的对象进行相等比较是不安全的。(如果您控制两个类及其所有公共超类,有时可以这样做,但很难正确实现;请参阅这篇出色的文章以获取详细信息。)
因此,尽管您的equals()
方法编译通过,但恐怕它存在致命缺陷。
由于如果它们行为正常,不相关的类永远不会比较为相等,因此Kotlin编译器使等式操作符==
和!=
比equals()
方法本身更严格:**如果编译器可以确定这两个对象不能相关联,它会阻止您甚至尝试比较它们。**毕竟,它们永远不会比较为相等(假设它们行为正常),所以为什么要尝试呢?
尝试这样做很可能是代码中的错误——而由于Kotlin在编译时努力识别尽可能多的错误,因此这是出于安全考虑的编译错误。(如果确实需要,您总是可以显式调用equals()
——虽然更冗长,但这应该提醒您可能有问题!)
正如cactustictacs的答案所指出的那样,这在语言规范中有详细说明。
(顺便说一下,编译器特殊对待这些操作符不应该让人感到意外,因为它必须这样做以允许null参数;null == something
是有效的,即使null.equals(something)
不是。)
英文:
There's a bit of compiler magic around the equality operators — which makes good sense as long as implementations of equals()
behave as they should.
First, note that you can call equals()
by name in this case, and it will use your override method as expected.
However, although it compiles, it doesn't behave correctly: your equals()
method doesn't fulfil its contract.
For one thing, it's not reflexive: a.equals(a)
should always return true, but your method always returns false.
And for another, it's not symmetric: a.equals(b)
should always give the same result as b.equals(a)
, but in your example, mode.equals("Boost")
returns true, whereas "Boost".equals(mode)
returns false. (After all, the system String class knows nothing about your code. How can it?)
Failure to follow the equals()
contract can cause all sorts of bizarre problems and unexpected behaviour. (For example, you could get duplicate values in a set or map, or their elements could seem to disappear and reappear at random, or iterators might never finish.) Such bugs can be very hard to track down.
In general, there's no safe way to allow objects of different classes to compare as equal. (It can sometimes be done if you control both classes and all their common superclasses, but it's very difficult to get right; see this excellent article for all the gory details.)
So while your equals()
method compiles, I'm afraid it's fatally flawed.
Because unrelated classes will never compare as equal if they're well-behaved, the Kotlin compiler makes the equality operators ==
and !=
more restrictive than the equals()
method itself: if the compiler can tell that the two objects can't be related, it prevents you from even trying to compare them. After all, they're never going to compare as equal (assuming they're well-behaved), so why even try?
Trying to do so is likely to be a bug in the code — and since Kotlin works hard to spot as many bugs as possible at compile time, it makes this a compile error for safety. (You can always call equals()
explicitly if you really need to — it's more verbose, but that should be a reminder that something may not be right!)
As cactustictacs' answer points out, this is detailed in the language spec.
(BTW, it shouldn't be a surprise that the compiler treats those operators specially, because it has to do so to allow null arguments; null == something
is valid even though null.equals(something)
isn't.)
答案3
得分: 1
Comparison with an object not being a SettingString too needs a cast with 'as Any':
class SettingString(private var selected: String) {
override infix fun equals(other: Any?): Boolean {
if (other is SettingString) {
return other == selected
}
return other is String && selected == other
}
override fun hashCode(): Int {
return selected.hashCode()
}
}
val mode = SettingString("Boost")
val other = SettingString("Boost")
mode != other // This obviously works
val other2 = "Boost"
mode != other2 // Doesn't work
mode != other2 as Any // This does work
!(mode equals "Boost") // This does work also
See the discussion here: https://discuss.kotlinlang.org/t/overloading-with-different-types-of-operands/4059/17
英文:
Comparison with an object not being a SettingString too needs a cast with 'as Any':
class SettingString(private var selected: String) {
override infix fun equals(other: Any?): Boolean {
if (other is SettingString) {
return other == selected
}
return other is String && selected == other
}
override fun hashCode(): Int {
return selected.hashCode()
}
}
val mode = SettingString("Boost")
val other = SettingString("Boost")
mode != other // This obviously works
val other2 = "Boost"
mode != other2 // Doesn't work
mode != other2 as Any // This does work
!(mode equals "Boost") // This does work also
See the discussion here: https://discuss.kotlinlang.org/t/overloading-with-different-types-of-operands/4059/17
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论