32

What’s New in Kotlin 1.3 [FREE]

 4 years ago
source link: https://www.tuicool.com/articles/RRRvQ33
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

Kotlin has come a long way since its initial release in July 2011. It was originally designed as a replacement for Java to be used by JetBrains’ programmers, who were frustrated by Java’s limitations and found that their preferred language, Scala , compiled too slowly. Since then, it has grown from an interesting side project, used by a handful of developers, to the Google-decreed preferred language for Android development.

Kotlin is making inroads into other platforms, with the the Kotlin-to-JavaScript transpiler for web development and Kotlin/Native, which aims to compile Kotlin into native applications that run on iOS, MacOS, Windows, Linux and WebAssembly.

It’s also making inroads into developers’ hearts and minds. HackerRank’s 2019 Developer Skills Report, based on their survey with over 70,000 participants, lists Kotlin as one of the top 3 programming languages that developers want to learn in 2019.

Kotlin 1.3 has been around since October 2018, but that doesn’t mean that it’s too late to take a closer look at the features that this latest version brings. Kotlin is still new to many developers, even those who develop Android apps for a living.

In this tutorial, you’ll learn about the most important changes in Kotlin 1.3.

The Kotlin Playground

One of the most useful recent programming trends is the playground. An interactive environment that lets you enter and then run snippets of code and see the results without having to set up a whole new project. They’re useful for learning a new language or framework and for answering “What if I tried this?” questions.

It’s a simplified online IDE that lets you enter Kotlin code, execute it and even share it by providing a link to your code, or as an embedded iframe that you can include on a web site.

Let’s try out the Kotlin Playground. Point your favorite browser at play.kotlinlang.org . You’ll be taken to a web page that looks like this:

3EjENzQ.png!web

Click the Run button to run the sample code that was provided by the Kotlin Playground. An output pane with the text Hello, world!!! will appear at the bottom of the page.

Before continuing, click the Settings button. This pop-up will appear:

2meEf2U.png!web

This lets you select which version of Kotlin will be used when you click the Run button. Select the latest 1.3 version in the Kotlin Version menu. At the time of writing, it’s 1.3.40 .

Now that you’ve got the playground set up, it’s time to try out some Kotlin 1.3 features!

Language Improvements

Main Function Without Arguments

Every program has an entry point when the program is executed. Thanks to the influence of C, the entry point for programs in many programming languages is called main() . It’s in every Kotlin program, even in an Android Studio project.

Another C convention borrowed by Kotlin is that main() takes an array of strings as its argument. This array contains the parameters that follow the program’s name when it’s run from the command line.

Until Kotlin 1.3, this was the only version of the main() function in Kotlin, and you’d have to write Hello, World! programs like this:

fun main(args: Array<String>) {
  println("Salutations, user!")
}

The single argument, args , contains the arguments that’re passed to the Kotlin program when it’s called from the command line.

In Kotlin 1.3, you’d simply ignore args . You can use a new form of main() that doesn’t take any arguments at all. This is useful for applications where you don’t expect command line parameters, including those you’d write in the Kotlin playground.

Run the following in the Kotlin Playground:

fun main() {
  println("Salutations, user!")
}

It works as you’d expect, without arguments.

Subject Capture

The Kotlin 1.3 improvement you’re most likely to use is the when keyword new subject capture capability.

Consider the following:

fun fastCheapOrGood(): String {
  return when (determineOutcome()) {
    0 -> "Fast."
    1 -> "Cheap."
    2 -> "Good."
    else -> "None of the standard outcomes."
  }
}

The subject is the value that comes after the when keyword. In the function above, the subject is determineOutcome() , and its value determines which of when ’s cases is applied.

Suppose you wanted to display determineOutcome() value in the else case. In Kotlin 1.3, there was no way to “capture” when ’s subject. The usual solution is to declare an extra variable like so:

val outcome = determineOutcome()
return when (outcome) {
  0 -> "Fast."
  1 -> "Cheap."
  2 -> "Good."
  else -> "None of the standard outcomes: the selection was $outcome."
}

This works, but it introduces two complications:

  1. It introduces a new variable whose scope exists outside the when block. This is fine if you want to use outcome beyond the scope of when , but introduces the risk of side effects if you don’t need the value of outcome anywhere else.
  2. It adds an extra line of code. That may not seem like such a bad thing, but consider Corbato’s Law : “Every programmer has a number of lines of code they can write in a day, and this number’s the same, no matter the programming language.” The take-away from this is that you should program in languages and ways that let you do as much as possible, with as little code as possible.

This is where Kotlin 1.3’s new subject capture for when comes in. It allows you to declare a variable that captures when ’s subject, and whose scope is limited to the when block:

return when (val outcome = determineOutcome()) {
  0 -> "Fast."
  1 -> "Cheap."
  2 -> "Good."
  else -> "None of the standard outcomes: the selection was $outcome."
}

Now, there’s one less line of code and one less stray variable.

Random Number

If you needed a random number generator for a desktop or mobile Kotlin project, you could always rely on the Java Virtual Machine’s Random class. But, with Kotlin expanding beyond Android and other Java-based systems, there needed to be a Kotlin-specific way to generate random numbers that works across platforms.

The solution is an abstract class that can implement any number of randomizing algorithms. If you don’t need a cryptographically secure random number generator, the default implementation provided by the Random.Default companion object should suit most needs.

Let’s take Kotlin’s Random for a spin. Run the following in the Kotlin playground a few times:

import kotlin.random.*

fun main() {
  println("${fastCheapOrGood()}")
}

fun fastCheapOrGood(): String {
  return when (determineOutcome()) {
    0 -> "Fast."
    1 -> "Cheap."
    2 -> "Good."
    else -> "None of the standard outcomes."
  }
}

fun determineOutcome(): Int {
  return Random.nextInt(6) // Generates a random number between 0 and 5 inclusive.
}

Random comes with many extension functions that save you from having to use an extra line to declare an index variable to pick a random item from an array or collection. Run this in the Kotlin Playground:

import kotlin.random.*

fun main() {
  val colors = arrayOf("amaranth", "coquelicot", "smaragdine")
  println("Random array element: ${colors.random()}")

  val flavors = listOf("maple", "bacon", "lemon curry")
  println("Random list item: ${flavors.random()}")
}

There’re also extension functions that you can use to pick a random character from a string or a random item from a range. Add the following to the end of main() and run the code again:

val pangram = "How razorback-jumping frogs can level six piqued gymnasts!"
println("${pangram.random()}")

val firstHalfOfAlphabet = 'a' .. 'l'
println("Random range item: ${firstHalfOfAlphabet.random()}")

Functions About Nothing

The isNullOrEmpty() and orEmpty() can now be used on more than just nullable strings. You can now use them to get similar results with arrays and collections.

Run the following in the Kotlin Playground:

fun main() {
  val nullArray: Array<String>? = null
  val emptyArray = arrayOf<String>()
  val filledArray = arrayOf("Alpha", "Bravo", "Charlie")

  println("nullArray.isNullOrEmpty(): ${nullArray.isNullOrEmpty()}") // true
  println("emptyArray.isNullOrEmpty(): ${emptyArray.isNullOrEmpty()}") // true
  println("filledArray.isNullOrEmpty(): ${filledArray.isNullOrEmpty()}") // false 
  println("nullArray.orEmpty(): ${(nullArray.orEmpty()).joinToString()}") // []
  println("filledArray.orEmpty(): ${filledArray.orEmpty().joinToString()}") // ["Alpha", "Bravo", "Charlie"]
}

Kotlin 1.3 also introduces two new functions about nothing. The first, ifEmpty() , lets you perform a lambda if the array, collection or string you apply it to contains no elements.

Add the following to the end of main() and run the code again:

val emptyString = ""
val nonEmptyString = "Not empty!"
println(emptyString.ifEmpty {"emptyString is empty"}) // emptyString is empty
println(nonEmptyString.ifEmpty {"emptyString is empty"}) // Not empty!

The second, ifBlank() , performs a lambda if the string you apply it to is nothing but whitespace or is the empty string.

Add the following to the end of main() and run the code again:

val spaces = "  "
val newlinesTabAndReturns = "\n\r\t\n\r\t"
val nonBlankString = "Not blank!"
println(spaces.ifBlank {"'spaces' is blank"}) // 'spaces' is blank
println(newlinesTabAndReturns.ifBlank {"'newlinesTabAndReturns' is blank"}) // 'newlinesTabAndReturns' is blank
println(emptyString.ifBlank {"'emptyString' is blank"}) // (empty string)
println(nonBlankString.ifBlank {"'nonBlankString' is blank"}) // Not blank!

Boolean

The Boolean type now has a companion object, which means there’s something to attach extension functions to. To see a practical application of this new capability, run the following in the Kotlin Playground:

import kotlin.random.*

fun Boolean.coinToss(): Boolean = Random.nextBoolean()
fun Boolean.asHeadsOrTails(): String {
    if (this) {
        return "Heads"
    } else {
        return "Tails"
    }
}
var penny = false
println("Coin toss: ${penny.coinToss().asHeadsOrTails()}")

HashCode

A HashCode takes an object of any size as its input and outputs a string or number that’s usually smaller than the object. A hash function’s result acts as a sort of fingerprint for its input object because if two objects are equal and fed into the same hash function, the results are also equal.

hashCode() takes an object of any size as its input and outputs an integer. The object you provide as input to hashCode() could be one byte or a million bytes in size, but its output is always an integer (which takes up 32 bytes).

Let’s try hashCode() on a few strings. Run the following in the Kotlin Playground:

fun main() {
  val shortString = "Hi."
  val anotherShortString = "Hi."
  val mediumString = "Predictions are hard to make, especially ones about the future."
  val longString = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi neque nunc, elementum vitae consectetur ut, eleifend vitae ante. Donec sit amet feugiat risus. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Maecenas luctus in quam eu tristique. Morbi tristique tortor arcu, sed fringilla orci hendrerit eget. Curabitur libero risus, hendrerit tincidunt enim quis, consectetur rhoncus metus. Etiam sed lacus mollis, pulvinar mauris nec, tincidunt purus."

  println("shortString's hashcode: ${shortString.hashCode()}") // 72493
  println("anotherShortString's hashcode: ${anotherShortString.hashCode()}") // 72493
  println("mediumString's hashcode: ${mediumString.hashCode()}") // 642158843
  println("longString's hashcode: ${longString.hashCode()}") // -420880130
}

Note that shortString and anotherShortString have the same hashCode() value. That’s because they contain the same text, and are therefore equal.

In Kotlin 1.3, hashCode() has been expanded to work for nullable types. It follows these simple rules:

hashCode()
hashCode()

Add the following to the end of main() and run the code again:

val yetAnotherShortString = "Hi."
val nullString: String? = null
println("yetAnotherShortString's hashcode: ${yetAnotherShortString.hashCode()}") // 72493
println("nullString's hashcode: ${nullString.hashCode()}") // 0

Coroutines

We saved the biggest change for last! Coroutines have been in Kotlin since version 1.1 as an experimental feature, and Kotlin 1.3 is the first version where they’re a standard part of the language.

One way to describe coroutines is “a way to write asynchronous code using lightweight threads and a structured syntax”. A less technical, more practical way to phrase this is “an easier-to-read way to program things that happen at the same time”.

Let’s start with a simple example. Run the following in the Kotlin Playground:

import kotlinx.coroutines.*

fun main() { 
  // 1
  GlobalScope.launch {
    // 2
    delay(1000L)
    println("And then about a second later, this'll be printed.")
  }
  // 3
  println("This'll be printed first.") 
  // 4
  Thread.sleep(2000L)
}

You should see the following in the output section of the page:

This'll be printed first.
And then about a second later, this'll be printed.

Here’s what’s happening in the code:

  1. The launch keyword is a coroutine builder, which starts a coroutine. That’ll run parallel to the current thread without blocking it. Every coroutine runs inside a scope, and this one runs inside the GlobalScope . When launched within this scope, a coroutine operates at the same scope as the application, and the lifetime of the coroutine is limited only by the lifetime of the application.
  2. This is the code of the coroutine. delay() stops the execution of the coroutine it’s in for a specified number of milliseconds and then resumes the execution once that time has passed, all without blocking the current thread. The coroutine waits one second, then prints a message.
  3. This is the first line of code after the coroutine. The coroutine is running in parallel to this code, and since it’s waiting one second before printing its message, this println() executes first.
  4. This line keeps the program “alive” for an additional two seconds, which gives the coroutine enough time to execute: one second for the delay, and the milliseconds it takes for the coroutine println() to do its thing. If you comment out or remove this, the program’s second line of output disappears.

Let’s try another example. Run the following in the Kotlin Playground:

import kotlinx.coroutines.*

fun main() = runBlocking { // 1
  launch { 
    delay(200L)
    println("After 200-millisecond delay.")
  }
  // 2
  coroutineScope {
    // 3
    launch {
      delay(500L) 
      println("After 500-millisecond delay.")
    }
    delay(100L)
    println("After 100-millisecond delay.")
    println("${perform200msTask ()}")
  }
  // 3
  println("...and we're done!") 
}
// 4
suspend fun perform200msTask(): String {
  delay(200L)
  return "Finished performing a 200ms task."
}

You’ll see the following in the output section of the page:

After 100-millisecond delay.
After 200-millisecond delay.
Finished performing a 200ms task.
After 500-millisecond delay.
...and we're done!

Here’s what’s happening in the code:

  1. The runBlocking keyword allows main() , which is regular blocking code, to call suspending functions, which you can think of as a function that runs as a coroutine.
  2. The coroutineScope keyword defines a scope for coroutines to “live” in. It can contain many coroutines, which allows you to group tasks that happen concurrently. This block of code blocks the current thread until all the coroutines within it have completed.
  3. Because the previous block is a coroutineScope , which is blocking, this line isn’t executed until the previous block has completed. This means that “…and we’re done!” is printed last.
  4. The suspend keyword marks as a suspending function . When called, it gets executed in parallel.

If you want to confirm that the coroutineScope blocks the current thread until its inner coroutines have completed, make the following change in the code from this:

// 2
  coroutineScope {

To this:

// 2
  launch {

Run the code again. The output will now look like this:

...and we're done!
After 100-millisecond delay.
After 200-millisecond delay.
Finished performing a 200ms task.
After 500-millisecond delay.

With that change, everything prior to the final line in main() is executed in parallel and unblocked, and control jumps immediately to that line. The result is that “…and we’re done!” is printed first instead of last.

Using Kotlin 1.3 in Android Studio

By now, you’re eager to try Kotlin 1.3 in your Android projects. You can see which version of Kotlin your project is using by selecting ToolsKotlin from the menu, which will open this dialog box:

vaqeuyU.png!web

If a newer version of Kotlin is available, you’ll be given the chance to install it:

tools-kotlin-1-650x385.png

Where to Go From Here?

The first place you should go to is the the What’s New in Kotlin 1.3 page on the official Kotlin site. It contains a complete list of every new feature introduced in Kotlin 1.3.

If you find the official list of changes too dry for your liking, you might prefer Todd Ginsberg’s overview of the additions and changes that came with Kotlin 1.3. It’s got examples that are easy to follow, and goes into more detail when covering Kotlin 1.3’s experimental features. Remember that these features are designated as experimental — don’t use them in production code!

Kotlin 1.3’s biggest change is coroutine support, and it’s pretty likely that you’ll want more reading material. The official tutorial lives on the kotlinx.coroutines GitHub repository , and it covers not just the basics, but additional topics such as using coroutines for UI programming and reactive streams.

And finally, the topic of coroutines is so big that it could have its own book. Luckily for you, we wrote it! Kotlin Coroutines by Tutorials introduces you to asynchronous programming in Kotlin by applying coroutines to common Android programming problems.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK