preface
With ajava
Old bird’s point of view, how to seekotlin
. How Java source code should be refactored with Kotlin. How to learn Kotlin correctly and apply it to actual development. This article will explore.
This paper is divided into two parts, key and difficult points and unspoken rules.
Key and difficult points: Blocks of knowledge points that can be explained independently in Kotlin. Provide a separate Demo. Most of this is a new concept (compared to Java) pioneered by Kotlin.
The unwritten rule: Kotlin is Google’s replacement for Java, and it’s 100 percent compatible with Java, but in fact, when Java became Kotlin, we had to manually change a lot of things, and even some parts had to be broken and refactored to achieve optimal coding. Some of kotlin’s features are different or even reversed from Java. This part of the knowledge is more fragmentary, separate Demo is not convenient to provide, in the form of small examples to write.
The body of the outline
-
Difficult point
- Lambda and operators
- Higher-order functions and operators
Kotlin
The generic- Set operations
- coroutines
- Operator overloading
-
Hidden rules
-
There is no one-to-one relationship between Kotlin files and classes
-
symbionts
-
inheritance
-
The modifier
-
Null pointer problem
-
The body of the
Difficult point
Lambda expressions
Lambda expressions were introduced in JDK1.8 to improve the verbose writing of one-method callback functions that is prevalent in Java. I’m not talking about lambdas in Java. In Kotlin, lambdas are built into the core of the code, not annotations + syntactic sugar like in Java.
Basis:
A lambda expression is shown below:
The four parts of a lambda expression
- Enclosing {} braces
- Parameter list: x:Int,y:Int
- Connector – >
- Methods body x + y
Take a chestnut
Here is a Kotlin file:
/** * For example, IF I want to pass an execution to the doFirst method, the type is a ramda expression that outputs an Int and inputs 2 ints
fun calculate(p1: Int, p2: Int, event1: (Int.Int) - >Int, event2: (Int.Int) - >Int) {
println("Executive doFirst")
println("Executive event1${event1(p1, p2)}")
println("Executive event2${event2(p1, p2)}")}// Test lambda expressions
fun main(a) {
val sum = { x: Int, y: Int ->
print("Peace")
x + y
}
val diff = { x: Int, y: Int ->
print("Poor")
x - y
}
// In kotlin, we can pass the ramda representation as a normal variable
calculate(p1 = 1, p2 = 2, event1 = sum, event2 = diff)
}
Copy the code
Defines a calculate function, where p1, p2 are Int, and event1 and event2 are lambda expressions. High-level language features, first-class function citizens: functions themselves can be passed as ordinary arguments and called. So what does kotlin’s lambda kernel look like?
As you can see from the figure above,
- The last two parameters of the Calculate method are compiled to Function2 type.
- To execute event1, event2 invokes its invoke method
- INSTANCE is used to get an INSTANCE, but why null.INSTANCE
The Function2 source code blinds my titanium dog eyes:
In function. kt file:
Function interface, Function2 interface….. Function22 interface. Well, don’t question Google’s design ideas, they’re right anyway… There are 22 interfaces (as for why, I guess Google doesn’t think any crazy developer is going to cram more than 22 arguments together) to represent the list of possible lambda expression arguments that Kotlin is developing.
Here’s another chestnut
To set the click event for a Button, Kotlin writes:
val btn = Button(this)
val lis = View.OnClickListener { println("111") }
btn.setOnClickListener(lis)
Copy the code
Or:
val btn = Button(this)
btn.setOnClickListener { println("111")}Copy the code
The argument to the setOnClickListener method is the OnClickListener interface:
public interface OnClickListener {
void onClick(View v);
}
Copy the code
Interfaces like this lambda expression can be written in both ways to greatly reduce the amount of code.
One last chestnut
It is worth mentioning that lambda expressions have one variation: they can be placed outside the parentheses when the lambda expression is the last argument to a method. If only one argument is a lambda expression, the parentheses can be omitted
This is very important, not knowing this, can be very painful in many places.
fun testLambda(s: String, block: () -> String) {
println(s)
block()
}
fun testLambda2(block: () -> String) {
block()
}
fun main(a) {
testLambda("First parameter") {
println("Block function body")
"Return value"
}
testLambda2 {
println("Block function body")
"Return value"}}Copy the code
conclusion
Lambda expressions in Kotlin can be passed, assigned, and used as normal arguments.
Higher-order functions and operators
As mentioned above, lambda expressions in Kotlin have been integrated into the language core, which is embodied in higher-order functions that use lambda expressions as arguments. Functions that take lambda expressions as arguments or return values are called higher-order functions.
Official higher-order functions
Kotlin Google has already wrapped some higher-order functions for us.
- run
- with
- apply
- also
- let
- Takeif and takeunless
- repeat
- lazy
The run function is explained
The code looks like this (ignore androidStudio’s code optimizations for the sake of showing the full picture) :
class A {
val a = 1
val b = "b"
}
fun testRun(a) {
The run method can be used in two ways. One is independent of objects, i.e. as a global function
run<String> {// I can specify the type of return value
println("I'm a global function.")
"Return value"
}
val a = A()
// The other is dependent objects
a.run<A,String> {// It is also possible to specify the type of return value
println(this.a)
println(this.b)
"Return value"}}fun main(a) {
testRun()
}
Copy the code
As shown above:
Run functions fall into two categories
-
Global functions that do not depend on objects.
-
“Similar” extension functions that depend on objects.
Both can specify return value types (which should be easy to understand if you’re familiar with generics, as described in the next section).
Read the source code:
@kotlin.internal.InlineOnly
public inline fun <R> run(block: () -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T. () - >R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
Copy the code
The run function is overloaded with different arguments
- The former argument is of type ()->R, returns R, executes block() inside the function, and returns the result
- T.()->R, return R, T.() is dependent on T, return R, return result.
- Also, you can see that both are inline functions (executing code without pushing the method out of the stack, similar to executing the code segment directly at the target)
So, the former does not need to depend on the object, and the latter must depend on the object (because it is an “extension function” of class T).
Usage scenarios
By its nature, the run method encapsulates a piece of code, whether it depends on an object or not, and is inline. So:
-
If you don’t want to separate pieces of code into methods, and you don’t want them to look messy, it’s important to tell subsequent developers that the code is a whole, and that you don’t want to insert code into it, or break it up. Use the run method to combine them into a scope.
run { println("This is a piece of code with very similar logic.") println("But I don't want to isolate it as a method.") println("And you're worried someone's gonna change it.") println("So use the run method to put them in the same scope.") println("The rest of the team sees this and knows not to insert irrelevant code.")}Copy the code
-
Even better, the run function returns a value that can be used:
fun testRun2(param1: String, param2: String, param3: String) { // I want none of these parameters to be null. If the check is null, the method body is not executed val checkRes: Boolean = run<Boolean> { when { param1.isNullOrEmpty() -> { false } param2.isNullOrEmpty() -> { false } param3.isNullOrEmpty() -> { false } else -> true}}if (checkRes){ println("Parameters checked, now you can proceed.")}else{ println("Parameter check failed, body code not executed")}}fun main(a) { testRun2("1"."2"."")}Copy the code
Main results:
With the parameters checked, you can now proceed
Conclusion small
The run method is useful for integrating small pieces of code
Other higher-order functions
The higher order functions listed above are similar in principle, but the use of different scenarios, so in addition to run, the other is not explained in detail, but only illustrate the use of scenarios.
apply
There is only one difference between run, which returns the value after block() is executed, and apply, which returns this, so apply must depend on objects. Because this is returned, you can apply calls continuously.
fun testApply(a) {
val a = A()
a.apply {
println("If an object is going through multiple stages of processing on it")
}.apply {
println("Then it's easy to get confused when you have multiple stages all jammed together,")
}.apply {
println("At this point, you can use Apply to separate each stage.")
}.apply {
println("Make your code more elegant.")}}fun main(a) {
testApply()
}
Copy the code
with
Glide image loading framework, with(context) and then chain call
Glide.with(this).load(image).asGif().into(mImageView);
Copy the code
Kotlin’s “with” seems to take this idea a step further (just by analogy, no further), as follows:
class A {
val a = 1
val b = "b"
fun showA(a) {
println("$a")}fun showB(a) {
println("$a $b")}}fun testWith(a) {
val a = A()
with(a) {
println("Scope can refer directly to the created A object.")
this.a
this.b
this.showA()
this
}.showB()
}
fun main(a) {
testWith()
}
Copy the code
details
- In the scope with(a){}, you can use a reference to the current a object, either this. XXX or a.xx
- With (a){} The last line in the braces scope is the return value, and if I return this, then after with I can continue calling a’s method
also
Also, like with, must depend on objects and return the value this. So it also supports chained calls, which differ from Apply:
The apply block takes no arguments, but also takes this as an argument. The difference is this:
The current object is called differently in scope {}.
class A {
val a = 1
val b = "b"
fun showA(a) {
println("$a")}fun showB(a) {
println("$a $b")}}fun testApply(a) {
A().apply {
this.showA()
println("= = = = = = =")
}.showB()
}
fun testAlso(a) {
A().also {
it.showA()
println("= = = = = = =")
}.showB()
}
Copy the code
XXX must be used for apply and it. XXX must be used for also.
let
Analogies to run:
public inline fun <T, R> T.run(block: T. () - >R): R {
return block()
}
public inline fun <T, R> T.let(block: (T) - >R): R {
return block(this)}Copy the code
There is only one difference: Block execution of run requires no arguments, whereas block execution of let requires this.
The differences are as follows:
A().run {
println("Return value of last action")
this
}.showA()
A().let {
println("Return value of last action")
it
}.showA()
Copy the code
The run{} scope can only operate on the current object with this. XXX, let with it.xxx
Takeif and takeunless
The two work in reverse, and they must depend on objects. See the source code:
public inline fun <T> T.takeIf(predicate: (T) - >Boolean): T? {
return if (predicate(this)) this else null
}
public inline fun <T> T.takeUnless(predicate: (T) - >Boolean): T? {
return if(! predicate(this)) this else null
}
Copy the code
Predicate is a lambda expression of type (T)->Boolean, representing an assertion judgment that returns itself if true, or null otherwise
class A {
val a = 0
val b = "b"
fun showA(a) {
println("$a")}fun showB(a) {
println("$a $b")}}fun testTakeIfAndTakeUnless(a) {
println("test takeIf")
A().takeIf { it.a > 0}? .showB() println("= = = = = = = = = =")
println("test takeUnless")
A().takeUnless { it.a > 0}? .showB() }fun main(a) {
testTakeIfAndTakeUnless()
}
Copy the code
Execution Result:
test takeIf
==========
test takeUnless
0 b
Copy the code
TakeIf/takeUnless applies to wrapping the conditional code in a scope {} and then using? XXXX security callers to perform object operations.
repeat
Repeat is a dumb-ass version of repeating.
fun testRepeat(a) {
repeat(10) {
print("$it ")}}fun main(a) {
testRepeat()
}
Copy the code
Execution Result:
0 1 2 3 4 5 6 7 8 9
Copy the code
lazy
Lazy is used to delay initialization of constants defined by val.
class B {
val i: Int by lazy {
println("Perform I initialization")
20
}
init {
println("Constructor execution")}}Copy the code
If only the B object is initialized and the variable I is not used, the lazy initialization is not performed.
fun main(a) {
B()
}
Copy the code
Execution Result:
Constructor executionCopy the code
If the variable I is used, it will be initialized before the call:
fun main(a) {
println("The value of the I variable is: + B().i)
}
Copy the code
Execution Result:
Constructor execution executes I initializes the value of the I variable:20
Copy the code
conclusion
Kotlin’s higher-order functions, run, apply, also, let, with, etc., are designed to use the **{} scope ** to seal tightly connected code within a scope, making a method look orderly inside, easier to read, more maintainable, and more elegant code. Above, all functions except lazy are inside the standart.kt file.