Three articles to get you started Kotlin (Middle)
Standard functions and static methods
Standard functions
Kolin’s Standard functions refer to those defined in the standard.kt file, and any Kotlin code is free to call all of the Standard functions.
There are not many standard functions, but it is difficult to learn them all in one row. Because we’re going to focus on a couple of common functions here.
with
The with function takes two arguments: the first argument can be an object of any type, and the second argument is a Lambda expression. The with function provides the context of the first argument object in the Lambda expression and returns the last line of code in the Lambda expression as the return value.
Example code is as follows:
val result = with(obj) {
// Here is the context of obj
"value" // The return value of the with function
}
Copy the code
fun with(a) {
val list = listOf("Apple"."Banana"."Orange"."Pear"."Grape"."Watermelon")
val result = with(StringBuilder()) {
append("with\nstart\n")
for (fruit in list) {
append(fruit + "\n")
}
append("end")
toString()
}
println(result)
}
Copy the code
run
The use and scenario of the run function is very similar to that of the with function, with a few syntax changes. First of all, the run function cannot be called directly, but must call the run function of an object. Second, the run function takes only a Lambda argument and provides the context of the calling object in the Lambda expression. Everything else is the same as with, including the fact that the last line of the Lambda expression is also returned as the return value.
Example code is as follows:
val result = obj.run {
// Here is the context of obj
"value" // Return value of the run function
}
Copy the code
fun run(a) {
val list = listOf("Apple"."Banana"."Orange"."Pear"."Grape"."Watermelon")
val result = StringBuilder().run {
append("run\nstart\n")
for (fruit in list) {
append(fruit + "\n")
}
append("end")
toString()
}
println(result)
}
Copy the code
apply
The apply and run functions are also very similar in that they are called on an object and take only a Lambda argument. They also provide the context of the calling object in the Lambda expression, but apply cannot specify a return value. Instead, it automatically returns the calling object itself.
Example code is as follows:
val result = obj.apply {
// Here is the context of obj
}
// result == obj
Copy the code
fun apply(a) {
val list = listOf("Apple"."Banana"."Orange"."Pear"."Grape"."Watermelon")
val result = StringBuilder().apply {
append("apply\nstart\n")
for (fruit in list) {
append(fruit + "\n")
}
append("end")
}
println(result.toString())
}
Copy the code
Defining static methods
Static methods are methods that can be invoked without creating an instance, and are supported by all major programming languages.
Generic utility classes are generally not necessary to create instances and are basically universal.
Kotlin does not directly define static method keywords, but does provide syntactic features to support the writing of similar static method calls, such as singleton classes, Companion Objects, etc. These syntactic features should be sufficient for normal development needs.
However, if you really need to define truly static methods, Kotlin still offers two ways to do it: annotations and top-level methods.
@ JvmStatic annotations
class Util {
companion object {
@JvmStatic
fun doAction(a) {
println("do action2")}}}Copy the code
The top method
Methods that are not defined in any class,
As long as you define a top-level method, it must be static
fun doSomething(a) {
println("do something")}Copy the code
Call the Kotlin static method in Java
Call Kotlin’s top-level method in Java and you’ll find that when found, because Java has no concept of top-level methods, Java creates a Java class for StaticMethodKt and then calls the static method of doSomething().
public class JavaTest {
publicvoid test() { Util1.doAction2(); StaticMethodKt.doSomething(); }}Copy the code
Delayed initialization and sealing classes
Lazy initialization of variables
Lazy initialization uses the lateInit keyword, which tells the Kotlin compiler that I will initialize this variable later so I don’t have to assign it to null in the first place.
This modifier can only be used for attributes in the class body.
class Dog() {
lateinit var say: String
fun call(a) {
say = "Dog bark."}}Copy the code
Checks whether an LateInit var has been initialized
if (foo::bar.isInitialized) {
println(foo.bar)
}
Copy the code
Kotlin provides a lazy() function that takes a lambda expression as an argument and returns a lazy object
The following is an example:
class Dog {
val bark: String by lazy {
println("Execute code block on first access")
"Dog"}}Copy the code
Seal type
Sealed classes are used to represent a restricted class inheritance structure: when a value can have a finite number of types and no other types. In a sense, they are an extension of enumerated classes: the collection of values of enumerated types is also limited, but only one instance of each enumerated constant exists, whereas a subclass of a sealed class can have multiple instances of containable state.
To declare a sealed class, add the sealed modifier before the class name. Although a sealed class can have subclasses, all subclasses must be declared in the same file as the sealed class itself.
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
Copy the code
- A sealed class is itselfIn the abstract, it cannot be instantiated directly and can have abstractions (abstractMembers).
- Sealed classes do not allow non –privateConstructor (whose constructor defaults toprivate).
- Note that classes (indirect successors) of extended sealed class subclasses can be placed anywhere without being in the same file.
- The key benefit of using a sealed class is the use
when
expressionIf you can verify that the statement covers all cases, you do not need to add another statement to the statementelse
The clause. Of course, this is only if you usewhen
Useful as expressions (using results) rather than as statements.
fun eval(expr: Expr): Double = when(expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
// The 'else' clause is no longer needed because we have covered all cases
}
Copy the code
The sample
sealed class Apple {
abstract fun taste(a)
}
open class RedFuji : Apple() {
override fun taste(a) {
println("The red Fuji apple is sweet and delicious!")}}data class Gala(var weight: Double) : Apple() {
override fun taste(a) {
println("Galala fruit, weight is$weight")}}fun main(a) {
var ap1: Apple = RedFuji()
var ap2: Apple = Gala(2.3)
ap1.taste()
ap2.taste()
judge(ap1)
judge(ap2)
}
fun judge(ap: Apple) {
when (ap) {
is RedFuji -> println("The red Fuji apple is sweet and delicious!")
is Gala -> println("Gala fruit")}}Copy the code
Extend function and operator overloading
Extension function
Extension functions represent the ability to open a class and add new functions to it without modifying its source code.
The syntax structure is very simple, as follows:
fun ClassName.methodName(param1: Int, param2: Int): Int {
return 0
}
Copy the code
To think about counting the number of characters in a string, you typically write a singleton or static pattern
object StringUtil {
fun lettersCount(str: String): Int {
var count = 0
for (char in str) {
if (char.isLetter()) {
count++
}
}
return count
}
}
Copy the code
General extension functions are defined in top-level methods. This allows extension functions to have a global access domain, and extension functions can in many cases make apis cleaner, richer, and more object-oriented.
fun String.lettersCount(a): Int {
var count = 0
for (char in this) {
if (char.isLetter()) {
count++
}
}
return count
}
Copy the code
call
fun main(a) {
val str = "ABC123xyz! @ #"
val count = StringUtil.lettersCount(str)
println("count $count")
println("ex count ${str.lettersCount()}")}Copy the code
Overloaded operator
Kotlin’s operator overloading allows us to add any two objects together, and to do much more.
Using the plus operator as an example, if you want to add two objects, the syntax is as follows:
class Obj {
operator fun plus(obj: Obj): Obj {
// Handle the adding logic}}Copy the code
The comparison between syntactic sugar expressions and the actual called functions is as follows:
Syntactic sugar expression | Actually calling the function |
---|---|
a + b | a.plus(b) |
a – b | a.minus(b) |
a * b | a.times(b) |
a / b | a.div(b) |
a % b | a.rem(b) |
a++ | a.inc() |
a– | a.dec() |
+a | a.unaryPlus() |
-a | a.unaryMinus() |
! a | a.not() |
a == b | a.equals(b) |
a > ba < ba >= b****a <= b | a.compareTo(b) |
a.. b | a.rangeTo(b) |
a[b] | a.get(b) |
a[b] = c | a.set(b, c) |
a in b | b.contains(a) |
Example For example two Money object operator overloads use the second is the operator keyword
class Money(val value: Int) {
operator fun plus(money: Money): Money {
val sum = value + money.value
return Money(sum)
}
operator fun plus(newValue: Int): Money {
val sum = value + newValue
return Money(sum)
}
}
Copy the code
fun main(a) {
val money1 = Money(5)
val money2 = Money(10)
val money3 = money1 + money2
println(money3.value)
val money4 = money3 + 20
println(money4.value)
}
Copy the code
For other operators, overload the table by referring to the corresponding method
if ("hello".contains("he")) {
println("hello contains in ")}if ("he" in "hello") {
println("he in ")}Copy the code
Random string repetition, the idea is to repeat the string passed in N times, if we can write STR * N to indicate that the string is repeated N times
fun getRandomLengthString(str: String): String {
val n = (1.20.).random()
val builder = StringBuilder()
repeat(n) {
builder.append(str)
}
return builder.toString()
}
Copy the code
Kotlin already provides us with a repeat function for the string N times, which can be rewritten as follows
operator fun String.times(n: Int) = repeat(n)
Copy the code
Eventually we can abbreviate it as follows
fun getRandomLengthString1(str: String) = str * (1.20.).random()
Copy the code
Higher order functions in detail
Higher-order functions are closely related to Lambda.
Definition: A function is called a higher-order function if it takes another function as an argument, or if the type of the return value is another function.
fun example(func: (String.Int) - >Unit) {
func("hello".123)}Copy the code
Lambda expressions call higher-order functions
Kotlin also supports a variety of other ways to call higher-order functions, such as Lambda expressions, anonymous functions, and member references.
Lambda expressions are also the most common way of calling higher-order functions.
The sample
val result1 = num1AndNum2(num1, num2) { n1, n2 ->
n1 + n2
}
Copy the code
A higher-order function that mimics the functionality of the standard function Apply
Note that this function type parameter declaration is a bit different from the syntax we learned earlier:
It precedes the function type with a syntax of StringBuilder.
This is actually the syntax that defines the completion of higher-order functions, the ClassName in front of the function type. Indicates the class in which the function type is defined
The advantage of this definition is that the lambda expression passed in when we call the build function automatically has the StringBuilder context.
fun StringBuilder.build(block: StringBuilder. () - >Unit): StringBuilder {
block()
return this
}
Copy the code
The role of inline functions
Inline functions can completely eliminate the runtime overhead associated with using Lambda expressions. The way it works isn’t that complicated, except that the Kotlin compiler automatically replaces the code in the inline function at compile time to where it was called, so there’s no runtime overhead.
inline
Defining inline functions is as simple as adding a declaration with the inline keyword when defining higher-order functions, as shown below:
inline fun num1AndNum2(num1: Int, num2: Int, operation: (Int.Int) - >Int): Int {
val result = operation(num1, num2)
return result
}
Copy the code
noinlline
Noinlline partially disables inlining
After using an inline modifier, all Lambda expressions or functions passed to the function are inlined, or if you want parameters of one or more function types in a function not to be inlined, you can use the noinline modifier
inline fun inlineTest(block: () -> Unit.noinline blokc2: () -> Unit){}Copy the code
There is another important difference between inline and non-inline functions: the Lambda referenced by an inline function can be used to return a function using the return keyword, whereas non-inline functions can only return locally.
It is good programming practice to write higher-order functions as inline functions. In fact, most higher-order functions can be declared directly as inline functions, but there are a few exceptions.
crossinline
Some inline functions may call lambda expression arguments that are passed to them not directly from the function body, but from another execution context, such as local objects or nested functions. In this case, non-local control flow is also not allowed in the lambda expression. To identify this case, the lambda expression argument needs to be marked with the Crossinline modifier
inline fun f(crossinline body: () -> Unit) {
val f = object: Runnable {
override fun run(a) = body()
}
/ /...
}
Copy the code
Application of higher order functions
Let’s simplify the following extension
val editor = getSharedPreferences("data", Context.MODE_PRIVATE).edit()
editor.putString("name"."yx")
editor.putInt("age".30)
editor.apply()
Copy the code
We can use higher-order functions to simplify the use of SharedPreferences, as follows:
The following is an example:
fun SharedPreferences.open(block: SharedPreferences.Editor. () - >Unit) {
val editor = edit()
editor.block()
editor.apply()
}
Copy the code
Once you have defined the open function, it will be easier to store data in your project using SharedPreferences in the future, as follows:
getSharedPreferences("data", Context.MODE_PRIVATE).open {
putString("name"."yx")
putInt("age".30)}Copy the code
Generics and delegates
The generic
In normal programming mode, we need to specify a specific type for any variable. Generics allow us to write code without specifying a specific type, which makes the code much more extensible.
There are two main ways to define generics, one is to define generic classes, and the other is to define generic methods, using both syntactic structures. Of course, T in parentheses is not required, in fact you can use any letter or word, but in general, T is a generic way of writing.
If you wanted to define a generic class, you could write:
class MyClass<T> {
fun method(param: T): T {
return param
}
}
Copy the code
When calling MyClass and method(), generics can be specified as concrete types, as follows:
val myClass = MyClass<Int> ()val result = myClass.method(123)
Copy the code
If you don’t want to define a generic class, but just want to define a generic method, just write the syntax structure for defining generics above the method, as follows:
class MyClass {
fun <T> method(param: T): T {
return param
}
}
Copy the code
The call method also needs to be adjusted accordingly:
val myClass = MyClass()
val result = myClass.method<Int> (123)
Copy the code
As you can see, it is now time to specify a generic type when calling the method() method. In addition, Kotlin has a very good type inference mechanism. For example, if you pass in an Int parameter, it can automatically infer that the type of the generic is Int, so it can also omit the generic specification:
val myClass = MyClass()
val result = myClass.method(123)
Copy the code
Commissioned by class
The core idea of class delegation is to delegate the implementation of one class to another class. However, delegation also has some disadvantages. If there are few methods to implement in the interface, if there are dozens or hundreds of methods, each of which calls the corresponding method implementation in the auxiliary object, it can be very complicated to write. This problem can be solved in Kotlin with the class delegate functionality.
class MySet<T>(val helperSet: HashSet<T>) : Set<T> {
override val size: Int = helperSet.size
override fun contains(element: T): Boolean = helperSet.contains(element)
override fun containsAll(elements: Collection<T>): Boolean = helperSet.containsAll(elements)
override fun isEmpty(a): Boolean = helperSet.isEmpty()
override fun iterator(a): Iterator<T> = helperSet.iterator()
}
Copy the code
class MySet<T>(val helperSet: HashSet<T>) : Set<T> by helperSet {
fun helloWorld(a) = println("Hello World")
override fun isEmpty(a) = false
}
Copy the code
MySet is now a new data structure class that not only is never empty, but also prints helloWorld(), and the rest of the Set interface functions are consistent with HashSet. This is what Kotlin’s class delegate can do.
Attribute to entrust
The core idea of delegating attributes is to delegate the implementation of an attribute (field) to another class.
Let’s look at the syntax structure of the delegate attribute, as follows:
class MyClass {
var p by Delegate()
}
Copy the code
Here we use the by keyword to connect the p property on the left to the Delegate instance on the right, which represents delegating the implementation of the P property to the Delegate class. The Delegate class’s getValue() method is automatically called when the p property is called, and the Delegate class’s setValue() method is automatically called when assigning a value to the P property.
class MyClassProp {
var p by Delegate()
}
class Delegate {
var propValue: Any? = null
operator fun getValue(myClassProp: MyClassProp, property: KProperty< * >): Any? {
return propValue
}
operator fun setValue(myClassProp: MyClassProp, property: KProperty<*>, any: Any?). {
propValue = any
}
}
Copy the code
Application of delegation
Implement a Lazy function of your own
val p by lazy{ ... }
Copy the code
- Lazy is a higher-order function
- The lazy function creates and returns a Delegate object
- When we call the p property, we’re actually calling the Delegate object’s getValue() method
- The getValue method in turn calls the Lambda expression passed in by the lazy function
- The p attribute results in the last line of code in the Lambda expression
class Later<T>(val block: () -> T) {
var value: Any? = null
operator fun getValue(any: Any? , property:KProperty< * >): T {
if (value == null) {
value = block()
}
return value as T
}
}
fun <T> later(block: () -> T) = Later(block)
val p by later {
println("First load")
"123"
}
fun main(a) {
println("p$p")
println("p$p")}Copy the code