Is `SomeClass::class.java.kotlin === SomeClass::class` guaranteed? What identity guarantees do the .java/.kotlin conversions give?
answer
- Compare KClass with ==, not ===
- java.lang.Class is canonical per classloader (=== holds)
- Round-trip KClass->Class->KClass is == equal
- Class identity = (name, classloader)
- .java/.kotlin are cheap; member reflection needs kotlin-reflect
basics
~10 sRound-tripping a class through .java and back to .kotlin gives you an equal KClass, and comparing with == works. The conversions are cheap lookups, not new copies of your data.
solid answer
~40 s`KClass` instances compare correctly by `==` (equals/hashCode are well-defined and based on the underlying type), so `SomeClass::class == SomeClass::class.java.kotlin` is reliably true. Reference identity (`===`) of the *KClass wrapper* is **not** something to rely on — Kotlin may hand out different `KClassImpl` wrapper instances for the same class, so prefer `==`. On the Java side, `java.lang.Class` objects *are* canonical per (class, classloader): `SomeClass::class.java === SomeClass::class.java` holds because a classloader returns the same Class object. The `.java`/`.kotlin` properties are lightweight — `.java` reads an already-resolved Class handle, and `.kotlin` wraps an existing Class — neither performs heavy parsing (that only happens when you touch members, which needs kotlin-reflect).
code
kotlin · 6 linesval k1 = String::class
val k2 = k1.java.kotlin
println(k1 == k2) // true (equals)
// k1 === k2 is not guaranteed
println(String::class.java === String::class.java) // true (canonical Class)go deeper
Knows round-tripping a class and comparing with == gives a match and conversions are cheap.
Distinguishes == from === for KClass and knows java.lang.Class is canonical per classloader.
Explains classloader-scoped identity and why frameworks key maps on Class, plus the lazy/kotlin-reflect cost boundary.
Connects classloader identity to hot-reload/app-server ClassCastException pitfalls and chooses stable keys deliberately.
## What round-tripping means Starting from a Kotlin class, you can go `KClass -> Class -> KClass`: ```kotlin val k1 = String::class val k2 = k1.java.kotlin k1 == k2 // true (use equals) k1 === k2 // not guaranteed (wrapper identity) ``` ## Equality vs reference identity - **`==` (equals):** `KClass` defines a sensible `equals`/`hashCode` keyed on the underlying JVM type, so two KClass objects describing the same class are **equal**. Always compare KClasses with `==`. - **`===` (reference identity):** Kotlin does **not** promise to return the *same* `KClass` wrapper object every time. The reflection layer can create fresh wrapper instances, so `===` on KClasses is fragile — don't depend on it. ## The Java side is canonical `java.lang.Class` instances are **canonical per classloader**: a given classloader loads a class once and always returns that same `Class` object. Therefore: ```kotlin String::class.java === String::class.java // true ``` This is why frameworks safely use `Class` objects as map keys. If you need identity-based lookups, the `Class` (not the KClass wrapper) is the stable key. ## Classloader caveat "Same class" is really "same (binary name, defining classloader)". The same `.class` loaded by two different classloaders produces two **unequal** `Class` objects (and two unequal KClasses). This is the classic source of `ClassCastException` in app servers / hot-reload setups. ## Cost of the conversions Both properties are cheap: - `.java` returns the JVM Class handle the runtime already has — no parsing. - `.kotlin` constructs/looks up a thin KClass wrapper around an existing Class. The expensive part of Kotlin reflection — reading members, supertypes, annotations from the `@Metadata` — happens lazily and **requires the kotlin-reflect artifact**. Plain `::class`, `.java`, and `.kotlin` are stdlib-only. ## Takeaway Round-trip safely with `==`; treat the underlying `java.lang.Class` as the identity-stable key; remember classloaders define class identity.
- Should you use == or === to compare two KClass values?Use ==. KClass defines equals/hashCode by the underlying type; === wrapper identity isn't guaranteed.
- When can two Class objects for the 'same' class be unequal?When loaded by different classloaders — class identity is (binary name, defining classloader), a common cause of ClassCastException.
Two photocopies of the same ID read identical (==), but they aren't the literal same sheet (===); the government's master record (java.lang.Class) is the one canonical original.
saying these in an interview costs you the question
- Relying on === to compare KClass instances
- Assuming the same .class always yields one Class regardless of classloader
- Thinking .kotlin re-parses metadata (it doesn't; member access does)
- Believing round-trip breaks equality