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
let
function
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:
- Can’t modify
val
Property that can only be decoratedvar
- 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