Like and follow, no longer get lost, your support means a lot to me!

🔥 Hi, I am Ugly. This article has been collected by GitHub · Android-Notebook. Welcome to grow with Peng Chouchou. (Contact info on GitHub)


Today in history

On February 29, 2000, R 1.0.0 was released. The R language was originally developed by Ross Ihaka and Robert Jettman of the University of Auckland, New Zealand, and was developed by the “R Development Core Team”. R is a GNU project based on the S language. The syntax comes from Schema and is mainly used for statistical analysis, mapping, and data mining. RStudio is a widely used integrated development environment designed for the R language. — The Great Programmer


preface

Extensions are a language feature of Kotlin that adds new functions or properties to a class without modifying/inheriting the class. Extensions allow us to reasonably follow the open closed principle, and in most cases are a better choice than inheritance.


directory


Front knowledge

The content of this article will cover the following preconditions/related knowledge, thoughtful I have helped you to prepare, please enjoy ~

  • Java | the essence of the method call (including overloading and rewrite the difference)

1. Why use extensions?

In Java, we are used to wrapping common code into utility classes such as StringUtils, ViewUtils, etc. For example:

StringUtils.java

public static void firstChar(String str) {
    ...
}
Copy the code

To use it, we need to call stringutils.firstchar (STR). However, this traditional invocation is not straightforward enough or ideographic enough to allow the caller to ignore the strong connection between String and firstChar(). In addition, the caller wants to omit the StringUtils class name so that firstChar() looks more like a property and method inside the String, like this: “STR”.firstchar ().

To do this, you need to modify or inherit the String class in Java. However, String is a final class in the JDK and cannot be modified or inherited.

We can use the Kotlin extension to solve this problem. We can define firstChar as a String extension function:

StringUtils.kt

Define the String extension function fun String.firstchar () {... }Copy the code

In this case, you can use “STR”.firstchar (). Here we extend the String class without modifying or inheriting the String.

Summary: Extensions are a feature in Kotlin that allows you to add new functions or properties to a class without modifying/inheriting the class, more in line with the open closed principle.

OCP (Open Closed Principle)

The open closed principle is one of the principles of object-oriented software design: open to extension, but closed to modification.


2. Extension functions & extended attributes

2.1 Declaration extension

Declaration extensions are as simple as adding “class or interface name” to the declaration. The name of the class is called Receiver Type, and the object that calls the extension is called the receiver object. In most cases, extensions are declared as “top-level members,” such as:

Utils.kt

Declare the extension function: fun <T: Any? > MutableList<T>.exchange(fromIndex: Int, toIndex: Int) {val temp = this[fromIndex] this[fromIndex] = this[toIndex] this[toIndex] = temp}  val MutableList<Int>.sumIsEven get() = this.sum() % 2 == 0Copy the code

We can use it as a member function/property:

xxx.kt

Val list = mutableListOf(1,2,3) uses the extension function: list.exchange(1,2) uses the extension attribute: val isEven = list.sumisevenCopy the code

Tip: MutableList is the receiver type and list is the receiver object.

Inside an extension function, you can use this to refer to the receiver object as if it were a “member function,” but sometimes you can omit it. For example:

Declare an extended property: val MutableList<Int>.sumiseven get() = this.sum() % 2 == 0 // omit this in this.sum()Copy the code

2.2 Empty receiver

In Section 2.1 (p. 488), the “non-empty receiver type” was used to define the extension (no keywords in MutableList?). When an extension is called with “nullable variables”, a compile-time error is reported. Such as:

val list:MutableList<Int>? = null list.sumIsEven // Only safe (? .). or non-null asserted (!! .). calls are allowed on a nullable receiver of type MutableList<Int>?Copy the code

Following the hint, we know that we can define an extension using “nullable receiver type” and also internally nullable the receiver object using NULL == this. Such as:

Extension function fun <T: Any? For nullable receiver type > MutableList<T>? .exchange(fromIndex: Int, toIndex: Int) { if (null == this) return val temp = this[fromIndex] this[fromIndex] = this[toIndex] this[toIndex] = temp } Extended property of nullable receiver type val MutableList<Int>? .sumIsEven: Boolean get() = if (null == this) false else this.sum() % 2 == 0Copy the code

2.3 Calling in Java

The nature of an extension: An extension function is a static function defined outside of the class. The first argument to the function is an object of the receiver type. This means that the extension is invoked without the creation of adaptation objects or any additional runtime cost.

In Java, we just call the extension like we would call a normal static method. Such as:

xxx.java

ArrayList<Integer> list = new ArrayList<>(3); Use the extension function: utilskt. exchange(list, 1, 2); Use extended attributes: Boolean isEven = utilskt. getSumIsEven(list);Copy the code

2.4 Scope of the extension

When you define an extension, it does not automatically take effect throughout the project. In other package paths, you need to use improt import. Such as:

Import utils. exchange or import utils. *Copy the code

When you define “name extensions” in different packages and need to use them in the same file, you need to rename them using the as keyword. Such as:

Import Utils. Exchange as swap import Utils. Exchange as swapCopy the code

2.5 Precautions

  • Extension functions cannot access private or protected members

An extension function or an extension property is essentially a static method defined outside the class, so an extension cannot break the wrapper of the class to call a private or protected member.

  • 2. Cannot override extension functions

Extension functions are compiled as static functions in Java, are not part of the class, and are not polymorphic. Although you can define an extension function with the same name for both the parent class and the child class, which looks like a method override, the two functions are actually unrelated. When this function is called, the exact version of the function called depends on the “static type” of the variable, not the “dynamic type.”

Static method call

About the nature of the Java method call, before I wrote an article in the system analysis: Java | the essence of the method call (including overloading and rewrite the difference). Static method calls generate the InvokeStatic bytecode instruction after compilation, which handles the following logic:

  • 1. Compile: the symbolic reference of the method is determined and solidified into the parameters of the method call instruction in the bytecode;
  • 2. Class loading and parsing: according to the class name in the symbol reference, find the method whose simple name is consistent with the descriptor in the corresponding class. If found, convert the symbol reference into a direct reference; Otherwise, search in each parent class from the bottom up by inheritance;
  • 3,Call stage: the symbolic reference has been converted to a direct reference; callinvokestaticInstead of loading the object onto the operand stack, you simply push the required parametersinvokestaticThe instructions.
  • 3. If a class member function and its extension function have the same signature, the member function takes precedence

  • Extended properties have no supported fields and do not save any state

Extended properties are stateless and getter accessors must be defined. Since it is not possible to add additional fields to existing Java classes, there is no place to store supporting fields. For example, the following code is a compilation error:

val MutableList<Int>? .sumIsEven: Boolean = true // (X) Initializer is not allowed here because this property has no backing field get() = if (null == this) false else this.sum() % 2 == 0Copy the code

3. Functions in the library

In the Kotlin standard library, a series of generic inline functions are defined: t.ply, T.supply, T.let, T.run, and with. Do you have a clear understanding of their usage & nature, and are they all extension functions?

val str1: String = "".run {
    println(this.length)
    this
}

val str2: String = with("") {
    println(this.length)
    this
}

val str3: String = "".apply {
    println(this.length)
}

val str4: String = "".also {
    println(it.length)
}

val str5: String = "".let {
    println(it.length)
    it
}
Copy the code

In the examples above, we see that some functions scope using this, while others scope using it. What exactly do these two keywords refer to, and why the difference?

Let’s first find the declarations for these functions:

standard.kt

public inline fun <R> run(block: () -> R): R { 
    return block()
}

public inline fun <T, R> T.run(block: T.() -> R): R {
    return block()
}

public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    return receiver.block()
}

public inline fun <T> T.apply(block: T.() -> Unit): T {
    block()
    return this
}

public inline fun <T> T.also(block: (T) -> Unit): T {
    block(this)
    return this
}

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

Don’t worry, let’s comb it out:

function Parameter 1 Parameter 2 The return value
run / ()->R R
T.run / T.()->R R
with T T.()->R R
T.apply / T.()->Unit T
T.also / (T)->Unit T
T.let / (T)->R R

Still confused, let me ask you a few questions:

  • runvsT.runI missed oneTWhat’s the difference?

The difference is that run is a normal function and t. run is an extension function. This in run is the declared class object (except for top-level functions), and this in t.run is the receiver object;

  • T.()->Unitvs(T)->UnitOr,T.()->Rvs(T)->RWhat’s the difference in the position of T?

The differences are: T in T.()->Unit is the receiver type, and T in (T)->Unit is the function argument;

  • whywithwiththis.letwithit?
    • The block argument in the run, with, and apply functions is “an extension of T”, so this is the receiver object for the extension function. Also, since block has no parameters, there is no definition of IT.
    • Also and let arguments block are “functions that take T”, so it is the only argument. Also, since block is not an extension function, there is no definition of this.

Lambda expressions

Lambda expressions are essentially “blocks of code that can be passed as values.” In older versions of Java, passing code blocks required anonymous inner class implementations, whereas using lambda expressions didn’t even require function declarations; you could simply pass code blocks as function values.

When a lambda expression has only one argument, the IT keyword can be used to refer to the unique argument.


4. Extended application scenarios

In this section, we’ll look at some application scenarios for using extensions in Android development.

4.1 Encapsulating tool Utils

In Java, we are used to encapsulating common code into utility classes. Traditional Java utility methods are not straightforward or ideographic enough to allow callers to ignore the strong connection between String and firstChar(). In addition, the caller wants to omit the StringUtils class name to make firstChar() look more like a property and method inside the String. None of these requirements are a problem for the Kotlin extension.

4.2 Solve the annoying findViewById

In Android, findViewById() is often called to find a particular View instance in the View tree, for example:

Old SDK: loginButton = (Button) findViewById(R.i.D.btn_login); New SDK: loginButton = findViewById(R.I.D.btn_login);Copy the code

Tip: In the new SDK, findViewById() is a generic method, so you no longer need to cast it.

public <T extends View> T findViewById(@IdRes int id) {
   return getWindow().findViewById(id);
}
Copy the code

Usually, we’ll define an instance variable or local variable to hold the return value of findViewById(), and most of the time, these variables are just “temporary variables” that won’t be of much use after the event binding/assignment. If you have more complex interfaces, you may even need to define dozens of lines of temporary variables!

Can we skip these temporary variables and just work with R.ID.*? The answer is yes, we can use Kotlin’s “higher order function + extension function”. Such as:

fun Int.onClick(click: () -> Unit) {
    findViewById<View>(this).apply {
        setOnClickListener {
            click()
        }
    }
}
Copy the code

In this case, we can bind the click event directly using R.ID.* (r.ID * is essentially an integer) :

R.id.btn_login.onClick {
    // do something
}
Copy the code

It’s a lot cleaner, so we don’t have to define a bunch of temporary variables. However, every time you need to write the prefix R.ID, this also seems redundant, can you omit it? Yes, we need to use Kotlin’s Gradle plugin for Android: Kotlin-Android-Extensions.

apply plugin : 'kotlin-android-extension'
Copy the code

At this point, we can directly manipulate the View instance with the component ID, such as MainActive.java

btn_login.setOnClickListener{
    // do something
}
Copy the code

Try decommounting this code and you can see that the Kotlin-Android-Extensions plug-in automatically inserts the following code into the Activity class:

MainActivity.class

public class MainActivity extends AppCompatActivity { private HashMap _$_findViewCache; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); ((Button) this._$_findViewCache(id.btn_login)).setOnClickListener((View.OnClickListener) null.INSTANCE); } public View _$_findCachedViewById(int var1) { if (this._$_findViewCache == null) { this._$_findViewCache = new HashMap(); } View var2 = (View) this._$_findViewCache.get(Integer.valueOf(var1)); if (var2 == null) { var2 = findViewById(var1); this._$_findViewCache.put(Integer.valueOf(var1), var2); } return var2; } public void _$_clearFindViewByIdCache() { if (this._$_findViewCache ! = null) { this._$_findViewCache.clear(); }}}Copy the code

As you can see, when accessing the R.ID.* control, we first look in the cache collection _$_findViewCache, and if there is no, we look through findViewById() and add it to the cache collection.

An _$_clearFindViewByIdCache() method is also provided to clear the complete cache when the interface view is completely replaced. In Fragment#onDestroyView(), this method is called to clear the cache, but not in the Activity.

4.3 Simple Solution of LeetCode problems

When solving algorithm problems, using extension functions can make the code more concise and more ideographic. For example, we need to swap elements at two locations in an array. Compared to the traditional way of writing the extension function, you can see that the meaning is much clearer.

fun swap(arr: IntArray, from: Int, to: Int) { ... } swap(arr,0,1) fun intarray. swap(from: Int, toInt) {... } arr. Swap (0, 1)Copy the code

5. To summarize

  • Extensions can add new functions or properties to a class without modifying the class/inheriting the class, which is more consistent with the open closed principle. Compared with the traditional Java method of the tool call more simple and direct, more ideographic;

  • An extension function is a static function defined outside of the class. The first argument to the function is the receiver type. An extension is called without the creation of an adaptation object or any additional runtime cost. In Java, we just call extensions like normal static methods;

  • The block argument in the run, with, and apply functions is “an extension of T”, so this is the receiver of the extension function. Also and let arguments block are “functions that take T”, so it is the only argument.


The resources

  • Core Programming for Kotlin (Chapter 7) by the Droplet Technology team
  • Extensions Feature — Kotlin official documentation
  • This Keyword — Kotlin’s official document
  • Android KTX — Official Documentation for Android Developers
  • The Kotlin Field (Section 3.3) — By Dmityr Jemerov and Svetlana Lsakova

Creation is not easy, your “three company” is the biggest power of Ugly, we see next time!