Walk through the exact Java-visible members generated for a companion containing a private const val, a public const val, and a @JvmStatic var. What surprises Java callers here?
answer
- private const -> invisible to Java
- public const -> static final, read directly, inlined
- const inlining -> stale value until recompile
- @JvmStatic var -> static getX()/setX()
- no @JvmStatic -> Companion.getX() hop
basics
~20 sA private const is invisible to Java. A public const becomes a static final field read directly (Foo.X). A @JvmStatic var generates static getX()/setX() on the class. The surprise: const is inlined, so changing it can leave stale values in compiled Java.
solid answer
~40 sMapping each: (1) `private const val` is a compile-time constant but with private visibility — it's inlined where used inside Kotlin and is NOT part of the Java-visible API, so Java can't read it. (2) `public const val MAX = 3` becomes `public static final int MAX = 3` on the OUTER class, read as `Foo.MAX`, with the value INLINED into every compiled consumer — the classic surprise: bumping the constant in a library doesn't change already-compiled Java callers until they recompile (binary-compat hazard). (3) `@JvmStatic var counter` generates static `getCounter()`/`setCounter(int)` on `Foo` (plus the Companion-instance accessors). Surprises: the const inlining; that `private const` vanishes from the API; and that a `var` without @JvmStatic forces `Foo.Companion.getCounter()`.
code
kotlin · 12 linesclass Foo {
companion object {
private const val SECRET = 42
const val MAX = 3
@JvmStatic var counter: Int = 0
}
}
// Java:
// int m = Foo.MAX; // static final, value 3 inlined
// Foo.setCounter(7); // @JvmStatic setter
// int c = Foo.getCounter(); // @JvmStatic getter
// Foo.SECRET; // NOT visible (private)go deeper
Knows public const is read directly and a plain var needs the Companion getter.
Lists the generated members correctly including @JvmStatic getter/setter and that private const isn't exposed.
Explains const inlining and its binary-compatibility consequences and recommends @JvmField/@JvmStatic to avoid it.
Treats public const changes as ABI events and sets policy for constants in shared/published Kotlin libraries consumed by Java.
## The declarations ```kotlin class Foo { companion object { private const val SECRET = 42 // private compile-time constant const val MAX = 3 // public compile-time constant @JvmStatic var counter: Int = 0 // mutable, static accessors } } ``` ## Member-by-member Java view ### `private const val SECRET` - `const` = compile-time constant; `private` limits visibility. - Inside Kotlin it's inlined at use sites; to JAVA it is effectively **invisible** (not part of the accessible API). Java cannot read `Foo.SECRET`. ### `public const val MAX` - Emitted as `public static final int MAX = 3` on the OUTER class `Foo`. - Java reads it directly: `int m = Foo.MAX;` — no getter, no `Companion`. - **Inlining surprise**: because it's a compile-time constant, the literal `3` is copied into every class that references it. If a library changes `MAX` to `5`, previously compiled Java consumers keep `3` until recompiled. This is the well-known constant binary-compatibility trap. ### `@JvmStatic var counter` - Generates static accessors on `Foo`: `public static int getCounter()` and `public static void setCounter(int)`. - The Companion-instance accessors also exist (`Foo.Companion.getCounter()`), and the generated statics delegate to the single companion instance, so state is shared. - Without `@JvmStatic`, Java would be forced to `Foo.Companion.getCounter()/setCounter()`. ## Summary of surprises 1. **`const` inlining** -> stale values in already-compiled Java until recompile. 2. **`private const`** -> not in the Java API at all. 3. **Plain `var`/`val` (no @JvmStatic, no @JvmField)** -> Java must hop through `Companion` and call getters. 4. A const value can only be a primitive or `String`; objects can't be `const`. ## Practical guidance - For published libraries, treat changing a public `const` like an ABI change. - Prefer `@JvmField`/`@JvmStatic`-backed accessors when you want runtime resolution (no inlining) and a stable contract. ## Key terms - **inlining**: copying a constant's value into the caller's bytecode at compile time. - **ABI / binary compatibility**: whether already-compiled callers keep working without recompiling. - **static accessor**: generated `getX()/setX()` static method.
- Why is bumping a public const a binary-compatibility hazard?Its value is inlined into compiled consumers, so they keep the old value until recompiled — runtime and source can silently disagree.
- How do you avoid the inlining trap but keep a simple Java field read?Use a non-const property with @JvmField (or @JvmStatic accessors); the value is resolved at runtime, not inlined.
saying these in an interview costs you the question
- Saying Java can read a private const
- Claiming const values are resolved at runtime
- Forgetting @JvmStatic generates both getter and setter for a var
- Thinking const works for arbitrary object types
- Ignoring the recompile-needed staleness of const changes