Before the order

Kotlin introduced a new feature called nullability to eliminate the danger of null references from code. Error converting run-time NPE to compiler.

Nullable and non-nullable types

In the Kotlin type system, there are nullable types and non-nullable types. When you allow a variable to be null, you need to display a question mark after the type, converting its non-null type to nullable type.

Common types are non-null types and cannot store null references. Variables can store null references only after the type is converted to a nullable type by adding a question mark.

val str:String? = null
Copy the code

You cannot call a method of a nullable type directly, assign it to a non-null type, and pass it to a function that takes a non-null type argument. Nullable types seem to have no interaction with non-nullable types, but in fact they do not. Nullable types only need a nullable to interact normally:

val str:String? = ""if(str ! = null) str.lengthCopy the code

Once an object of nullable type is nulled, the compiler treats the object as non-nullable in the nullable scope.

Kotlin’s nullity and Java’s Optional

The special wrapper type Optional introduced in Java8 addresses the null reference problem. But this approach is not widely used because it makes the code more verbose and the extra wrapped classes affect runtime performance.

But in Kotlin, there is no difference at runtime between nullable and non-nullable objects, and nullable types are not wrapper classes for non-empty types. All checking is done by the compiler, which makes Kotlin’s nullable type incur no additional overhead at run time.

Safe call operators:? .

The Kotlin standard library has an efficient security scheduling operator:? . It combines the NULL check and call into one operation. When you use? . When a method is called on an object of nullable type, if the value is not null, the method is executed normally; If the value is NULL, the method call does not occur and the entire expression returns NULL.

In addition to calling methods, security calls can also be used to access properties.

Elvis operator:? :

Elvis operator? : used to provide a default value instead of NULL. The Elvis operator receives two expressions and returns the left-hand expression if the left-hand expression is not empty. When the left-hand expression is empty, the right-hand expression is returned.

The Elvis operator is often used with the security scheduling operator:

val str:String? = null println(str? .length ? : 0)Copy the code

The Elvis operator can also be used in conjunction with return and throw to return a function or throw an exception ahead of time when the left side of the operator is null.

val str:String? Val length = STR? .length ? : throw IllegalArgumentException() println(length)Copy the code
str? .let { println(length) } ? :return/ / equivalent to theif(STR == null) // Interrupt the function if the function type is nullreturn// if STR is not null, the execution continues. println(length)Copy the code

It can also be used with the run function instead of if-lese:

str? .let {// STR not null logic}? : run {// STR is null logic}Copy the code

Non-empty assertion:!!

Kotlin offers non-null assertion operators for NPE enthusiasts!! (double exclamation mark), can cast any object to a non-null type, thus calling the object’s methods, but may cause an NPE to be thrown.

val str:String? Println (STR!! .length)Copy the code

So non-null assertions are used only to ensure that the nullable type object is not null. When a non-empty assertion is used and an exception occurs, the exception stack indicates only the row on which the exception occurred, not the expression, so it is best to avoid non-empty assertions on the same row.

Safe conversion: as?

As with regular Java conversions, a ClassCastException is thrown when the converted value is not of the type that your view converted. A common solution is to use an IS check to determine if the value matches the conversion type before using the conversion. But Kotlin offers a more compact operator, the safe conversion operator: as?

// Define the parent and subclasses open class Animal{fungetName(){
    }
}
class Dog:Animal(){
    fun getDogName(){ } } fun main(args:Array<String>){ val animal:Animal = Dog() val dog = animal as? Dog ? :return
    dog.getDogName()
}
Copy the code

The safe conversion operator attempts to convert a value to the given type, otherwise returns NULL:

Let the function

The let function turns the object on which it is called into arguments to a lambda expression. With the security scheduling operator, nullable objects called let functions can be converted to non-nullable types. A series of operations on the nullable type are then called in the let function.

fun main(args:Array<String>){ val str:String? = null str? .let { daqi(it) } } fun daqi(str:String){ }Copy the code

When multiple values need to be checked for null, nested let calls are not recommended. Instead, a single if statement is recommended to check these values once.

An extension of nullable types

The advantage of extending nullable types is that it allows the extension function to be called when the receiver is NULL and to handle null in the extension function, rather than calling the object’s methods after ensuring that the variable is not null. Because when the instance is null, the member method is never executed.

The Kotlin library has two extension functions for CharSequence: isNullOrEmpty and isNullOrBlank. Type to be called by the receiver.

When you define an extension function for a nullable type, it means that this in the function body may be null and needs to be nulled accordingly.

fun String? .daqi() {if (this == null){
        println("this is null") } } fun main(args:Array<String>){ val str:String? = null Because nullable types are received, do not use? . str.daqi() }Copy the code

Lazy initialization

In Kotlin, when an attribute is declared as a non-null type, it must be initialized in the constructor. But properties can be initialized in a special method, through dependency injection. You cannot provide a non-null initializer for an attribute in the constructor, but you still want to declare the type as non-null to avoid null-checking. You can modify this variable with the LateInit keyword, and use var because val must be compiled into a final field that must be initialized in the constructor.

class daqi{
    private lateinit var name:String
    
    fun onCreate(){
        name = "daqi"}}Copy the code

Nullability and Java

Kotlin classifies types from Java into nullable and non-nullable types based on nullability annotations in Java. For example, objects annotated @nullable are treated as Nullable objects by Kotlin. Objects annotated @notnull are treated as non-null by Kotlin.

When nullability annotations do not exist, the Java type is converted to Kotlin’s platform type. A platform type is essentially one in which Kotlin does not know its nullable information and can be treated as either nullable or non-nullable. If a non-empty type is selected, the compiler triggers an assertion on assignment that prevents Kotlin’s non-empty variables from holding null values. This means that the developer is responsible for properly handling the values from Java.

Kotlin defines a function in which the compiler generates a check for each argument of a non-null type and immediately throws an exception if called with an incorrect argument. (This check is performed when the function is called, not when the exception argument is used.)

Basic data types

Java distinguishes between primitive data types and reference types, and primitive data types have the nature of efficient storage and delivery. When you need to store some basic data types in a generic class, you need to store them as wrapper types for the basic data types. The JVM does not support primitive data types as type parameters.

Kotlin does not distinguish between base types and wrapper types. For variables, attributes, and return types, Kotlin’s base data types are compiled to Java’s base data types. Only for generic classes, the compiler wraps the class into the corresponding Java primitive type.

Basic data types

When a java-declared primitive data type variable is used, the type becomes a non-null type instead of a platform type. Because Java’s basic data types cannot store null values.

Nullable primitive data types in Kotlin are compiled to their corresponding wrapper types because Java primitive data types cannot store null values.

To digital conversion

Kotlin does not automatically convert a number from one type to another with a wider range of values. Kotlin defines a function for each primitive data type (except Boolean) to convert to the other primitive data types.

Kotlin requires that conversions be explicit, because in Java, boxing values are not only checked for the values they store, but also for boxing types.

// The comparison is returned herefalse
new Integer(42).equals(new Long(42))
Copy the code

The Kotlin library also provides extension functions for converting primitive data types to strings. If parsing the string fails, the NumberFormatException() method is thrown.

Root type

The Any type is the superclass of all Kotlin non-null types. Any cannot hold a null value. When a variable that needs to hold Any value includes a null value, you must use Any?

Any contains only toString, equals, and hashCode. All of Kotlin’s methods are inherited from Any. But Any cannot use methods that use other objects (such as wait and notify).

Nullability of type parameters

In Kotlin, the type parameters of all generic classes and functions are nullable by default, because the default upper bound is Any?

If you want the type parameter to be non-empty, you must specify a non-empty upper bound for it:

fun <T:Any> daqi(t:T){
    
}
Copy the code

References:

  • Kotlin in Action
  • Kotlin website

Android Kotlin series:

Kotlin’s Knowledge generalization (I) — Basic Grammar

Kotlin knowledge generalization (2) – make functions easier to call

Kotlin’s knowledge generalization (iii) — Top-level members and extensions

Kotlin knowledge generalization (4) – interfaces and classes

Kotlin’s knowledge induction (v) — Lambda

Kotlin’s knowledge generalization (vi) — Type system

Kotlin’s knowledge induction (7) — set

Kotlin’s knowledge induction (viii) — sequence

Kotlin knowledge induction (ix) — Convention

Kotlin’s knowledge induction (10) — Delegation

Kotlin’s knowledge generalization (xi) — Higher order functions

Kotlin’s generalization of knowledge (xii) – generics

Kotlin’s Generalization of Knowledge (XIII) — Notes

Kotlin’s Knowledge induction (xiv) — Reflection