Kotlin 1.4 has no major updates, more details and optimizations.

1. Install Kotlin 1.4

The first milestone release of Kotlin 1.4 has been released. You can see the release details here.

It’s best to still use a stable version of Kotlin (such as the latest 1.3.71) in your production environment, and if you want to experience the new 1.4 features right away, So my advice is to install an EAP version of IntelliJ IDEA 2020.1 Beta, and then install the latest Kotlin plugin on this version of IntelliJ. This allows you to continue working on projects using 1.3 without losing time to experience new features:

Figure 1: The IntelliJ IDEA EAP version can coexist with the official version

Open Settings, search Kotlin, and select Early Access Preview 1.4.x from the plugin version management drop-down menu:

Figure 2: Upgrading the Kotlin plug-in

Ok, restart IntelliJ and create a new project

2. Major syntax updates

Let’s take a look at Kotlin 1.4-M1 Released and see what’s new.

Kotlin1.4FeaturesSample

2.1 SAM conversion of Kotlin interfaces and functions

One is the long-awaited SAM conversion of Kotlin interfaces and functions. Thanks to the new type derivation algorithm, SAM conversions used to be possible only when calling Java methods that receive Java’s single method interface. Now this problem is gone. Here’s an example:

    // Note that fun Interface is a new feature
    fun interface Action {
        fun run(a)
    }
    
    // The Kotlin function takes the Kotlin single method interface
    fun runAction(a: Action) = a.run()
    // The Kotlin function takes a single Java method interface as an argument
    fun runRunnable(r: Runnable) = r.run()
Copy the code

Prior to 1.4, we could only:

    runAction(object: Action{
        override fun run(a) {
            println("Not good..")}})Copy the code

or

    runAction(Action { println("Not good..")})Copy the code

The runRunnable function does not support SAM, even though it receives a Java interface.

Now in 1.4?

    runAction { println("Hello, Kotlin 1.4!") }
    runRunnable { println("Hello, Kotlin 1.4!")}Copy the code

It’s wonderful.

2.2 Type derivation supports more scenarios

Type derivation gives Kotlin’s syntax a great deal of brevity. However, when you use Kotlin to develop, you will find that there are certain cases where the compiler makes us explicitly declare the type, which is not covered by the type derivation algorithm.

For example, the following code will prompt a type mismatch in Kotlin 1.3:

    val rulesMap: Map<String, (String?) -> Boolean> = mapOf(
        "weak"to { it ! =null },
        "medium"to { ! it.isNullOrBlank() },"strong"to { it ! =null && "^[a-zA-Z0-9]+$".toRegex().matches(it) }
    )
Copy the code

Figure 3: Prompt type mismatch in Kotlin 1.3

The example given in the blog article may seem complicated at first, but if you think about it, the problem is that we can use rulesMap types to determine the return value types of mapOf, which in turn determines the parameter types of mapOf, the generic parameter types of Pair. The type information is sufficient, but this code could not be compiled before Kotlin 1.4, because the level of type derivation is a little too high to be covered by the algorithm. Fortunately, the new derivation algorithm solves this problem and can cope with more complex derivation scenarios.

2.3 Intelligent type conversion of the last line of a Lambda expression

This is easier to understand. Let’s just look at the example:

    val result = run {
       var str = currentValue()
        if (str == null) {
            str = "test"
        }
        str // the Kotlin compiler knows that str is not null here
    }
    // The type of 'result' is String? In Kotlin 1.3 and String in Kotlin 1.4
Copy the code

Here result, as the return value of run, is actually the return value of run’s argument Lambda, so its type needs to be inferred from the type of STR.

In 1.3, the type of STR can be inferred to be String, because STR is a local variable and can be manipulated. The problem is that while STR is inferred to be String, the return type of the Lambda expression is not the inferred type String, but the declared type String of STR? .

This problem was solved in 1.4. Since STR can be inferred to be a String, the result of a Lambda expression is a String.

As a quick note, IntelliJ’s type hints seem to be buggy and are inconsistent in some cases:

Figure 4: Bug that appears to be type hints within IntelliJ lines

We can use the shortcut key to see the type of result is String, but the type prompt in the line is String? , but this does not affect the program.

Of course, some developers often complain about things like this:

    var x: String? = null
    
    fun main(a) {
        x = "Hello"
        if(x ! =null){
            println(x.length) 
        }
    }
Copy the code

I already know x is not null, why can’t I automatically derive it to String? It is important to note that this is not a case of the type derivation algorithm, but that the type of x really cannot be derived, because for a shared variable, any previous second judgment cannot be used as a basis for the next second.

2.4 Type support for functions with default arguments

If a function has default arguments, we can call it without passing them, for example:

    fun foo(i: Int = 0): String = "$i!"
Copy the code

It can be called either foo() or foo(5), and looks like two functions. (Int) -> String (Int) -> String (Int);

    fun apply1(func: () -> String): String = func()
    fun apply2(func: (Int) -> String): String = func(42)
    
    fun main(a) {
        println(apply1(::foo))
        println(apply2(::foo))
    }
Copy the code

Note, however, that in general ::foo is always of type (Int) -> String, except when passed as an argument to receive () -> String, the compiler automatically helps with the conversion.

2.5 Type derivation of the property broker

In the past, the type of the property broker is not considered when inferring the type of the proxy expression, so we often need to explicitly declare generic parameters in the proxy expression, as in the following example:

    import kotlin.properties.Delegates
    
    fun main(a) {
        var prop: String? by Delegates.observable(null) { p, old, new ->
            println("$old$new")
        }
        prop = "abc"
        prop = "xyz"
    }
Copy the code

This example works in 1.4, but in 1.3, you need to specify the generic type:

    var prop: String? byDelegates.observable<String? > (null) { p, old, new ->
        println("$old$new")}Copy the code

2.6 Mix positional parameters and named parameters

Positional arguments are arguments that are passed in by position, and there are only positional arguments in Java, which is the most familiar way to write it. Kotlin supports named parameters, so what happens when you use a mix of the two?

In Figure 5. 1.3, positional parameters are not allowed after named parameters

In 1.3, the third parameter will prompt an error because the positional parameter is already preceded by a named parameter, which is forbidden. The main goal is to avoid confusing input examples, but this example doesn’t seem to be confusing, so in 1.4 we can follow the named parameter with the positional parameter.

In fact, this feature does not have much effect on the input parameters. First, the position of the positional parameter must still be corresponding, and second, the position of the named parameter must not be messed up. For example, let’s add a default value for a in our example:

In Figure 6. 1.4, adding positional parameters after named parameters should ensure the corresponding positions

Note that Figure 6 is in the 1.4 environment, so we don’t have to explicitly pass in the value of a when we call. My intuition tells me that the argument b should be followed by c, but the compiler doesn’t appreciate it. So, even in 1.4, we need to make sure that the named parameter and the positional parameter correspond to the position of the parameter before adding the positional parameter after the named parameter.

Therefore, my personal suggestion is that it is best to give a named parameter in the case of many and easy to confuse parameters, and to use all positional parameters in the case of few parameters. Another piece of advice here is not to have too many parameters. More parameters means that the function is more complex and more likely to need refactoring.

2.7 Optimize property broker compilation

If you’ve ever written a property proxy class, you know that both get and set functions take a KProperty argument, which is actually the property being propped up. To get this parameter, the compiler generates an array of properties for the proxy, such as:

    class MyOtherClass {
        val lazyProp by lazy { 42}}Copy the code

After the compiled bytecode is decompiled:

    public final class com.bennyhuo.kotlin.MyOtherClass {
      static finalkotlin.reflect.KProperty[] ? delegatedProperties; static {};public final int getLazyProp();
      public com.bennyhuo.kotlin.MyOtherClass();
    }
Copy the code

Among them? The delegatedProperties array is what we call the array of properties that are being brokered. However, most property brokers don’t actually use KProperty objects, so generating this array indiscriminately is wasteful.

Therefore, in the case that the get and set functions of the property proxy class are implemented inline, the compiler can determine exactly whether the KProperty is being used. If it is not, then the KProperty object will not be generated.

If there are two classes of property proxies that use and do not use the KProperty object, then the generated array will contain only the KProperty object used in 1.4. For example:

    class MyOtherClass {
        val lazyProp by lazy { 42 }
        var myProp: String by Delegates.observable("<no name>") {
                kProperty, oldValue, newValue ->
            println("${kProperty.name}: $oldValue -> $newValue")}}Copy the code

Where myProp uses the KProperty object, lazyProp does not use the KProperty object, so the generated? }}}}}}}}}}}}}}}}}}}}}}}}}}}}}}}

2.8 Comma at the end of the parameter list

This is a small but very useful requirement. Let’s look at an example:

    data class Person(val name: String, val age: Int)
    
    fun main(a) {
        val person = Person(
            "bennyhuo".30)}Copy the code

The Person class has multiple arguments, and when you pass them, you see that the first argument is followed by a comma, and the last argument is not. That doesn’t seem like a problem, does it? Chances are you didn’t use too many lines to edit:

Figure 7: Multiline edit comma problem

This comma can sometimes get in the way, but the question of how each line can have a comma is much simpler:

Figure 8: Multi-line editing of all parameters

In addition to this scenario, when adjusting the argument list, for example if I put an ID at the end of Person, I also have to put a comma after the argument of age alone:

Figure 9: Add a comma to the original argument

At this point, I decided that id should come first, so I copied and pasted it, but I still had to change the comma. Of course, IntelliJ has a shortcut to swap lines directly and automatically handle commas, but overall it’s an interesting little feature.

Speaking of which, object literals in JavaScript also allow a comma after the last field:

Figure 10: Object literals in JavaScript

Note, however, that despite its strong ties to JSON, you are not allowed to put a comma after the last field of JSON (as well as the fields in quotes, of course).

2.9 Use continue and break in the when expression

The meanings of “continue” and “break” have not changed. They are still used in the loop, but they were not previously allowed in the when expression inside the loop. They had intended to use continue or break as fallthrough conditions in their when expressions, but they didn’t seem to have figured that out yet. They just didn’t want to delay the normal functionality of continue and break.

2.10 Optimization of tail recursive functions

Tail recursive functions are probably not used much, but there are two main optimization points

  • The default arguments to tail-recursive functions are initialized from left to right:

  • Tail-recursion functions cannot be declared as open, that is, they cannot be overridden by subclasses, because the form of tail-recursion functions explicitly requires that the last operation of the function must be called only by itself. Declaring tailrec in a parent class does not guarantee that the subclass will be able to correctly override it.

The default argument list initialization order for the tail-recursive function in Figure 11:1.4

2.11 Contract support

Starting in 1.3, Kotlin introduced an experimental feature Contract to deal with type derivation or intelligent type conversion in “obvious” situations.

In 1.4, this feature will remain experimental, but with two improvements:

  • Support for implementing contracts using inline specialized functions
  • In 1.3, it is not possible to add contracts for member functions, and since 1.4, it is possible to add contracts for final member functions (of course, any member functions may be overridden, so it is not possible to add contracts).

2.12 Other changes

In addition to the obvious syntactic changes, the experimental apis for coroutines in 1.1 and 1.2 are also removed in 1.4. If possible, obsolete coroutine apis should be removed as soon as possible. You can also use the coroutine compatibility package kotlin-Coroutines-experimental-compat.jar.

All that remains is to optimize for the compiler and the experience, which is actually the most important part of Kotlin 1.4. These are fairly abstract, so I won’t go into them.

In addition, during the writing of this article, I used IntelliJ IDEA 2019.3.3 to run Kotlin 1.3, IntelliJ IDEA 2020.1 BETA to run Kotlin 1.4-M1, It turns out that the latter code seems to have a significant increase in the speed of the prompt, I don’t know if I am not illusion, you can feel and comment.

3. Summary

Kotlin’s grammar is now mature, but again, it’s important to improve the development experience and expand the application scenario.

The future is foreseeable.

If you want to get a quick start on Kotlin or learn more about Kotlin in a comprehensive and in-depth way, you can follow my new course based on Kotlin 1.3.50. The first version of the course helped more than 3000 students master Kotlin. This update is even more exciting:

Scan the QR code or click on the linkKotlin from Beginner to MasterReady to enter the course!

For Android engineers who want to find a good Offer and want to take the technology further, take a look at my new course crack Android Advanced Interview. There are 700+ students studying. What are you waiting for?

Scan the QR code or click on the linkCrack the Android Advanced InterviewReady to enter the course!