官术网_书友最值得收藏!

Strict null safety

According to Agile Software Assessment (http://p3.snf.ch/Project-144126) research, a missing null check is the most frequent pattern of bugs in Java systems. The biggest source of errors in Java is NullPointerExceptions. It's so big that speaking at a conference in 2009, Sir Tony Hoare apologized for inventing the null reference, calling it a billion-dollar mistake (https://en.wikipedia.org/wiki/Tony_Hoare).

To avoid NullPointerException, we need to write defensive code that checks if an object is null before using it. Many modern programming languages, including Kotlin, made steps to convert runtime errors into compile time errors to improve programming language safety. One of the ways to do it in Kotlin is by adding nullability safeness mechanisms to language type systems. This is possible because the Kotlin type system distinguishes between references that can hold null (nullable references) and those that cannot (non-nullable references). This single feature of Kotlin allows us to detect many errors related to NullPointerException at very early stages of development. The compiler, together with the IDE, will prevent many NullPointerException. In many cases compilation will fail instead of the application failing at runtime.

Strict null safety is part of the Kotlin type system. By default, regular types cannot be null (can't store null references), unless they are explicitly allowed. To store null references, we must mark the variable as nullable (allow it to store null references) by adding a question mark suffix to the variable type declaration. Here is an example:

    val age: Int = null //1, Error 
    val name: String? = null //2 
  1. The compiler will throw an error, because this type does not allow null.
  2. The compiler will allow null assignment, because type is marked as nullable using the question mark suffix.

We are not allowed to call a method on a potentially nullable object, unless a nullity check is performed before a call:

    val name: String? = null 
    // ... 
    name.toUpperCase() // error, this reference may be null 

We will learn how to deal with this problem in the next section. Every non-nullable type in Kotlin has its nullable type equivalent: Int has Int?, String has String?, and so on. The same rule applies for all classes in the Android framework (View has View?), third-party libraries (OkHttpClient has OkHttpClient?), and all custom classes defined by developers (MyCustomClass has MyCustomClass?). This means that every non-generic class can be used to define two kinds of type, nullable and non-nullable. A non-nullable type is also a subtype of its nullable equivalent. For example, Vehicle, as well as being a subtype of Vehicle?, is also a subtype of Any:

The Nothing type is an empty type (uninhabited type), which can't have an instance. We will discuss it in more detail in Chapter 3, Playing with Functions. This type hierarchy is the reason why we can assign a non-null object (Vehicle) in a variable typed as nullable (Vehicle?), but we cannot assign a nullable object (Vehicle?) in a non-null variable (Vehicle):

    var nullableVehicle: Vehicle?  
    var vehicle: Vehicle 
 
    nullableVehicle = vehicle // 1 
    vehicle = nullableVehicle // 2, Error 
 
  1. Assignment possible.
  2. Error because nullableVehicle may be a null.

We will discuss ways of dealing with nullable types in the following sections. Now let's get back to type definitions. When defining generic types, there are multiple possibilities for defining nullability, so let's examine various collection types by comparing different declarations for a generic ArrayList containing items of type Int. Here is a table that presents the key differences:

Type declaration

List itself can be null

Element can be null

ArrayList<Int>

No

No

ArrayList<Int>?

Yes

No

ArrayList<Int?>

No

Yes

ArrayList<Int?>?

Yes

Yes

 

It's important to understand different ways to specify null type declarations, because the Kotlin compiler enforces it to avoid NullPointerExceptions. This means that the compiler enforces a nullity check before accessing any reference that potentially can be null. Now let's examine a common Android/Java error in the Activity class's onCreate method:

    //Java 
    @Override 
    public void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        savedInstanceState.getBoolean("locked"); 
    } 

In Java, this code will compile fine and accessing null objects will result in an application crash at runtime throwing NullPointerException. Now let's examine the Kotlin version of the same method:

    override fun onCreate(savedInstanceState: Bundle?) { //1 
         super.onCreate(savedInstanceState) 
         savedInstanceState.getBoolean("key") //2 Error 
    } 
  1. savedInstanceState defined as nullable Bundle?.
  2. The compiler will throw an error.

The savedInstanceState type is a platform type that can be interpreted by Kotlin as nullable or non-nullable. We will discuss platform types in the following sections, but for now we will define savedInstanceState as a nullable type. We are doing so because we know that null will be passed when an Activity is created for the first time. An instance of Bundle will only be passed when an Activity is recreated using a saved instance state.

We will discuss functions in Chapter 3 , Playing with Functions, but for now, we can already see that the syntax for declaring functions in Kotlin is quite similar to Java.

The most obvious way to fix the preceding error in Kotlin is to check for nullity exactly the same way as in Java:

    override fun onCreate(savedInstanceState: Bundle?) { 
        super.onCreate(savedInstanceState)
    } 
 
    override fun onCreate(savedInstanceState: Bundle?) { 
        super.onCreate(savedInstanceState) 
 
        val locked: Boolean 
        if(savedInstanceState != null) 
            locked = savedInstanceState.getBoolean("locked")  
        else 
            locked = false 
    } 

The preceding construct presents some boilerplate code, because null-checking is a pretty common operation in Java development (especially in the Android framework, where most elements are nullable). Fortunately, Kotlin allows a few simpler solutions to deal with nullable variables. The first one is the safe call operator.

主站蜘蛛池模板: 明水县| 辉南县| 苍山县| 瑞安市| 兴仁县| 将乐县| 揭西县| 襄樊市| 湛江市| 东兰县| 安乡县| 碌曲县| 甘南县| 宝坻区| 西充县| 扬中市| 裕民县| 德庆县| 城步| 古丈县| 常德市| 乌审旗| 辽阳县| 万盛区| 泗洪县| 怀集县| 灵台县| 通河县| 天全县| 长春市| 鹿邑县| 丹巴县| 太白县| 焉耆| 浑源县| 大洼县| 剑川县| 玉溪市| 武强县| 呼伦贝尔市| 襄樊市|