Kotlin basics

This article focuses on the points where Kotlin differs from Java and concludes.

Kotlin is statically typed, and all expressions are typed at compile time. The Kotlin source code is stored in a file with the suffix.kt and compiled by the compiler into a.class file.

Functions and variables

1) function

The function starts with the keyword fun, followed by the function name, as in

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

In Kotlin, if is an expression, not a statement. The difference between a statement and an expression is that an expression has a value and can be used as part of another expression. A statement always surrounds the top-level element in its code block and has no value of its own.

If the body of a function is enclosed in curly braces, the function is said to have a code block. If it returns an expression directly, it is said to have an expression body, as follows,

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

2) variable

Immutable: Use the val keyword, immutable reference, reference object value is mutable, corresponding to the Final in Java mutable variable: use the var keyword, mutable reference, corresponding to the non-final in Java

3) String templates

Concatenate variables with the $character as follows

println("Hello, $name!)
println("Hello, ${name[0]}!)
Copy the code

Classes and attributes

Value object: a class that has only data and no other code. The following

class Person(val name: String)
Copy the code

1) properties

Property: a combination of fields and their accessors. Read-only properties: Using the val keyword, generate a field and a simple getter. Mutable properties: Using the var keyword, generate a field, a getter, and a setter.

When the Kotlin property is called in Java, the field name is prefixed with a GET or set, such as name that generates a getter for getName and a setter for setName. But if the property name starts with is, the getter does not add any prefix, and is in the setter name is replaced with set.

2) Custom accessors

Getter for a custom property, isSquare is read-only, variable but not settable,

class Rectangle(val height: Int, val width: Int) {
    val isSquare: Boolean
        get() {
            return height == width
        }
}
Copy the code

Declaring a function with no parameters for isSquare and a custom getter property makes no difference in performance, but for class characteristics, it is more readable to declare as a property.

Representation and processing options: enumeration and “when”

1) enumeration

Kotlin uses enum classes to declare enumerations. It can also declare enum classes with attributes, as follows:

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, 0),
    BLUE(0, 0, 255),
    INDIGO(75, 0, 130),
    VIOLET(238, 130, 238;
    
    fun rgb() = (r * 256 + g) * 256 + b
}
Copy the code

This can be called with color.blue.rgb ().

2) the when

When is an expression that returns a value, and the last expression in the block is the result, for example, getMnemonic returns when,

fun getMnemonic(color: Color) = 
    when (color) {
        Color.RED -> "Richard"
        Color.ORANGE -> "Of"
        Color.YELLOW -> "York"
        Color.GREEN -> "Gave"
        Color.BLUE -> "Battle"
        Color.INDIGO -> "In"
        Color.VIOLET -> "Vain"
    }
        
Copy the code

If the match is successful, only the corresponding branch is executed. When combining multiple values into a branch, separate them with commas.

The arguments to when can use any object.

When can take no arguments, and the branching condition is an arbitrary Boolean expression, as follows

fun mixOptimized(c1: Color, C2: Color) = when { (c1 == RED && c2 == YELLOW) || (c1 == YELLOW && c2 == RED) -> ORANGE (c1 == YELLOW && c2 == BLUE) || (c1  == BLUE && c2 == YELLOW) -> GREEN (c1 == BLUE && c2 == VIOLET) || (c1 == VIOLET && c2 == BLUE) -> INDIGO else -> throw Exception("Dirty Color") }Copy the code

3) Smart conversion: merge type checking and conversion

Smart conversion: If you check a variable for a certain type, you don’t need to cast it later, you can use it as the type you checked. The property must be a val property and must not have a custom accessor.

Iterate on things

1) Iterative numbers: intervals and sequences

Use.. Operators represent intervals, such as 1.. 10, contains the start and end values.

Use the downTo step operator to represent a descending sequence of steps, such as 100 downTo 1 step 2, with start and end values.

Using the until operator to represent an interval that does not contain an end value, such as 0 until 100, is equivalent to 1.. 99.

2) the map

. Syntax can create not only numeric ranges, but also character ranges; In can be used to iterate over intervals or collections, as in

val binaryReps = TreeMap<Char, String>() for (c in 'A'.. 'F') { val binary = Integer.toBinaryString(c.toInt()) binaryReps[c] = binary } for ((letter, binary) in binaryReps) { println("$letter = $binary") }Copy the code

Binary reps [c] = binary equivalent to binary reps. Put (c, binary)

Tracking the subscript of the current item while iterating through the collection eliminates the need to create a separate variable to store the subscript and manually increment it, as in

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

3) Use “in” to check the members of sets and intervals

Use the in operator to check if a value is in an interval. N Checks if the value is not in the interval.

Five, abnormal

The throw structure in Kotlin is an expression that can be used as part of another expression.

Kotlin showed no abnormalities.

Definition and call of function

1) Named parameters

When a function is called, the name of the function parameter is explicitly indicated, which is more readable.

2) Default parameters

When declaring functions, specify default values for parameters to avoid creating overloaded functions. You can omit the trailing argument. If you use named parameters, you can omit some of the middle parameters, or you can only give the required parameters in any order.

The default value of the argument is compiled into the function being called, not where it was called.

Using the @jvMoverloads annotation instructs the compiler to generate a Java overloaded function, omitting each argument starting with the last.

3) Eliminate static utility classes: top-level functions and properties

Projects often have static utility classes with Util as the suffix, and Kotlin can use top-level functions instead.

Top-level functions: Put functions at the top of the code file without belonging to any class. The top-level function is compiled as a static function of the class.

To change the name of the generated class that contains the top-level function, annotate the file with @jVMName at the beginning of the file, in front of the package name, as in

@file:JvmName("StringFunctions") package strings fun joinToString(...) :String{... }Copy the code

Top-level attributes: Attributes placed at the top of a file are stored in a static field.

Extend functions and attributes

Extension function: a class member function defined outside the class.

Place the name of the class or interface you want to extend in front of the function you want to add. The name of this class is called the receiver type; The object used to call this extension function is called the receiver object. The following

In extension functions, other methods and attributes of the extended class can be accessed directly, just as if they were accessed in the class’s own methods. Unlike methods defined inside a class, extension functions cannot access private or protected members.

1) Import extension functions

Use the keyword as to change the name of the imported class or function:

import strings.lastChar as last
val c = "Kotlin".last()
Copy the code

2) Call extension functions from Java

Extension functions are static functions that take the calling object as their first argument. Suppose there is an extension function lastChar in stringutil. kt, called in Java as follows,

char c = StringUtil.lastChar("Java")
Copy the code

3) Extension functions cannot be rewritten

Because Kotlin treats extension functions as static functions, they cannot be overridden.

If the member function and extension function have the same signature, the member function takes precedence.

4) Extended properties

Like an extension function, an extension property is like a normal member property of the receiver. Getter functions must be defined because there are no supporting fields, so there is no implementation of the default getter and cannot be initialized because there is no place to store values, such as

val String.lastChar: Char
    get() = get(length - 1)
Copy the code

When an extended property is accessed from Java, its getter function is explicitly called, such as Stringutilkt.getlastchar (“Java”).

Variable arguments and infix expressions

1) Variable parameters

You use the vararg modifier to modify a variable parameter by placing the * expansion operator before the parameter.

2) Handling of key-value pairs: infix calls and destruct declarations

Use the mapOf function to create a map

val map = mapOf(1 to "one", 7 to "seven", 53 to "fifty-three")
Copy the code

To is a special type of function call, called an infix call, where the function name is placed directly between the target object name and the parameter.

Infix calls can be used with functions that take only one argument. To allow infix calls to functions, you need to mark them with the infix modifier, such as the declaration of to,

infix fun Any.to(other: Any) = Pair(this, other)
Copy the code

Val (number, name) = 1 to “one”, deconstruct 1 to nunmber, “one” to name

String and regular expression

The Split method in Java does not work with A dot, such as “12.345-6.a “. Split (“.”), “.” is treated as A regular expression representing any character, and returns an empty array.

Kotlin provides some overloaded extension functions with different arguments called split, which are used to carry the value of a regular expression that requires a Regex type.

A triple-quoted string does not need to escape any character and can contain any character, including a newline character, which can be simply embedded into a program. For example, the Windows-style path “C:\Users\yole\kotlin-book” can be written as “”C:\Users\yole\kotlin-book”””.

Prefix the content of the string, mark the end of the margin, and then call trimMargin to remove the prefix and the preceding whitespace from each line.

Local functions

Local functions keep code clean and avoid duplication, as shown below

class User(val id: Int, va; name: String, val address: String)

fun saveUser(user: User) {
    if (user.name.isEmpty()) {
    }
    if (user.address.isEmpty()) {
    }
    ......
}
Copy the code

Extract logic to extension functions

class User(val id: Int, va; name: String, val address: String)

fun User.validateBeforeSave() {
    fun validate(value: String, fieldName: String) {
        if (value.isEmpty()) {
        }
    }
    validate(name, "Name")
    validate(address, "Address")
}

fun saveUser(user: User) {
    user.validateBeforeSave()
    ......
}
Copy the code

Class inheritance structure

1) Interface in Kotlin

Kotlin uses the interface keyword to declare a Kotlin interface that can contain the definition of abstract methods and the implementation of non-abstract methods, and cannot contain any state. Interface methods can have a default implementation, requiring only a method body. Such as

interface Clickable {
    fun click()
    fun showOff() = println("I'm clickable!")
}
Copy the code

If you implement this interface, you need to provide an implementation for Click, and you can redefine the showOff method or not.

Kotlin replaces the extends and implements keywords in Java with a colon after the class name, enforces the override keyword.

If the same inherited member has more than one implementation, a display implementation must be provided, such as super.showoff ().

Kotlin compiles each interface with a default method into a combination of an ordinary interface and a class that uses the method body as a static function.

2) Open, final, and abstract modifiers: Final by default

Making changes to the base class causes the subclass to behave incorrectly, either by designing and documenting inheritance or by disallowing it, so classes and methods in Kotlin default to final.

If you override a member of a base class or interface, the overridden member is also open by default.

Smart conversions only work on variables that have not changed since type checking, and since the property is final by default, smart conversions can be used on most properties.

Abstract members are always open and do not need to use the open modifier explicitly.

Non-abstract functions in abstract classes are not open by default, but can be annotated as open.

3) Visibility modifier: defaults to public

Kotlin does not use packages for visibility control.

Kotlin introduced a new modifier, internal, to indicate that it is only visible inside a module. A module is a set of Kotlin files compiled together, an Intellij IDEA module, an Eclipse project, a Maven or Gradle project, or a set of files compiled using Ant tasks.

The encapsulation of package visibility control is easy to break because external code can define classes into the same package as your code, but internal provides a true encapsulation of module implementation details.

Kotlin allows private visibility in top-level declarations, including classes and attributes that are visible only in the file in which they are declared.

Protected members are visible only to the class and its subclasses. Extension functions of a class cannot access its private and protected members.

An external class in Kotlin cannot see the private members of its internal (or nested) classes.

4) Inner and nested classes: The default is nested classes

Kotlin’s nested classes cannot access instances of external classes.

Kotlin nested classes default to static, using the inner modifier if the inner class needs to hold an external class reference.

Unlike Java, referencing an external class instance in Kotlin requires accessing Outer from the Inner class using this@Outer.

5) Sealed classes: Define restricted class inheritance structures

Sealed: Adds a sealed modifier to the parent class. All direct subclasses must be nested within the parent class. Sealed classes cannot have subclasses outside the class. Sealed classes default to open classes, abstract classes, and constructor private.

The enclosing class is used as follows, as an expression for the enclosing class,

sealed class Expr {
    class Num(val value: Int) : Expr()
    class Sum(val left: Expr, val right: Expr) : Expr()
}

fun eval(e: Expr): Int =
    when (e) {
        is Expr.Num -> e.value
        is Expr.Sum -> eval(e.right) + eval(e.left)
    }
Copy the code

The above sealed class lists all possible classes as nested classes, and the WHEN expression covers all possible cases, so the else branch is no longer needed.

Declare a class with a non-default constructor or attribute

Main constructor: declared outside the class body.

From constructor: declared inside the class body.

1) Initialize the class

The primary constructor indicates the construction parameters and defines the properties initialized with those parameters.

The main constructor cannot contain initialization code, so you need to use an initialization block.

You can declare a default value for a constructor parameter just like a function parameter, as in

class User(val nickname: String, val isSubscribed: Boolean = true)
Copy the code

Some constructor parameters can be explicitly named, such as

val carol = User("Carol", isSubscribed = false)
Copy the code

If all constructor arguments have default values, the compiler generates an additional constructor with no arguments to use all the default values.

The parent class constructor must be explicitly called, even if it has no arguments.

2) Constructor: Initialize classes in different ways

If a path initializes a, B, and C, and another path initializes B, C, and D, a and D are not completely overridden, because Java automatically initializes properties to zero or null, Kotlin cannot be initialized automatically, so there are problems when all attributes are not fully overwritten.

To solve this problem, Kotlin initializes all secondary constructors by calling the primary constructor, and all fields need to be initialized. If the class has no primary constructor, then each slave constructor must either initialize the base class or delegate to another constructor that does so.

3) Implement the properties declared in the interface

Interfaces can contain abstract property declarations.

class PrivateUser(override val nickname: String) : User

class SubscribingUser(val email: String) : User {
    override val nickname: String
        get() = email.substringBefore('@')
}
 
class FacebookUser(val accountId: Int) : User {
    override val nickname = getFacebookName(accountId)
}
Copy the code

The nickname has a custom getter in the SubscribingUser that calculates the substringBefore on each access, and a support field in the FacebookUser that stores the data computed at initialization.

In addition to abstract property declarations, interfaces can contain properties with getters and setters, as long as they do not reference a support field (which requires storing state in the interface, which is not allowed). Such as

interface User {
    val email: String
    val nickname: String
        get() = email.substringBefore('@')
}
Copy the code

There are no supported fields for this property, and the resulting value is computed on each access.

The email property must be overridden in a subclass, and the nickname can be inherited.

4) Access support fields through getters or setters

There are two types of properties: 1) properties that store values; 2) Properties that have custom accessors that evaluate values on each access. Examples of combining these two implementations are as follows

class User(val name: String) {
    var address: String = "unspecified"
        set(value: String) {
            println("""
                Address was changed for $name:
                "$field" -> "$value".""".trimIndent())
            field = value
        }
}
Copy the code

In the body of the setter function, the special identifier Field is used to access the values of the supporting fields. In the getter, you can only read values; And in the setter, you can either read it or modify it.

How a property is accessed does not depend on whether it contains a support field. If explicitly referenced or the default accessor implementation is used, the compiler generates support fields for the property. If a custom accessor implementation is provided and field is not used, the support field will not be generated.

5) Modify the visibility of accessors

The visibility of accessors is the same as that of properties by default, and can be modified if necessary by placing visibility modifiers before the GET and SET keywords. Such as,

class LengthCounter {
    var counter: Int = 0
        private set
        
    fun addWord(word: String) {
        counter += word.length
    }
}
Copy the code

The counter property cannot be modified outside the class.

13. Data

The Kotlin compiler can generate boilerplate code behind the scenes for data classes and class delegates; this article covers only data classes; class delegates will be covered later.

“Any” is a simulation of java.lang.Object: the parent of all classes in Kotlin.

The IS check in Kotlin is a simulation of instanceof in Java, which checks whether a value is of a specified type.

Data classes: Add the data modifier to the front of the class to automatically generate equals, hashCode, and toString generic methods. As follows,

data class Client(val name: String, val postalCode: Int)
Copy the code

Properties not declared in the main constructor will not be added to the equality checking and hash calculation.

“Object” keyword: Combine declaring a class with creating an instance

Object defines a class and creates an instance at the same time.

A) Object declaration, a way of defining singletons;

B) associated objects that can hold factory methods and other methods associated with the class that do not depend on the class instance when called. Their members can be accessed by class name;

C) Object expressions, used instead of Java’s anonymous inner classes.

1) Object declaration: create singleton

The creation of Singleton class Singleton is shown in the figure below.

The above singleton is equivalent to the Java singleton form shown below,

An object declaration in Kotlin can contain declarations of properties, methods, initialization blocks, and so on. The only thing that is not allowed is constructors (both primary and slave constructors). Object declarations are created immediately at definition, without the need to call constructors elsewhere in the code.

Object declarations can also inherit from classes and interfaces. You can declare objects in a class.

2) Companion objects: factory methods and static members

Classes in Kotlin cannot have static members, and associated objects (along with package-level functions and properties) replace Java static method and field definitions. Using the Companion keyword to tag objects defined in a class gives you the ability to access the object’s methods and properties directly through the container class name, without explicitly specifying the object name. As follows,

class A {
    companion object {
        fun bar() {
        }
    }
}
Copy the code

Calls such as a.bar ().

Associated objects have access to all private members of the class, including the private constructor, which is ideal for implementing the factory pattern. Create a new User using the factory method, ensuring that each email corresponds to a unique User instance.

class User private constructor(val nickname: String) {
    companion object {
        fun newSubscribingUser(email: String) = 
            User(email.substringBefore('@'))
            
        fun newFacebookUser(accountId: Int) = 
            User(getFacebookName(accountId))
    }
}
Copy the code

An associated object is a common object declared in a class that can have a name, implement an interface, or have extension functions or properties. If you omit the name of the Companion object, the default name will be assigned to Companion.

Companion objects can also implement interfaces.

When called in Java, static members are generated in Java using the @jVMStatic annotation on the corresponding member. The @jVMField does nothing to generate getters and setters on the JVM for the Kotlin property.

C.func() can be called if C has an associated object and an extension function func is defined on c.panion.

3) Object expressions: anonymous inner classes written differently

When object is used to declare anonymous objects, it replaces the use of anonymous inner classes in Java and adds, for example, the ability to implement multiple interfaces and modify variables defined in the scope in which the object is created. The anonymous inner class is as follows,

window.addMouseListener(
    object : MouseAdapter() {
        override fun mouseClicked(e: MouseEvent) {
        }
        
        override fun mouseEntered(e: MouseEvent) {
Copy the code

An object expression declares a class and creates an instance of that class.

Unlike Java anonymous inner classes, which can only extend one class or implement one interface, Kotlin’s anonymous objects can implement multiple interfaces or none.

Unlike object declarations, anonymous objects are not singletons. Each time an object expression is executed, a new object instance is created.

Code in an object expression can access variables in the function that created it, access variables that are not shown in final, and modify variable values in an object expression, such as

fun countClicks(window: Window) {
    var clickCount = 0
    
    window.addMouseListener(object : MouseAdapter() {
        override fun mouseClicked(e: MouseEvent) {
            clickCount++
        }
    })
}
Copy the code

Object expressions are most useful when you need to override multiple methods in anonymous objects. Lambda or SAM is used when you only need to implement a single-method interface.