Consider this code:
fun main(args : Array<String>) {
println("Async" == MetricCategory.Async.toString())
println("Async" === MetricCategory.Async.toString())
}
It outputs
true
true
while i was expecting
true
false
Why is true
printed for the 2nd check since both references are different
Reference equality isn't that the variable name is the same, or it's accessed in the same way, it's that the location in memory is the same. As Strings are immutable, the compiler is often able to reserve memory in advance for them, and have all references to the same value point to the same place.
The immutability is important, because it's safe to share read-only references in cases where read/write references would be different. If you improperly share references between mutable data structures, modifications from one set of references will be reflected in the other, leading to strange and incorrect behavior. However, if the data can no longer change, you're free to save as much memory as possible by having everything point to the same data.
Depending on how MetricCategory.Async.toString()
is implemented the outcome of the operation might be arbitrary. Consider following example:
class MetricCategory {
object Async {
override fun toString(): String {
return "Async"
}
}
}
This implementation would result in true
, true
printed out. As documented the ===
operator compares referential equality:
evaluates to true if and only if a and b point to the same object.
But why are the 2 constant string expressions the same object? This is caused by a feature of JVM (and other runtimes) called string interning:
In computer science, string interning is a method of storing only one copy of each distinct string value, which must be immutable. Interning strings makes some string processing tasks more time- or space-efficient at the cost of requiring more time when the string is created or interned. The distinct values are stored in a string intern pool.
String interning does not happen automatically in JVM but it can be triggered manually.
class MetricCategory {
object Async {
override fun toString(): String {
val result = "a".toUpperCase() + "SYNC".toLowerCase()
return result.intern()
}
}
}
The above example would print true
, true
again but only because we've called String.intern
.
Consider below examples:
println("Async" == "Async") // true, obviously
println("Async" === "Async") // true, string interning for literals
println("Async" == java.lang.String("Async").toString())// true, obviously
println("Async" === java.lang.String("Async").toString()) // false, new instance on the right
println("Async" === java.lang.String("Async").toString().intern()) // true, right changed to shared instance
Further reading:
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With