The articles
Kotlin Jetpack: The Beginning
00. The Kotlin Pit Guide for Java Developers
03. Kotlin programming’s Triple Realm
04. Kotlin higher-order Functions
05. Kotlin Generics
preface
Extension, arguably the most interesting feature in Kotlin, is none.
This article will systematically explain the Kotlin extension function and extension properties as well as the more difficult extension scope and extension static resolution, and finally with a practical link, the extension function and the previous higher-order functions together.
preparation
- Update the Android Studio version to the latest
- Clone our Demo project locally and open it with Android Studio:
Github.com/chaxiu/Kotl…
- Switch to branch:
chapter_06_extension
- I strongly suggest that you follow this article to practice, the actual combat is the essence of this article
The body of the
1. What is extension?
Kotlin’s extension works like this: it can add new functionality to a class. The new functionality can be functions or properties.
With the Kotlin extension, we can easily write code like this:
// Extend the function "KotlinJetpackInAction".log() // extend the property val isBlank = string.isnullorBlankCopy the code
The above code looks like we’ve modified the String and added methods and attributes: log(), isNullOrBlank.
When I first saw the extension feature, I was really blown away. While extensions aren’t unique to Kotlin (other modern languages have them), it’s really remarkable that Kotlin can introduce them while still being Java compatible.
2. Top Level Extension
Top level extension is the most common type of extension, and its definition is very simple. Take the above two lines of code as an example to see how they should be defined.
// BaseExt.kt
package com.boycoder.kotlinjetpackinaction
// Attention!!
// A top-level extension cannot be defined in any Class, otherwise it becomes an "in-class extension"!
// Define extension functions for String
fun String.log(a) {
println(this)}// Define extended attributes for String
valString? .isNullOrBlank:Boolean
get() = this= =null || this.isBlank()
Copy the code
3. What is the principle of top-level extension?
To understand how top-level extensions work, look directly at the Java equivalent of the bytecode. The previous article explained how to decompile Kotlin code into Java, and look directly at the result:
public static final void log(String $this$log) {
System.out.println($this$log);
}
public static final boolean isNullOrBlank(String $this$isNullOrBlank) {
return $this$isNullOrBlank == null || StringsKt.isBlank((CharSequence)$this$isNullOrBlank);
}
Copy the code
The essence of top-level extensions is Java static methods, the same principle that we often write Utils classes in Java. Kotlin’s top-level extension feels amazing to use, but the principle is surprisingly simple. This is all because the Kotlin compiler does a layer of encapsulation and transformation for us.
Some people might scoff and say “syntactic sugar”, but what I see is Kotlin’s desire for simplicity and productivity.
4. Declare extensions as members
Top-level extensions at the Package level are simple to understand, while in-class extensions are slightly more complex.
This translation, though closer to nature, is too rigid, so I use inline extensions as members to declare it literal.
An in-class extension is written exactly the same as a top-level extension, except that it is inside another class. Let’s look at an example:
// The extended class
class Host(val hostname: String) {
fun printHostname(a) { print(hostname) }
}
class Test(val host: Host, val port: Int) {
fun printPort(a) { print(port) }
// Add an extension function to Host in the Test class
/ / left
fun Host.printConnectionString(a) {
printHostname() // Host.printHostname()
print(":")
printPort() // Test.printPort()
}
// Add an extended attribute to Host in the Test class
/ / left
val Host.isHomeEmpty: Boolean
get() = hostname.isEmpty()
fun test(a) {
host.printConnectionString()
}
}
fun main(a) {
// The Host extension is not accessible outside the class, unlike the top-level extension
Host("").isHomeEmpty
Host("").printConnectionString()
}
Copy the code
5. Extended summary:
The top extension
It cannot be defined within a class; it is scoped at Package level and can be used to guide packagesWithin the class extensions
It is defined in other classes, and its functions and limitations are limited to that classWithin the class extensions
The advantage is that it can access both the extended class (Host) and its own class (Test).extension
There is no actual modification of the extended class, so we can still access only the ones in the classpublic
Methods and Properties
6. What is the principle of in-class extension?
Let’s go straight to decompiled Java:
// The Host class does not add any attributes or methods
/ / left
public final class Host {...public final void printHostname(a) {
String var1 = this.hostname; System.out.print(var1); }}public final class Test {
public final void printPort(a) {
System.out.print(var1);
}
// The extension function to Host becomes a member function of Test, and Host becomes an argument
/ / left left
public final void printConnectionString(Host $this$printConnectionString) {$this$printConnectionString.printHostname();
String var2 = ":";
System.out.print(var2);
this.printPort();
}
// The extended property of Host becomes a member function of Test, and Host becomes a parameter
/ / left left
public final boolean isHomeEmpty(Host $this$isHomeEmpty) {
CharSequence var2 = (CharSequence)$this$isHomeEmpty.getHostname();
return var2.length() == 0; }}Copy the code
Returning to the English language of in-class extension :(variable extensions as members), this is very close to its essence. You should understand the difference between these two names: in-class extensions describe representations; Extension declarations describe principles for members.
Also, in this case, Test is called the Dispatch Receiver and Host is called the Extension Receiver. This…… Have you ever heard of a name like that? Right! This echoes the previous section: higher-order functions with receiver function types.
7. What is the type of the extension function?
In the previous chapter on function types with receivers, I said something like this:
Function types with receivers are, on the surface, equivalent to member functions (and extension functions). But essentially, it’s still done by the compiler injecting this.
A table to summarize:
So function types with receivers and extension functions are syntactically designed the same way.
Testext. kt: Testext. kt: testext. kt: testext. kt: testext. kt: testext. kt: testext. kt
fun testFunctionType(a) {
var lambda: A.(B, C) -> D? = A::test
lambda = A::testExt
lambda = ::testReceiver
var lambdaX: (A, B, C) -> D? = lambda
}
Copy the code
8. Extensions are static
The extension is static.
The subtext of this statement is: extensions do not support polymorphism. It’s easy to understand this code example:
open class Shape
class Rectangle: Shape(a)fun Shape.getName(a) = "Shape"
fun Rectangle.getName(a) = "Rectangle"
fun printClassName(s: Shape) {
println(s.getName())
}
printClassName(Rectangle())
// Output: Shape
Copy the code
This feature is counter-intuitive, but easy to understand, so keep an eye out for it in the future.
See this for more details on the code aboveGitHub Commit.
9. Override is extended within the class, and the extension function conflicts
This part is relatively difficult to understand the extension function part, the text is not easy to explain, only the actual running code through decompilation can be clear, please go to the Demo project to find testextasmember. kt run code, and decompilation to think about. I’ve explained that in the comments. The code case is also directly in the official documentation, this example is cleverly designed.
The code for testextasmember.kt is as follows:
open class Base {}class Derived : Base() {}open class BaseCaller {
open fun Base.printFunctionInfo(a) {
println("Base extension function in BaseCaller")}open fun Derived.printFunctionInfo(a) {
println("Derived extension function in BaseCaller")}val Derived.test: Int
get() = 1
fun call(b: Base) {
b.printFunctionInfo() // Call the extension function}}class DerivedCaller: BaseCaller() {
override fun Base.printFunctionInfo(a) {
println("Base extension function in DerivedCaller")}override fun Derived.printFunctionInfo(a) {
println("Derived extension function in DerivedCaller")}}/** * Steps: First run the code, then debug the code, finally decompile the code. * * The key to understanding this example is that * * BaseCaller().call(), DerivedCaller().call() is polymorphic. * * The base.printfunctioninfo () in the call function is static. * * this paragraph must be combined with the decompiled code to see * */
fun main(a) {
BaseCaller().call(Base())
BaseCaller().call(Derived())
DerivedCaller().call(Base())
DerivedCaller().call(Derived())
}
Copy the code
See this for more details on the code aboveGitHub Commit.
6. The actual combat
After learning so many theories, it’s time for our actual practice.
7. Extension function + SharedPreferences
Remember how troublesome Java SharedPreferences were? Do we write a lot of this template code?
SharedPreferences sharedPreferences= getSharedPreferences("data",Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString(SP_KEY_RESPONSE, response);
editor.commit();
editor.apply();
Copy the code
In the Java era we could wrap things like PreferencesUtils to avoid template code. Kotlin’s extension functions make our code look much cleaner. Next, we add an extension function for SharedPreferences:
fun SharedPreferences.edit(
commit: Boolean = false,
action: SharedPreferences.Editor. () - >Unit
) {
val editor = edit()
action(editor)
if (commit) {
editor.commit()
} else {
editor.apply()
}
}
Copy the code
This extension function is very simple, let’s see how to use it.
// MainActivity.kt
private val preference: SharedPreferences by lazy(LazyThreadSafetyMode.NONE) {
getSharedPreferences(SP_NAME, MODE_PRIVATE)
}
private fun display(response: String?).{... preference.edit { putString(SP_KEY_RESPONSE, response) } }Copy the code
Is it much cleaner? We finally have a place to cache API requests. 😂
Note: In addition, we can wrap SharedPreferences more thoroughly with other Kotlin features, which we will cover in the next article.
8. Extension function + Spannable
Writing a complex SpannableString in Java is a pain in the neck, and I’m going to look up some old code that will jog your painful memory:
SpannableString spannableString = new SpannableString("Set various font styles: Leaves should be leaves");
TextView tv_styleSpan = (TextView) findViewById(R.id.tv_styleSpan);
StyleSpan bold = new StyleSpan(Typeface.BOLD);
StyleSpan italic = new StyleSpan(Typeface.ITALIC);
StyleSpan boldItalic = new StyleSpan(Typeface.BOLD_ITALIC);
spannableString.setSpan(bold, 12.13, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
spannableString.setSpan(italic, 13.14, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
spannableString.setSpan(boldItalic, 14.16, Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
tv_styleSpan.setText(spannableString);
Copy the code
Let’s see what we can do with Kotlin’s extension function:
Here’s what we’re going to do. Although it looks like four lines of text, it’s displayed in a TextView:
Such an effect would take a lot of effort to implement in Java, but with the help of the Kotlin extension function, it’s a breeze to write:
// MainActivity.ktusername.text = ktxSpan { name!! .bold().italic().size(1.3 F).background(Color.YELLOW)
.append("\n")
.append("\n")
.append("Google".strike().italic().size(0.8 F).color(Color.GRAY))
.append("\n") .append(company!! .color(Color.BLUE).underline()) .append("\n")
.append("\n") .append(url(blog!! , blog)) }Copy the code
How is the corresponding Kotlin extension function implemented? It’s not that hard, it’s only 20 lines:
This is the entry function, which accepts an initial value and a Lambda expression. The notes are very detailed, so I won’t go into details:
CharSequence.() -> SpannableString is equivalent to (CharSequence) -> SpannableString
fun ktxSpan(s: CharSequence = SpannableString(""), func: CharSequence.() -> SpannableString) = func(s)
Copy the code
This is the core code for the entire ktxSpan:
/** * setSpan(o, 0, length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) */
private fun span(s: CharSequence, o: Any): SpannableString = when (s) {
is SpannableString -> s
else -> SpannableString(s)
}.apply { setSpan(o, 0, length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) }
Copy the code
There are extension functions that encapsulate the various Span apis:
/** * implements convenient Api */ with extension functions
fun CharSequence.bold(s: CharSequence = this) = span(s, StyleSpan(android.graphics.Typeface.BOLD))
fun CharSequence.italic(s: CharSequence = this) = span(s, StyleSpan(android.graphics.Typeface.ITALIC))
fun CharSequence.underline(s: CharSequence = this) = span(s, UnderlineSpan())
fun CharSequence.strike(s: CharSequence = this) = span(s, StrikethroughSpan())
/* part of code omitted */
Copy the code
You can go to download the Demo debugging run:Github.com/chaxiu/Kotl…Welcome to Star Fork.
Question 1:
This ktxSpan has some room for optimization. Do you know how to optimize it?
Question 2:
Can the HTML DSL we wrote in the higher-order function above also be optimized with extensions?
There is 3:
What problems with Java do the Kotlin top-level extensions solve?
There is 4:
What are the actual usage scenarios for Kotlin in-class extensions?
9. The ending
- Kotlin
The top extension
Addressing various Java Utils issues, it not only improves code readability, but also improves usability - Readability:
response.isNullOrBlank()
比TextUtils.isEmpty(response)
Better readability - Ease of use: When typing in the IDE:
response.
The IDE will prompt usresponse.isNullOrBlank()
TextUtils does not automatically prompt.
All see this, give it a thumbs up!
【Kotlin Jetpack 】