Brief description: I haven’t updated this article for a long time, and this is probably the second article in 2019. Many friends have left messages on the official account saying whether they are broken or running away. I’m ok. I’m not running away. I’m just thinking about the main direction of the next article. How can I help others while improving myself? During this period, I have been constantly recognizing myself and understanding myself, discovering my shortcomings and how to timely check and fill my deficiencies. Let’s get down to business:
The Kotlin type system involves an important concept known as nullability and why Kotlin can reduce nullpointer exceptions to some extent compared to Java. In addition, Kotlin defines its type system in a completely different way from Java. It is also because of this type system that Kotlin has the natural advantage of having a significantly lower frequency of null pointer exceptions than Java does. In addition, Kotlin considers using a completely different type of system from Java and how it can be extremely compatible and interoperable.
First, consider a few concepts
Before we dive into the Kotlin type system, let’s consider a few concepts that, without clarity, make it difficult to fundamentally understand the Kotlin type system and why Kotlin is superior to Java in terms of type systems.
- 1. The nature of types
What is the nature of a type? Why do variables have types? Both of these questions are well answered on Wikipedia. A type is essentially a classification of data that determines the possible values of that type and the operations that can be performed on the values of that type. Special attention should be paid to the following statement: “The possible values of this type and the operations that can be done on the values of this type.” Since the Java type system is not 100% compliant with this rule, this is also a problem with the Java type system, which will be examined in detail below.
- 2. Classes and types
When it comes to class and type estimation, many developers tend to ignore the distinction between them, because in real application scenarios the distinction is not that fine. We tend to use classes as if they were types, which are two very different things. In fact, in Java, For example, List
, Lis
, and List. For the former, List
and List
can only be types and not classes, while for List, it can be either a List class or a type (a native type in Java). In fact, Kotlin takes this concept to a higher level, because Kotlin has an extra nullable type for each class. For example, the String class has two types: String and String, right? Nullable type. In Java, there is only one type for each class (the class itself), so it is often ignored.
We can divide Kotlin’s classes into two broad categories (and Java does the same): generic classes and non-generic classes
A generic class
Non-generic classes are the most common classes in development. When a generic class defines a variable, its class is actually the type of the variable. For example: var MSG: String Here we can say that the String class and the MSG variable are of the same type. But there’s a special type in Kotlin and that’s nullable, which we can define as var MSG: String? Where the String class and MSG variable String, right? The types are different. So in Kotlin a class usually has at least two types. So classes and types are not the same thing.
A generic class
Generics are more complex than non-generic classes, in fact, one generic class can correspond to an infinite number of types. It’s easy to see why. As we know from the previous article, we define generic parameters when we define a generic class. To get a valid generic type, we need to replace the type parameters in the definition with the concrete type arguments in the external use place. We know in Kotlin that List is a class, it’s not a type. It can be derived from an infinite number of generic types such as List
, List
, List
>, List
- Subclasses, subtypes and superclasses, supertypes
A subclass is a derived class that inherits its superclass. For example, class Student: Person(), where Student is generally called a subclass of Person, and Person is the superclass of Student.
Subtypes and supertypes are completely different. We know from the above class and type differences that a class can have many types, so subtypes are not just as strict as the inheritance relationship of subtypes. The general rule for subtype definition is that whenever A value of type A is needed, it can be replaced with A value of type B, and then type B can be said to be A subtype of type A or A supertype of type B. It is obvious that rules for subtypes are looser than rules for subtypes. So we can analyze the following examples:
Note that a type is also a subtype of its own, and it is obvious that Person must be replaceable wherever a value of the Person type appears. Subclass relationships are also subtype relationships in general. Values like String certainly do not replace values of Int, so they do not have a subtype relationship
Another example, all non-null types of a class are subtypes of the nullable type of that class, but not the other way around, like Person non-null type Person, right? Nullable subtype, obviously, any Person? Wherever a value of a nullable type occurs, it can be replaced with a value of the Person non-nullable type. In fact, I can experience these in the development process, for example, careful students will find that in the Kotlin development process, if a function receives a nullable type parameter, it is legal to pass in a non-nullable type argument at the call place. But if a function accepts arguments of a non-null type, passing in arguments of nullable type will prompt you that there may be a null pointer problem and you need to make a non-null check. Because we know that non-null types are safer than nullable types. Here’s a picture to understand:
The nature of null-pointer exceptions in the Java type system
With that said, let’s take a look at some of the basic types in Java and see what’s wrong with applying the definition of type nature.
- Use type definition validation
int
Type:
For example, a variable of type int can only store data of type int. We all know that it is stored in 4 bytes. The value ranges from -2147483648 to 2147483647. There seems to be nothing wrong with the int type and the nature of the type statement so perfectly. But what about strings? Is it the same? Please read on
- Use type definition validation
String
Type or other type that defines a class:
A variable of type String, for example, can have two values in Java: an instance of the String class and null. The first type of null allows you to call all the operation methods on String, but the second type of null allows you to call all the operation methods on String. If you force a null to operate on a String, congratulations. You will get a NullPointerException NullPointerException. For robustness in Java, this requires the developer to make extra judgments about String values and then handle them accordingly. If you don’t make extra judgments, it’s easy to get null-pointer exceptions. This leads to multiple values of a variable of the same type, but cannot be treated equally and consistently. All values of type int are treated identically. All possible values of type int can perform the same operation. Here’s an interesting example:
Java instanceof doesn’t even recognize null as a String value. The operations on these two values are also completely different: a real String allows you to call any of its methods, whereas a null value allows very limited operations. So how does the Kotlin type system solve this problem? Please read on.
3. How Kotlin type system solves the problem (why nullable types are designed)
In the Java type system, the type of String or any other custom class does not seem to conform to the definition of the type itself, but all possible values of that type are treated differently, resulting in ambiguity. The immediate problem is that the developer has the additional burden of making non-null judgments, which can cause the program to crash if not handled properly. This is the nature of the null-pointer problem in Java.
To get to the heart of the problem, Kotlin did a great job of splitting up types, splitting all of Kotlin’s types into two types: non-null types and nullable types; In other words, only instances of the String class do not have null values. Therefore, you can use all methods of the String class for non-empty values. There is no ambiguity. Would, of course, also there is null, then you can use the nullable types, in the use of nullable types of variables when the compiler for nullable types at compile time do do certain judgment, if there is a nullable type of variable operating method, the corresponding class will prompt you need to do additional sentence empty processing, then developers do it according to the prompt to empty processing, Imagine doing that. Would your Kotlin code still have a null pointer? (It is important, however, to specify whether a variable is nullable or non-nullable. If a nullable type is defined, you are responsible for it, and the compiler prompts you to do extra nullable processing for it.) . Let’s take a look at some examples:
-
1. Variables or constants of a non-null type cannot accept null values
-
2. Is in a variable or constant of a non-null type (equivalent to Java instanceof)
-
3, nullable variable or constant directly operating on the corresponding method will cause an obvious compilation error and prompt nullable operation
However, the above are all Java can not give you, so Java programs generally exist in three states: a Buddha nullation, often there will be null pointer problems. The other is to call nothing at all, but the code is full of if-else code, which is very unreadable. Finally, developers who are familiar with the logic of the program and the flow of data are able to determine where nulling is needed and where it is not, which is extremely demanding because people make mistakes all the time.
Four, can be empty type
- Safe call operator “? .”
? Is null executed if not null? . Otherwise, null is returned
text? .substring(0.2) // equivalent to if(text! Text. Substring (0,2) else null
Copy the code
In fact, Kotlin is really worried about type nullifying, and we all know that nullifying in Java is nothing more than if-else or? XXX: XXX ternary operator to achieve. But sometimes the whole code is an “arrow” when nesting nullates, and the readability is poor. From the above examples? .saves a lot of code than if-else, which doesn’t fully reveal its benefits, especially in this example.
If-else nesting in Java
Safe call operator in Kotlin? . Chain call processing
Do you think Kotlin might be better for you compared to the two implementations? .chain calls untangle nested if-else processing.
- 2. Elvis operator “? :”
If? If the preceding expression is null, execute? The following expression, which will usually be the same as? .Together. (Note: This is similar to the Java? XXX: XXX tri operator is different) carbon (29).png
- 3, safe type conversion operator as?
Return null if the cast fails, otherwise return the correct cast value
val student = person as? StudentPerson as Student else null
Copy the code
- 4. Non-null assertion operators!! Simplifies non-empty expressions with contracts
Non-empty assertion operator!! “Forces the compiler to tell the value of this variable cannot be null, which is risky. Throw a null pointer exception if null exists.
Many Kotlin developers hate this operator because it is not elegant enough to write and will affect the readability of their code. How to avoid using it in Kotlin code! Operators. Please refer to my previous article on how to remove all in your Kotlin code!! (non-empty assertion).
Non-null assert that the usage scenario is actually exist, for example, you have to a variable in a function to empty processing, but the logic behind again used to it and you can be sure that it would not make empty, then maybe the compiler can’t identify whether it is empty, but because it is a nullable type, then it will prompt you to empty processing, Very annoying is not, a lot of people may be adopted at this time!! It really lacks readability.
A new solution to the above problem, in addition to the solution given in the previous article, is the contract (which actually actively tells the compiler a rule so that it does not prompt short processing). The contract is officially proposed as version 1.3 of Kotlin, It’s Experimental(like custom contracts), but Kotlin’s internal code already uses contracts. See how built-in contracts solve this problem in my previous JetBrains developer newsletter, the new Kotlin1.3 feature (Contract contracts and coroutines).
- The standard library functions let, also, run, apply, and with can be used to simplify nullable expressions. The standard library functions let, also, run, apply and with can be used to simplify nullable expressions. Run, with, let, also and apply
- 6. Java-compatible platform types
From the above we can see that Kotlin has a completely different type system than Java. There is no such thing as nullable and non-nullable types in Java. But we all know that Kotlin and Java are very interoperable and almost fully compatible with Java. So how is Kotlin compatible with variable types in Java? We certainly call Java code a lot in Kotlin, and one might reply that Java uses @notnull and @nullable annotations. Indeed Kotlin annotations can identify a variety of different styles, including javax.mail. The annotation, android. Support. The annotation, org. Jetbrains. The annotation, etc. However, some of the previous third-party libraries were not written in such a standard way, so it is obviously not possible to solve this problem in this way.
So Kotlin introduced a new concept called platform type. Platform type is essentially a type that Kotlin does not know about nullability information and can be treated as either nullable or non-nullable. This means that you are just as responsible for what you do on this type as you are in Java code, and Kotlin is not going to clean up your mess in Java. So in Java, when Kotlin calls a function parameter, the system handles nullable types by default (for security reasons). If you specify that it is not null, you can change it to a non-null type, and the system does not report a compilation error, but once you do, you must ensure that it is not null.
So the question is, a lot of people say why don’t you just cast all nullable types for security reasons? In fact, this scheme seems to be feasible, but in fact it is a little bit wrong, and it is redundant to do a lot of extra nulls for variables that clearly cannot be null. Otherwise, non-null types have no reason to exist.
Basic data types and other basic types
- 1. Basic data types
We all know that Java makes a distinction between primitive data types and wrapper types. For example, a variable of the basic data type int stores its value directly. A variable that refers to a String (wrapper type) only stores a reference to the object’s memory address. Primitive data types have the natural advantage of efficient storage and delivery, but methods of these types cannot be called directly, and they cannot be used as generic argument types in Java collections.
In fact, Kotlin doesn’t have the basic data type and wrapper type that Java does, it’s always the same type. Many people might ask that since the basic data type and wrapper type are the same in Kotlin, does that mean that Kotlin uses reference types to store data? Is it very inefficient? No, Kotlin tries to convert Int to Java primitives like Int at runtime, and to Java wrappers like Integer when it comes to collections or generics. This is actually a low-level optimization, as to what scenario is converted to int and what scenario is converted to Integer. See Kotlin’s autoboxing and High Performance Exploration for inline classes in this article.
Basic data types are also divided into nullable and non-nullable types. For details, see the following type hierarchy diagram:
- 2, What do you like? type
The Any type is the supertype of all non-null types. Any? A type is a supertype of all types, that is, a non-null supertype is also a supertype of all nullable types. Because Any? Is the supertype of Any. For specific levels, please refer to the following figure:
- 3. Unit type
The Unit type, which is an empty type in Kotlin, is equivalent to the void type in Java, and can be omitted by default
- 4, Nothing type
Nothing is a subtype of all types, it’s a subtype of all non-null types and it’s a subtype of all nullable types, because Nothing is Nothing, right? Whereas Nothing? Again, subtypes of all nullable types. See the following hierarchy diagram for details:
Collection and array types
- The difference between a Collection read-only set and a MutableCollectio mutable set is as follows:
In a Collection, there are only methods to access elements, but no methods like Add, remove, and clear. In a MutableCollection, there are more methods to modify elements than in a Collection.
A read-only Collection is associated with a mutable Collection:
MutableCollection is actually a subinterface of the Collection interface.
- 2. Class relationships between sets
From the collection. kt file you can see that there are sets Iterable and MutableIterable, Collection and MutableCollection, List and MutableList, Set and Mut AbleSet, Map, and MutableMap. So what’s the class diagram between them?
Iterable and MutableIterable are the parents of read-only and mutable collections, respectively. Collection inherits from Iterable. List and Set inherit from Collection. And then the MutableMap interface is inherited from Map.
- 3. The correspondence between collections in Java and collections in Kotlin
We just said that the design of collections in Kotlin is different from that in Java, but each Kotlin interface is an instance of its corresponding Java collection interface, that is, the collection in Kotlin has some correspondence with the collection in Kotlin. The ArrayList and HashSet classes in Java are actually implementations of the MutableList and MutableSet collection interfaces in Kotlin. The class diagram above can be further refined by adding this relationship.
- 4. Initialization of collections
Since Kotlin’s collections are mainly divided into read-only and mutable collections, the functions for initializing read-only and mutable collections are also different. For a List set, the listOf() method is used to initialize read-only sets. For a mutable set, mutableListOf() is used or ArrayList
is created. Since the internal implementation of mutableListOf() also creates an ArrayList, this ArrayList is actually java.util.arrayList
in Java, It’s just using typeAlias in Kotlin (the use of TypeAlias was described in detail in the blog earlier). About the specific contents please refer to this kind of kotlin. Collections. TypeAliasesKt implementation
- 5, collection of precautions for use
Note # 1: Use read-only collections in preference everywhere in your code, and use mutable collections only when you need to modify the collection
Note 2: Read-only collections are not necessarily immutable, similar to the read-only and immutable principle for Val.
Note 3: you cannot pass a read-only collection as an argument to a function with a mutable collection.
- 6. Set conversion rules for platform types
As with the nullability platform types mentioned earlier, there is no way to know the type of nullability information in Kotlin, and it can be treated as either nullable or non-nullable. The platform type of a collection is similar in that variables of a collection type declared in Java are treated as platform types. A platform-type collection is essentially a collection of unknown variability, and Kotlin can think of it as a read-only collection or a mutable collection. It doesn’t really matter, because you just have to choose according to your needs, and everything you want to do will work, unlike nullability platforms, which have the risk of extra judgment operations and null Pointers.
Note: However, there are three things to consider when deciding which Kotlin type to use to represent a set type variable in Java:
- 1. Is the set empty?
If it’s idle, change it to the set in Kotlin, ok? For example, List
in Java is converted to List
in Kotlin?
- 2. Is the element in the set empty?
If it is empty, replace it with the set generic argument in Kotlin. For example, List
in Java is converted to List
- 3. Will the operation method modify the collection? (Read-only or mutable collection)
If it is read-only, for example, List
in Java is converted to List
in Kotlin; If it is mutable, for example, List
in Java is converted to MutableList
in Kotlin.
Note: of course, one or more of the above three cases can occur at the same time, so the conversion to the collection type in Kotlin is also the final recombination type of many cases.
Seven,
That’s pretty much it for Kotlin’s type system. In fact, if you take a closer look at why Kotlin’s type system is designed this way, it does make some sense. We often hear that Kotlin has fewer null pointer exceptions than Java. Many people say that Kotlin has fewer null pointer exceptions than Java.
We all know that Android development has reached a plateau, that the bubble is deflating, and that the demands on Android developers are going to be higher and higher. The era of “only use apis” is long gone, so developers need to constantly adjust and improve their capabilities to cope with these changes. Analysis of the source code partners know to understand the source code of the most critical point is the use of data structure algorithm and the use of some advanced design patterns. Because of this, the future direction of the article will be aimed at data structure algorithm, design pattern, source code analysis to do some output, the recent plan is a Weekly Kotlin related article (original or translation), a weekly design pattern related and a weekly data structure algorithm related (combined with the topic on LeetCode).
Welcome to the Kotlin Developer Association, where the latest Kotlin technical articles are published, and a weekly Kotlin foreign technical article is translated from time to time. If you like Kotlin, welcome to join us ~~~
Welcome to Kotlin’s series of articles:
Original series:
- How do you make your callbacks more Kotlin
- Jetbrains developer briefing (3) Kotlin1.3 new features (inline class)
- JetBrains developer briefing (2) new features of Kotlin1.3 (Contract and coroutine)
- Kotlin/Native: JetBrains Developer’s Day (Part 1
- How to overcome the difficulties of generic typing in Kotlin
- How to overcome the difficulties of Generic typing in Kotlin (Part 2)
- How to overcome the difficulties of Generic typing in Kotlin (Part 1)
- Kotlin’s trick of Reified Type Parameter (Part 2)
- Everything you need to know about the Kotlin property broker
- Source code parsing for Kotlin Sequences
- Complete analysis of Sets and functional apis in Kotlin – Part 1
- Complete parsing of lambdas compiled into bytecode in Kotlin syntax
- On the complete resolution of Lambda expressions in Kotlin’s Grammar
- On extension functions in Kotlin’s Grammar
- A brief introduction to Kotlin’s Grammar article on top-level functions, infix calls, and destruct declarations
- How to Make functions call Better
- On variables and Constants in Kotlin’s Grammar
- Elementary Grammar in Kotlin’s Grammar Essay
Translated by Effective Kotlin
- The Effective Kotlin series considers arrays of primitive types for optimal performance (5)
- The Effective Kotlin series uses Sequence to optimize set operations (4)
- Exploring inline modifiers in higher-order functions (3) Effective Kotlin series
- Consider using a builder when encountering multiple constructor parameters in the Effective Kotlin series.
- The Effective Kotlin series considers using static factory methods instead of constructors (1)
Translation series:
- Remember a Kotlin official document translation PR(inline type)
- Exploration of autoboxing and High performance for inline classes in Kotlin (ii)
- Kotlin inline class full resolution
- Kotlin’s trick of Reified Type Parameter
- When should type parameter constraints be used in Kotlin generics?
- An easy way to remember Kotlin’s parameters and arguments
- Should Kotlin define functions or attributes?
- How to remove all of them from your Kotlin code! (Non-empty assertion)
- Master Kotlin’s standard library functions: run, with, let, also, and apply
- All you need to know about Kotlin type aliases
- Should Sequences or Lists be used in Kotlin?
- Kotlin’s turtle (List) rabbit (Sequence) race
Actual combat series:
- Use Kotlin to compress images with ImageSlimming.
- Use Kotlin to create a picture compression plugin.
- Use Kotlin to compress images.
- Simple application of custom View picture fillet in Kotlin practice article