Nullable sex

Foreword: NullPointerException is a feature provided by the Kotlin type system to help you avoid NullPointerExceptions

What is?

Is a type that can be null and essentially looks like this:

Type? == Type or null

var str: String? = null
Copy the code

In plain English, just think of it as a new type, and that way, if you meet

var a: Int? = 10
var b: Int = a / / an error
Copy the code

This side of this situation, will not feel surprised, after all, it is not the same type??

role

The display helps programmers avoid NullPointerException without affecting program performance

Nullable types resolve the null pointer exception at compile time and do nothing at run time, so there is no impact on runtime performance

Null-pointer exceptions are common in Java

int strLen(String s) {
    return s.length(); // s is null
}
Copy the code

In real Java projects, all if judgments are required

// Can use the ternary operator, one line, but also cumbersome
int strLen(String s)  {
    int len = 0;
    if (null == s || (len = s.length()) <= 0) {
        throw new RuntimeException("String length is empty")}return len;
}
Copy the code

Of course, Optional was introduced after jdk1.8, but it was troublesome, making the code verbose and causing performance problems

int strLen(String s) {
    return Optional.ofNullable(s).orElse("").length();
}
Copy the code

Rewriting this function with Kotlin requires the programmer to actively determine whether the function accepts null arguments. If support is needed,

fun strLen(s: String?).= s? .lengthCopy the code

In the code above, s? If s is null, the function returns null. The caller can use the return value null to determine if s is null

If the argument must not be null

fun strLen(s: String) = s.length
Copy the code

Nullable types also have intelligent conversions, just like the is Int conversion for when

var a: String? = "zhazha"
var b: String
if(a ! =null) {
    b = a // This line of code will not report an error
    println(b)
}
Copy the code

How does it work?

Method one: Use the secure call operator? .

In the previous example code, s? .length will be found? Operator, which is equivalent to

if (s == null) null else s.length
Copy the code

If s == null all of s? The value of the.length expression is null, which occurs when the expression can be null

val len: Int? = s? .lengthCopy the code

The variable type that receives the return value from this function should also be nullable; after all, the result could be NULL

So use the safe call operator? The variable that receives the result also needs a nullable operator

The other? Operators can also be called chaining, as in:

valname:String? = person? .children? .nameCopy the code

As long as the result of one step is null, the following code will not run, and the entire expression will be null

Method two: the Elvis operator? :

val firstName: String? = "zhazha"
vallastName: String = firstName ? :""
Copy the code

And you can see that with this approach, right? The operator disappears

Similar to:

if (firstName == null) "" else firstName
Copy the code

Elvis goes like this:

vallastName: String = firstName ? :throw Exception("Error")
Copy the code

Method 3: If

if(firstName ! =null) {
    val lastName: String = firstName
}
Copy the code

Use this method when you feel the code is less readable

Method four: Use non-null assertion operators!!!!! .

Can you really take it off this way? Null-pointer detection is turned off, and null-pointer exceptions are no longer required for variables in expressions

val firstName: String? = null
val lastName: String = firstName
Copy the code

This method is not recommended unless you can guarantee that the value is never null, such as not using singletons defined by Object

Mode 5: Prerequisite functions

All of these functions can come off, right? coat

fun main(args: Array<String>) { val firstName: String? = null // checkNotNull(firstName) // checkNotNull(firstName) {"firstName is null "} // requireNotNull(firstName) { } // check(firstName! = null) require(firstName ! = null) val s: String = firstName }Copy the code

Security transformationas?

We learned in the previous section that as, as a strong cast operator, can be used in conjunction with is casts, but a ClassCastException is reported if the cast is unsuccessful

So Kotlin created as? Method of use

private fun sum(a: Any, b:Any) {
    val c: Int? = a as? Int
    val d: Int? = b as? Int
}
Copy the code

In this example, null is returned to c if the Any argument points to a type other than Int, otherwise the override succeeds

As commonly? Cooperate with? Use:

private fun sum(a: Any, b:Any): Int {
    val c: Int = a as? Int? :0
    val d: Int = b as? Int? :0
    return c + d
}
Copy the code

letfunction

Let function source:

public inline fun <T, R> T.let(block: (T) - >R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)}Copy the code

You can see that it’s an extension function

fun main(args: Array<String>) {
    val str:String? = nullprintln(str? .let { it.length +100 }) // null
}
Copy the code

Print null, STR == null, so STR? The let function after == null will not execute and will return NULL but we need null to be equal to 0 and eventually print 100

fun main(args: Array<String>) {
    val str:String? = nullprintln(str.let { (it? .length ? :0) + 100})}Copy the code

See str.let in the code? STR == null but str.let does not return an error. See the advantage of extending functions? The extension function only takes the target object’s this as a formal argument, but this is null.

Nullability extension function

Extension functions for nullable type definitions handle null problems

val str: String? = null

if (str.isNullOrBlank()) {
    throw Exception("str == null or str is blank")}Copy the code

Source code is like this: return this = = null | | this. IsBlank ()

IsNullOrBlank () = null; isNullOrBlank() = null; IsNullOrBlank will not return an error.

Only extension functions can do this; calls to normal member methods are distributed through the object instance, so they can never be executed when the instance is NULL.

Lazy initializationlateinit

Most of the time, not all of the initialization of member attributes needs to be done within the constructor, as shown in the following code for member attribute A

private class MyClass(val b: Int) {
    var a: Person / / an error
    constructor(a: Person, b: Int) : this(b) {
        this.a = a
    }
    
    init {
        // init balabala}}Copy the code

The main problem is that the initialization order of the Kotlin objects is

Calling the main constructor => member properties outside the main constructor or init blocks (depending on the order in which they are defined) => Call the constructor again

For example, b is inside the main constructor and A is outside the main constructor

When building an object, the secondary constructor calls two constructors, the primary constructor and the secondary constructor

Properties outside the main constructor and init code blocks are both put inside the main constructor by the compiler when constructing an object

// Suppose this is the primary constructor
constructor(b: Int) {
    this.b = b
    // This is the main structure
    Init and attributes outside the main constructor are next
    this.a = ? // error, I did not know what to initialize variable a to???? So the error was reported
    // init balabala
    // Then call the constructor again (if you use the secondary constructor to construct an object)
}

// Then call the constructor
constructor(a: Int) {
    this.a = a The primary constructor reported an error when initializing the secondary constructor. The secondary constructor did not have time to build an object
}
Copy the code

Var a: Int = 0 is a common solution to this problem, but in some architectures, there is a special initialization scheme that does not require the programmer to help initialize it, such as Spring

This is where the Lateinit keyword is needed

private class MyClass(val b: Int) {
    lateinit var a: Person
    constructor(a: Person, b: Int) : this(b) {
        this.a = a
    }
    
    init {
        // init balabala}}Copy the code

But this keyword is limited:

  1. Can’t modifyvalProperty that can only be decoratedvar
  2. You cannot modify underlying data types, such as properties like Int Double Float Long

Lazy load initialization (lazy initialization)

class Player {
    val config: String by lazy { loadConfig() }
    fun loadConfig(a): String {
        println("load Config...")
        return "xxxxxxxxx"}}Copy the code
public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
Copy the code

Lazy is passed the function type, a function type that returns type T without arguments () -> T

Nullability of type parameters (generic nullability)

Type parameter passes can be null

fun <T> printHashCode(t: T){ print(t? .hashCode()) }fun main(args: Array<String>) {
    printHashCode(null)}Copy the code

The type parameter is passing T without any, okay? But you can still pass null, and then inside the function if you don’t write t, right? A null-pointer exception is reported

Null is of type Any?

The issue of nullability between Kotlin and Java

Platform type

When Kotlin calls Java functions, he can’t tell if Java’s arguments are nullable, so he generalizes platform types

Java platform type = kotlin nullable type or Kotlin non-nullable type

This judgment is left to the programmer’s discretion

In Java, create a Person

public class Person {
    private final String name;
    public String getName(a) {
        return name;
    }
    public Person(String name) {
        this.name = name; }}Copy the code

Used in Kotlin

fun yellAt(person: Person) {
// println(person.name.toUpperCase() + "!!!" ) // java.lang.NullPointerException: person.name must not be null
    println(person.name?.toUpperCase() + "!!!")}fun main(args: Array<String>) {
    val person = Person(null)
    yellAt(person)
}
Copy the code

Person: The person parameter has no nullability? Person.name?.toupperCase () can be used as a nullable type, and person.name. ToUpperCase () can be used as a non-nullable type

Kotlin in Person! Represents a platform type from the Java platform, users can not use! It simply informs the programmer that the variable is not nullable

Platform type encounters inheritance

When Kotlin inherits and overwrites Java functions, he can choose either nullable or non-nullable types

public interface StringProcessor {
    void process(String value);
}
Copy the code
class StringPrinter : StringProcessor {
    override fun process(value: String) {
        println(value)
    }
}

class NullableStringPrinter : StringProcessor {
    override fun process(value: String?).{ println(value ? :"")}}Copy the code