Kotlin logo

Kotlin

A concise multiplatform language developed by JetBrains

Language design

Call for Feedback: Java Statics, Result Expressions and More

Thank you all for the feedback we got on the previous call! Here comes another round of changes and adjustments. Your opinions and use cases are welcome.

Java Statics and Inheritance

We are going to improve interoperability with Java statics in Kotlin by allowing Kotlin subclasses to access static members of their superclasses: we will now be able to use constants, nested classes and static utilities defined up the inheritance tree. Same for members of companion objects of supertypes.

We will also allow calling Java statics on Java classes that inherit them.

What won’t be allowed is calling an inherited static member on a Kotlin class:

// Java
public class Base {
    public static void foo() {}
}

// Kotlin
class Derived : Base() {
    fun test() {
        foo() // OK
        Base.foo() // OK

        Derived.foo() // ERROR!
    }
}

Lateinit val’s

With the help of our users we found that we have previously missed an unpleasant hole in the design of lateinit val: backing fields for such properties were not final, and thus Java code could modify them freely. That makes the val-ness of such properties vanish, because no code can ever assume any immutability on them, so we decided to take this feature back: from now on, only vars can be marked lateinit. We’ll keep thinking on use cases and improvements of this feature.

Backing fields and custom setters

An addition to the previously announced change in backing field syntax: if a property has a custom setter or is open, the setter may be reading the field before writing it, so there’s no syntax for initializing this property for the first time, unless it’s done upon declaration. So, we now require initializers for such properties:

var foo: Foo? = makeFoo() // initializer required
    set(v) {
        if (field != null)
            notifyListeners()
        field = v
    }

If we really need to initialize such a property in the constructor, we’d have to introduce a backing property:

private var _foo: Foo?
var foo: Foo?
    get() = _foo
    set(v) {
        if (_foo != null)
            notifyListeners()
        _foo = v
    }

init {
    _foo = ...
}

Type parameter declarations

Historically, Kotlin had two forms of syntax for type parameter declarations for functions:

fun <T> T.foo() = ...

fun T.foo<T>() = ...

Two is too many, so we decided to keep only the first one, because it places the declaration of T before its usages, which is easier to read and code completion will be more helpful.

Visibilities of subclasses and elements of declarations

It’s a technical requirement, but rather intuitive: if something is public, it should not, for example, expose a private type:

private open class Super

class Public : Super() {
    private class Private

    fun public(p: Private): Private = ...
}

From now on, public classes can’t extend private ones, and public members can’t expose private types.

More formally: a supertype or an element of a declaration must be (effectively) at least as visible as the declaration/class itself.

Marking result expressions

This one is not really decided, and very debatable indeed, but it can’t be added after 1.0, so we have been thinking about it for some time:

As some of you rightfully observed, it may be difficult at times to see what expressions are used as results of blocks or lambdas.

We are considering prefixing such result expressions with the ^ symbol (or maybe some other prefix) to make them visible, in the following cases:

  • expression is a result of a multi-line block or lambda, AND
  • its type is not Unit, nor Nothing.

Example (from the Kotlin code base):

val getter = target.getGetter() ?: run {
    val defaultGetter = DescriptorFactory.createDefaultGetter(target, Annotations.EMPTY)
    defaultGetter.initialize(target.getType())
    ^defaultGetter
}

or

 private fun transformTryCatchBlocks(methodNode: MethodNode, newTryStartLabels: HashMap<LabelNode, LabelNode>) {
     methodNode.tryCatchBlocks = methodNode.tryCatchBlocks.map { tcb ->
         val newTryStartLabel = newTryStartLabels[tcb.start]
         ^if (newTryStartLabel == null)
             tcb
         else
             TryCatchBlockNode(newTryStartLabel, tcb.end, tcb.handler, tcb.type)
     }
 }

Note that simply highlighting result expressions in the IDE is not really a solution:

  • It does not prevent accidentally changing results of lambdas or block by adding a harmless-looking println() at the end,
  • It doesn’t work on GitHub, in Terminal or anywhere outside the IDE

We did a quick experiment on the Kotlin codebase, and added all the necessary prefixes:

See diff on github

The diff above will give you a sense of what such code might look like.

To give you an idea of how often this will occur in the code: we got 493 lines changed out of about 230’000 lines of Kotlin code (0.21%), and 233 files were changed out of 2190 Kotlin files we have in our code base (every 10th file).

image description