Write at the beginning: I plan to write a Kotlin series of tutorials, one is to make my own memory and understanding more profound, two is to share with the students who also want to learn Kotlin. The knowledge points in this series of articles will be written in order from the book “Kotlin In Action”. While displaying the knowledge points in the book, I will also add corresponding Java code for comparative learning and better understanding.

Kotlin Tutorial (1) Basic Kotlin Tutorial (2) Functions Kotlin Tutorial (3) Classes, Objects and Interfaces Kotlin Tutorial (4) Nullability Kotlin Tutorial (5) Types Kotlin Tutorial (6) Lambda programming Kotlin Tutorial (7) Operator overloading and Other conventions Higher-order functions Kotlin tutorial (9) Generics


Kotlin interconverts with Java

Kotlin code is converted to Java files after compilation. For Kotlin, we can see the converted Java code in advance by using the following tools: Go to Tools>Kotlin>Show Kotlin Bytecode in AS and click Decompile on the panel.

For previously written Java Code, we can also use a tool To Convert it To Kotlin Code: Code > Convert Java File To Kotlin File

Functions and variables

Hello, world!

Learn how to write a “Hollo World” in Kotlin! Let’s start with familiar Java:

public static void main(String[] args) {
        System.out.println("Hello, world!");
    }
Copy the code

And then there, that’s how Kotlin wrote it:

fun main(args: Array<String>) {
    println("Hello, world!")}Copy the code

You can see from Kotlin:

The fun keyword is used to declare a function;

Main is the method name;

Args: Array

represents a parameter, which can be found in Java. Conversely, Kotlin uses the variable name first, then:, and then the type declaration.

Kotlin doesn’t have a special syntax for declaring arrays. Instead, it uses arrays, sort of like collections.

Println replaces System.out.println, which is the Kotlin library that gives Java library functions a lot of syntactically cleaner wrappers;

I don’t know if you’ve noticed; The semicolon is omitted from Kotlin.

function

If you’re familiar with Java, you might be wondering where is the return value? Where’s that?

fun max(a: Int, b: Int): Int {
    return if (a > b) a else b
}
Copy the code

The return value is an Int, so why didn’t the function above write that? In Kotlin, it is: Unit, and: Unit can be omitted by default, so you can’t see the declaration of the return value.

The same method in contrast to Java:

public static int max(int a, int b) {
        if (a > b) {
            return a;
        } else {
            returnb; }}Copy the code

Notice a difference in the use of if in the method body? In Kotlin, if is an expression, not a statement. A statement differs from an expression in that an expression has a value and can be used as part of another expression. The statement always surrounds the top-level element in its code block and has no value of its own. In Java, the control structures used are statements. In Kotlin, most control structures except for loops (for, do, do/while) are expressions.

Expression function body

If the function body of a method consists of a single expression, we can use that expression as the entire function body, without curly braces and return statements:

fun max(a: Int, b: Int) = if (a > b) a else b
Copy the code

Ps: Actions can be invoked by Alt + Enter in AS, providing a way to Convert between two function styles: “Convert to expression body” and “Convert to block body”

You may have noticed that the body of the expression function here also does not have a return type. As a statically typed language, doesn’t Kotlin require that every expression should have a type at compile time? In fact, every variable and expression has a type, and every function has a return type. But for expression body functions, the compiler parses the expression as the function body and treats its type as the function return type, even if it is not written out. This gap is often referred to as type derivation.

variable

In Java, variables are declared from a type, like this:

final String str = "this is final string";
int a = 12;
Copy the code

This is not possible in Kotlin, however, because many variable declaration types can be omitted. So in Kotlin you start with the keyword, then the variable name, and then you can add the type (which can also be omitted) :

val str: String = "this is a final string"
var a = 12
Copy the code

Where: String can also be omitted. From the right side of =, it can be inferred that the type of the left variable is String, just as the type of a variable is omitted.

Mutable variables and immutable variables

There are two keywords for declaring variables: val (from value) — immutable reference. Cannot be assigned again after initialization, corresponding to the Final modifier in Java. Var (from variable) — Variable references. The value of this variable can be changed to correspond to normal variables in Java.

Although var stands for mutable and omitted types, as seen above, it may at first seem similar to scripting languages such as JS, where it is possible to directly assign a value of another type, such as this:

var a = 12
a = "string"/ / errorCopy the code

In practice, this is a mistake. Even though the var keyword allows a variable to change its value, its type does not. Here the type of variable A is determined on the first assignment, which is Int. Assigning a value of type String again will prompt an error and a ClassCastException will occur on the run.

Note that although the val reference itself is immutable, the object it points to can be mutable, for example:

val languages = arrayListOf("Java")
languages.add("Kotlin")
Copy the code

In fact, as in Java, final defines a collection in which the data can be changed.

### string template

val name = "HuXiBing"
println("Hello, $name!")
Copy the code

This is a new Kotlin feature. In the code, you declare a variable name and use it in the following string literals. Like many scripting languages, Kotlin lets you reference local variables in string literals by prefixing the variable name with the character $, which is equivalent to the string link “Hello, “+ name + “!” in Java. The same efficiency but more compact. By converting to Java code, we can see that these two lines of code actually look like this:

String name = "HuXiBing";
String var3 = "Hello, " + name + '! ';
System.out.println(var3);
Copy the code

Of course, expressions are statically checked, and if you try to reference a variable that doesn’t exist, the code won’t compile at all. If you want to use $in a string, you need to escape it: println(“\$x”) prints $x and does not interpret x as a reference to a variable. It is also possible to refer to more complex expressions, rather than being limited to simple variable names, by enclosing the expressions in curly braces:

println("1 + 2 = The ${1 + 2}")
Copy the code

Double quotes can also be nested directly within double quotes, as long as they are within the range of an expression (that is, inside curly braces) :

val a = 12
println("a ${if (a >= 10) ">= 10" else" < 10"}")
Copy the code

Classes and properties

Let’s start with a simple JavaBean class Person, which currently has only one property: Name.

public class Person {
    private final String name;

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        returnname; }}Copy the code

In Java, the method body of a constructor often contains exactly the same code: it assigns parameters to fields with the same name. In Kotlin, this logic can be expressed without so much boilerplate code. Convert this object To Kotlin using Convert Java File To Kotlin File:

class Person(val name: String)
Copy the code

Such classes, which have only data and no other code, are often called value objects, and many languages provide a concise syntax for declaring them. Note that the public modifier is missing in the conversion from Java to Kotlin, which is public by default in Kotlin, so you can omit it.

attribute

The idea of a class is to encapsulate data and the code that processes it into a single entity. In Java, data is stored in fields, which are usually private. If you want users of a class to access data, you provide accessor methods: a getter and possibly a setter. You’ve already seen an example of an accessor in the Person class. Setters can also contain additional logic, including steaming the value passed to them, sending notifications about changes, and so on. In Java, the combination of a field and its accessor is often called a property, and in Kotlin, a language feature such as property time completely replaces field and accessor methods. Declaring a property in a class is the same as declaring a variable: use the val and var keywords. Properties declared as val are read-only, while the var property is mutable.

Class Person(val name: String,// read-only attribute, generate a field and a simple getter)Copy the code

It may be clearer to look at the code converted to Java:

public final class Person {
   @NotNull
   private final String name;
   private boolean isMarried;

   @NotNull
   public final String getName() {
      return this.name;
   }

   public final boolean isMarried() {
      return this.isMarried;
   }

   public final void setMarried(boolean var1) {
      this.isMarried = var1;
   }

   public Person(@NotNull String name, boolean isMarried) {
      super();
      Intrinsics.checkParameterIsNotNull(name, "name"); this.name = name; this.isMarried = isMarried; }}Copy the code

Kotlin does not even need to use templates to generate beans. The corresponding code will be generated automatically when compiled. Use in Java should be familiar, is this:

Person person = new Person("HuXiBing".true);
System.out.println(person.getName());
System.out.println(person.isMarried());
Copy the code

The generated getter and setter methods prefix the property name with get and set, except that if the property begins with is, the getter does not prefix the property, and is is replaced with set in the setter name. So you will call isMarried(). In Kotlin it looks like this:

val person = Person("HuXiBing".true) // Call constructor without keyword new println(person.name) // Can access property directly, but call getter println(person.ismarried)Copy the code

In Kotlin, you can refer to properties directly without calling the getter. The logic is the same, but the code is cleaner. Setters for mutable properties do the same: in Java, use Person.setMarried (false) to indicate divorce, whereas in Kotlin, you could write person.ismarried = false.

Custom accessors

If additional logic is needed in getter and setter methods, it can be done by custom accessors. For example, there is a requirement to declare a rectangle that can determine if it is a square. You don’t need a separate field to store this information, because you can always tell by checking whether the rectangles are equal in length and width:

class Rectangle(val height: Int, val width: Int) {
    val isSquare: Boolean
        get() {// Declare the getter for the propertyreturn height == width
        }
}
Copy the code

The property isSquare does not require a field to hold its value, it only has a custom implemented getter whose value is computed each time the property is accessed. Remember the expression function body? Get () = height == width. Also look at converting to Java code for better understanding:

public final class Rectangle {
   private final int height;
   private final int width;

   public final boolean isSquare() {
      return this.height == this.width;
   }

   public final int getHeight() {
      return this.height;
   }

   public final int getWidth() {
      returnthis.width; } public Rectangle(int height, int width) { this.height = height; this.width = width; }}Copy the code

Kotlin source layout: directories and packages

Like Java, each Kotlin file can start with a package statement, and all declarations (classes, functions, and attributes) defined in the file are placed in this package. If declarations defined in other files also have the same package, this file can use them directly; If the packages are different, you need to import them. As in Java, import statements come first. Use the keyword import:

package com.huburt.imagepicker

import java.util.Random
Copy the code

Package and import declarations in Java:

package com.huburt.imagepicker;

import java.util.Random;
Copy the code

Merely omitted; Kotlin does not distinguish whether an import is a class or a function (yes, Kotlin’s functions can exist on their own, not necessarily declared in a class). Such as:

import com.huburt.other.createRandom
Copy the code

Com.huburt. other is the package name and createRandom is the method name, which is defined directly at the top level of the Kotlin file. In Java, classes are placed in file and directory structures that match the package structure. In Kotlin, the package hierarchy doesn’t have to follow the directory hierarchy, but following Java’s directory layout and placing source files in the appropriate directory based on the package structure is a better choice, avoiding unexpected errors.

Represents and handles choices: enumerations and When

The statement enumeration

Kotlin declares enumerations:

enum class Color {
    RED, ORANGE, YELLOW, GREEN, BLUE
}
Copy the code

The declaration of enumerations in Java:

enum Color {
    RED, ORANGE, YELLOW, GREEN, BLUE
}
Copy the code

This is one of the few cases where Kotlin declarations use more keywords than Java (more class keywords). Unlike Kotlin, where enum is a soft keyword that has special meaning only if it appears before class and can be used as a common name elsewhere, class remains a keyword and continues to declare variables using the names clazz and aClass. Enumerations, like Java, are not worth lists; you can declare properties and methods for enumerated classes:

enum class Color(val r: Int, val g: Int, val b: Int) {

    RED(255, 0, 0), ORANGE(255, 165, 0),
    YELLOW(255, 255, 0), GREEN(0, 255, 255),
    BLUE(0, 0, 255);

    fun rgb() = (r * 256 + g) * 256 + b
}
Copy the code

Enumeration constants use the same declaratively constructed methods and properties syntax as you saw before for regular classes. When you declare each enumerated constant, you must provide the attribute value for that constant. Notice that this shows you that the only way Kotlin syntax has to use a semicolon (;) If you define any methods in an enumerated class, use a semicolon to separate the list of enumerated constants from the method definition.

Use When for enumeration classes

For Java, switch is usually used to match enumerations, for example:

public String getColorStr(Color color) {
        String str = null;
        switch (color) {
            case RED:
                str = "red";
                break;
            case BLUE:
                str = "blue";
                break;
            case GREEN:
                str = "green";
                break;
            case ORANGE:
                str = "orange";
                break;
            case YELLOW:
                str = "yellow";
                break;
        }
        return str;
    }
Copy the code

Instead of switch, Kotlin uses when. Like if, when is an expression that returns a value, so we write an expression body function that returns when directly:

fun getColorStr(color: Color) =
        when (color) {
            Color.RED -> "red"
            Color.ORANGE -> "orange"
            Color.YELLOW -> "yellow"
            Color.GREEN -> "green"
            Color.BLUE -> "blue"} // call println(getColorStr(color.red))Copy the code

The code above finds the corresponding branch based on the color value passed in. Unlike Java, you don’t need to write a break statement on every branch (omiting a break in Java usually leads to a bug). If the match is successful, only the corresponding branch is executed, and multiple values can be merged into the same branch, separated by a comma (,).

fun getColorStr(color: Color) =
        when (color) {
            Color.RED, Color.ORANGE, Color.YELLOW -> "yellow"
            Color.GREEN -> "neutral"
            Color.BLUE -> "cold"
        }
Copy the code

If you feel that too much Color is written, you can omit it by importing it:

Import com.huburt.other.color import com.huburt.other.color.* // Import enumeration constant fun getColorStr(Color: Color) = when (color) { RED, ORANGE, YELLOW ->"yellow"// Use the constant name GREEN ->"neutral"
            BLUE -> "cold"
        }
Copy the code

Use arbitrary objects in the When structure

The when structure in Kotlin is much more powerful than the Switch structure in Java. Switch requires that constants (enumerated constants, strings, or numeric literals) be used as branching conditions. When allows you to use any object. We use this property to write a function that mixes two colors:

fun mix(c1: Color, c2: Color) {
    when (setOf(c1, c2)) {
        setOf(Color.RED, Color.YELLOW) -> Color.ORANGE
        setOf(Color.YELLOW, Color.BLUE) -> Color.GREEN
        else -> throw Exception("Dirty color")}}Copy the code

SetOf is a method in Kotlin’s standard library for creating sets (unordered). The when expression matches the set generated by setOf(c1, c2) with all branches until a branch meets the criteria, executing the corresponding code (returning the mixed color value or throwing an exception). Being able to use any expression as a branching condition for WHEN in many cases makes your code neat and beautiful.

Use When with no arguments

You may realize that the above example is somewhat inefficient. Whenever this function is not called, it creates Set instances that simply check whether two given colors match two other colors. In general this is not a big deal, but if the function is called frequently, it is well worth rewriting in another way. To avoid creating additional junk objects.

fun mixOptimized(c1: Color, c2: Color) =
        when {
            (c1 == Color.RED && c2 == Color.YELLOW) ||
                    (c1 == Color.YELLOW && c2 == Color.RED) -> Color.ORANGE

            (c1 == Color.BLUE && c2 == Color.YELLOW) ||
                    (c1 == Color.YELLOW && c2 == Color.BLUE) -> Color.GREEN
            
            else -> throw Exception("Dirty color")}Copy the code

If no arguments are given to the WHEN expression, the branching condition is an arbitrary Boolean expression. The mixOptimized method does exactly the same thing as the above example; it doesn’t wear an extra object.

Intelligent transformation

In Java, there is often a case where a parent class declaration refers to a subclass object, and when you want to use a method of a subclass, you need to determine whether it is a subclass, if it is a subclass object, call the method of the subclass, using the following code:

class Animal {

}

class Dog extends Animal {
    public void dig() {
        System.out.println("dog digging");
    }
}

Animal a = new Dog();
if (a instanceof Dog) {
     ((Dog) a).dig();
}
Copy the code

In Kotlin, the compiler does the hard work for you. If you check that a variable is of a certain type, you don’t need to cast it later, and you can use it as if it were the type you checked (calling methods, etc.). This is an intelligent conversion.

val d = Animal()
if (d is Dog) {
    d.dig()
}
Copy the code

Here is checks whether a variable is of some type (an instanceof a class), equivalent to instanceof in Java. As you can see, the d variable is an Animal object, so you can call the Dog method without forcing it. Smart transformations only work if the variable is checked and does not change after that. When you perform a smart conversion on a class property, the property must be a Val property and must not have a custom accessor. Otherwise, there is no way to verify that each access to the property returns the same value.

Use the as keyword in Kotlin to display conversion types (strong conversions) :

val d = Animal()
val dog = d as Dog
Copy the code

Ps: in fact, just omit strong transfer code, personal feeling effect is not very obvious.

Use When instead of If

How Kotlin differs from if in Java has already been mentioned. For example, if expressions are used in contexts where Java ternary operators are applicable: if (a > b) a else b (Kotlin) and a > b? A: B (Java) has the same effect. Kotlin does not have ternary operators because, unlike Java, if expressions return values. If is fine for a small number of judgment branches, but when is a better choice for a large number of judgment branches, which have the same effect and are all expressions that return values.

The code block branches off If and When

The above example only executes one line of code for a branch that satisfies the criteria, but what if there is more than one line of code in a branch? Add the omitted {} as a block of code, of course:

val a = 1
val b = 2
var max = if (a > b) {
   println(a)
   a
} else b

var max2 = when {
   a > b -> {
     println(a)
     a
  }
  else -> b
}
Copy the code

The last expression in the block is the result, the return value. Compare Java code:

int a = 1;
int b = 2;
int max;
if (a > b) {
   System.out.println(a);
   max = a;
} else{ max = b; } // Switch cannot be usedCopy the code

There are fewer assignments, and the use of WHEN is more convenient in multi-condition situations. Are you starting to see the beauty of Kotlin?

cycle

The While loop

The while and do-while loops in Kotlin are exactly the same as in Java and won’t be covered here.

Iterating numbers: intervals and sequences

Kotlin has the concept of an interval, which is essentially the interval between two values, usually numbers: a starting value, an ending value, using.. Operator to represent the interval:

val oneToOne = 1 .. 10
Copy the code

Note that Kotlin’s interval is either contained or closed, meaning that the second value is always part of the interval. If you don’t want to include the last number, you can create the interval using until: val x = 1 until 10, which is the same as val x = 1.. 9

The most basic thing you can do with an integer interval is loop over all of its values. If you can iterate over all the values in an interval, such an interval is called a sequence. We played Fizz-Buzz with integer iterations. Players took turns incrementing the count, replacing the number divisible by 3 with the word fizz, and replacing the number divisible by 5 with the word buzz. If a number is a common multiple of 3 and 5, you say FizzBuzz.

fun fizzBuzz(i: Int) = when {
    i % 15 == 0 -> "FizzBuzz"
    i % 5 == 0 -> "Buzz"
    i % 3 == 0 -> "Fizz"
    else -> "$i"
}

fun play() {
    for (i in1.. 100) {print(fizzBuzz(i))
    }
}
Copy the code

The for loop in Kotlin exists in only one form, written for

in

. The range 1.. 100 is

, so the above example iterates through the array and calls the fizzBuzz method. To make the game more complicated, we could start with 100 and count backwards, and only count even numbers.


for (i in 100 downTo 1 step 2) {
     print(fizzBuzz(i))
}
Copy the code

Here 100 downTo 1 is the descending sequence (the default step is 1), and step 2 is set to decrease by 2 each time.

Iterative map

As an example, we use a small program that prints a binary representation of characters.

Val binaryReps = TreeMap<Char, String>(for (c in 'A'.'F'Val binary = integer.tobinaryString (c.tobinaryString ())// Binary reps [c] = binary// map values based on key c }for ((letter, binary) inBinaryReps) {// Iterate over map, assigning keys and values to two variables println("$letter = $binary")}Copy the code

. Syntax can create not only numeric ranges, but character ranges as well. It is used here to iterate over all characters from A to F, including F. The for loop allows you to expand the elements of the set in an iteration (the key-value pairs of the map), storing the expanded result in two separate variables: letter as the key and binary as the value. A concise syntax for accessing and updating a map based on the key. Read values using map[key] and set them using map[key] = value instead of needlessly using get and put. Binary [c] = binary [c] = binary

You can also use the expansion syntax to track the subscript of the current item while iterating through the collection. There is no need to create a separate variable to store the subscript and manually increment it:

    val list = arrayListOf("10"."11"."1001")
    for ((index, element) in list.withIndex()) {
        println("$index : $element")}Copy the code

Use IN to check the members of collections and intervals

Use the IN operator to check whether a value is in an interval, or its inverse! In to check if the value is not in the interval. The following shows how to use in to check whether a character belongs to a character range.

fun isLetter(c: Char) = c in 'a'.'z' || c in 'A'.'z'
fun isNotDigit(c: Char) = c !in '0'.'9'
Copy the code

This technique for checking if a character is English seems simple enough. At the bottom level, nothing special is done and the character encoding is checked to see if it is somewhere between the first and last encoding (a <= c && c <= z). But this logic is neatly hidden away in the interval class implementation of the standard library. The in operation conforms to! In also works with when expressions.

fun recognize(c: Char) = when (c) {
    in '0'.'9' -> "It's a digit!"
    in 'a'.'z'.in 'A'.'z' -> "It's a letter!"
    else -> "I don't know..."
}
Copy the code

Exception in Kotlin

The basic form of Kotlin’s exception-handling statements is similar to Java, except that the new keyword is not required and that the throw structure is an expression that can be used as part of another expression.

 val  b = if (a > 0) a else throw Exception("description")
Copy the code

Try, catch, finally

As in Java, exceptions are handled using try structures with catch and finally clauses. The following example reads a line from a given file, attempts to parse it into a number, and returns the number; Or return NULL if the line is not a valid number.

fun readNumber(reader: BufferedReader): Int? {
    try {
        val line = reader.readLine()
        return Integer.parseInt(line)
    } catch (e: NumberFormatException) {
        return null
    } finally {
        reader.close()
    }
}
Copy the code

Int? The value may be of type int or null. Kotlin’s unique null mechanism does not include? The declaration of the identity cannot assign null, as will be explained in a later article.

The biggest difference with Java is that the throws clause does not appear in the code: if you write this function in Java, you explicitly write throws IOException at the end of the function declaration. The reason you need to do this is that IOException is a checked exception. In Java, such exceptions must be handled explicitly. You must declare all checked exceptions that your function can throw. If you call another function, either handle the checked exceptions of that function or declare that your function can throw those exceptions as well. Like many other modern JVM languages, Kotlin does not distinguish between checked and unchecked exceptions. The exception thrown by the function is not specified, and may or may not be handled. This design is based on a decision made using exception practices in Java. Experience shows that these Java rules often result in a lot of pointless code to rethrow or ignore exceptions, and they don’t always protect you from possible errors.

Try as an expression

The try keyword in Kotlin, like if and when, introduces an expression whose value can be assigned to a variable. For example, the above example could also be written like this:

fun readNumber(reader: BufferedReader): Int? =
        try {
            val line = reader.readLine()
            Integer.parseInt(line)
        } catch (e: NumberFormatException) {
            null
        } finally {
            reader.close()
        }
Copy the code

If a try block executes perfectly, the last expression in the block is the result. If an exception is caught, the last expression in the corresponding catch block is the result.