Author’s brief introduction

He Lun, senior R&D manager of BU Mobile terminal of Ctrip Vacation, is responsible for r&d of front-end page of booking process of group tour products on iOS and Android platforms. Keen interest in new technology.

Since Google announced Kotlin as the official Android development language in 2017, Kotlin has been a hit with Android developers. Its strong security, simplicity and interoperability with Java bring refreshing development experience for developers, but also greatly improve the efficiency of Android native code development.

However, most developers use Kotlin only to translate Java code logic into Kotlin syntax. Kotlin and Java are two fundamentally different design languages, although they are highly interoperable.

This article, assuming that the reader has some basic Kotlin development background, explains some useful language features that are kotlin-specific to help developers write more “Kotlin-style” code. These language features include null-safety, Elvis expressions, compact strings, and more.

01

More secure pointer manipulation

In Kotlin, everything is an object. There are no keywords such as int and double, only classes such as int and double.

All objects are held by a pointer, and there are only two types of Pointers: var for mutable and val for immutable. For better null safety, all objects in Kotlin explicitly specify nullable or non-nullable properties, that is, whether the object can be null.

For nullable objects, calling their methods directly will result in an error at compile time. This eliminates the possibility of nullPointerExceptions.

As shown above, the compiler reports an error

Error:Only safe (? .). or non-null asserted (!! .). calls are allowed on a nullable receiver of type String

02

? Expressions and Elvis expressions

Kotlin specific? Expressions and Elvis expressions allow you to write much cleaner code while still being safe. For example, we commonly delete child controls in Android page development, written in Java like this:

To get more secure code, we had to add a lot of if else statements to make sure we didn’t generate null-pointer exceptions. But the Kotlin? Operators can implement the above logic very succinctly:

What about this? What is the logic inherent in expressions? For example, if view == NULL, no subsequent calls will be made, and the entire expression will return NULL without throwing an exception. In other words,? The entire expression returns NULL whenever an operation object is NULL.

In addition to? Expressions. Kotlin has another killer called Elvis expressions, which is? : expression, which together can express complex logic in an ultra-compact form.

Take the expression above, which we divide into two parts with a red line. If the preceding part is null, the entire expression returns the value of c; otherwise, the value of the preceding part. Translate it into Java code like this

It’s the same thing as this

That is, the meaning of the Elvis expression is for the whole? The Elvis expression gives the expression a custom default value if the entire expression is already null. This further ensures empty safety while keeping the code concise.

03

A more concise string

Like Java, Kotlin can initialize string objects with literals, but Kotlin uses triple quotation marks to facilitate writing long strings. This method also does not require the use of escape characters. What you see is what you get with strings.

At the same time, Kotlin introduced string templates, which can access variables and use expressions directly in strings:

04

Powerful when statement

Instead of the switch operator, Kotlin uses the WHEN statement instead. Similarly, when compares its parameters to all branch conditions in order until a branch satisfies the condition. If none of the other branches meet the criteria it will go to the else branch.

But functionally the WHEN statement is much more powerful. The first point is that we can use any expression (not just a constant) as a branching condition, which switch can’t. As shown in the following code, the first three branch conditions are: 1, the variable is in the interval [1, 10]; 2, the variable x is not in the interval [10, 20]; 3, the variable x is a string. This expression is almost impossible to implement with the switch statement, and can only be implemented with the if else chain.

Speaking of the if else chain, we can simply replace it with the when statement:

05

Object to compare

Java’s == operator compares reference values, whereas Kotlin’s == operator compares content, and === compares reference values. With this in mind, we can write code with cleaner logic:

The above code can be implemented directly with the WHEN statement

06

Nullable Receiver

NullableReceiver I translate this as “NullableReceiver.” To understand the concept of a receiver, let’s take a look at an important feature in Kotlin: extensions. Kotlin can extend the new functionality of a class in a way that is traceless, that is, we do not need to inherit the class or use design patterns like decorator, and that is transparent to the user, that is, the user uses the extension functionality of the class as if it were the class itself.

To declare an extension function, we need to prefix it with a receiver type, that is, the type to be extended, using the following code as an example:

This code adds a swap function to any MutableList<Int> we can call this function on any MutableList<Int> :

Where MutableList<Int> is the receiver of this extension function. It’s worth noting that Kotlin allows this receiver to be NULL so that we can write code that would seem unthinkable in Java. For example, if we want to convert an object to a string, in Kotlin we can write:

This code defines a null pointer object and then calls toString. A Nullable Receiver is a Nullable Receiver. The Nullable Receiver is a Nullable Receiver. A Nullable Receiver is a Nullable Receiver.

Extension functions can get a pointer to the receiver object, namely the this pointer. As you can see from the definition of this method, this method extends the Any class, and the receiver type is followed by? Any? Class to extend. As you can see, the extension function initially nulls the receiver and returns the “NULL” string if it is null. So calling toString does not Crash for any object.

07

The keyword object

As mentioned earlier, everything is an object in Kotlin. Object is a keyword in Kotlin, which generally means “object” and can be used differently in different scenarios.

The first is an object expression that allows you to directly create an object that inherits from an anonymous class of a type (or some type) without having to create the object’s class first. This is similar to Java:

Second, object literals. This feature extends number literals, string literals, to generic objects. The corresponding scenario is if we only need “one object”, no special supertype is required. The typical scenario is where we need to use disposable objects piecemeal, such as inside functions.

Third, object declaration. This feature is similar to the singleton pattern in Java, but we can implement it without writing boilerplate code for the singleton pattern.

Note that the above code declares an object, not a class, and we want to use the object by referring to its name:

08

Interesting colon

Syntactically, Kotlin makes heavy use of the colon (:), and we can summarize what this colon stands for in Kotlin.

Consider the following four scenarios:

  • In a variable definition, represents the type of the variable

  • In a class definition, represents the type of the base class

  • In a function definition, the type that represents the return value of the function

  • In anonymous objects, represents the type of the object

In general, Kotlin’s designers probably wanted to use the colon as a shorthand for the concept of type.

09

Observable attribute

Observable properties, essentially the observer pattern, can also be implemented in Java, but Kotlin does not need boilerplate code to implement the observer pattern. Before we talk about Kotlin’s observable properties, let’s look at the delegates in Kotlin. Similarly, delegation is also a design pattern, and its structure is shown below:

Kotlin supports it at the language level without requiring any boilerplate code. Kotlin can implement the base class interface by delegating all public members of a subclass to the specified object using the by keyword:

In the code above, Base is an interface and BaseImpl is an implementation class, which can be implemented by delegating all public members of the Derived class to b objects using the BY B statement. When we create a Derived class, we pass in an instance of BaseImpl directly in the constructor, so calling a Derived method is the same as calling a method of an instance of BaseImpl, and accessing a Derived property is the same as accessing an instance of BaseImpl.

Going back to the concept of observable property, Kotlin realizes observable property by Delegates. Observable () :

In the above code, name is an attribute, and changing its value automatically calls back the {kProperty, oldName, newName ->} lambda expression. In short, we can listen for changes in the name property.

What’s the use of observable properties? A classic Crash in ListView is an IllegalStateException if the data length is inconsistent with the Cell length in the Adapter. The root cause of this exception is that the notifyDataSetChanged was not called after the data was modified, causing the ListView not to refresh in time. We can eliminate this problem if we make the data observable and refresh the ListView directly in the observe callback method.

10

Function types

Everything in Kotlin is an object, including functions. In Kotlin, functions are themselves objects that can have types and be instantiated. Kotlin uses a series of function types like (Int) -> String to handle function declarations, such as the common clickback function:

Arrow notation is the right combination, (Int) – > (Int) – > Unit is equivalent to (Int) – > ((Int) – > Unit), but is not equal to ((Int) – > (Int)) – > Unit. You can give a function type an alias by using a type alias:

The biggest advantage of function objects is that callbacks can be easily implemented without the need for a Java proxy class. Let’s use the ScrollView sliding Callback as an example to see how much it costs to write a Callback in Java. For the caller, the MyScrollView class, we first need an interface for Callback (OnScrollCallback), which has an onScroll method to implement. You then need a property to hold the callback object. Finally, when the View slides, we call the onScroll of this callback object to implement the callback.

For the caller, the user of MyScrollView, we need an object that implements the OnScrollCallback interface. Then set it to the callback object of MyScrollView to implement the slide callback.

We’re just implementing a simple callback, why make it so complicated? Essentially, because Java functions are not objects, to implement a callback, we must implement a proxy class that wraps the function, otherwise we cannot pass the function to the caller.

Kotlin implements callbacks in a completely different way, because Kotlin’s functions are also objects, so we pass function objects directly to the caller.

Take a look at the code above, it’s that simple!

There are several common ways to instantiate a function type:

One is a block of code that uses function numerals, such as lambda expressions {a, b -> a + b}, or anonymous functions fun(s: String): Int {return s.ointornull ()? : 0}

The other is to use an already declared callable reference, including the top-level, local, member, extension function ::isOdd String::toInt, or the top-level, member, extension attribute List<Int>::size, or the constructor ::Regex

The third is an instance of a custom class that implements a function-type interface

Fourth, compiler inference

11

tool

For new Kotlin developers, the compiler provides handy gadgets that can even convert Java code directly into Kotlin code. Copy the Java code directly to the.kt file, and the compiler will display the following prompt:

Kotlin is 100% Compatible with Java because it eventually compiles to Java bytecode, which we can see compiled bytecode using Android Studio tools:

We can also decomcompile the compiled Java bytecode into Java code to get a glimpse of how Kotlin works:

In fact, Kotlin has more than just a few excellent language features mentioned in this article, such as function default parameters, extended properties, lazy initialization, local functions, data classes, and more. Welcome to communicate with us in the process of learning.

【 Recommended reading 】

  • Practice of AIOps in Ctrip

  • The React Fiber que

  • Ctrip AI model engine design and practice

  • Ctrip QA- Traffic playback system revealed

  • Ctrip flight ticket real-time data processing practice and application