This is the 28th day of my participation in the August Challenge. For the Nuggets’ August challenge,
This article introduces the basics of the Kotlin language
About the Kotlin
Kotlin is a statically typed programming language running on the Java virtual machine, known as the Swift of the Android world, designed and open sourced by JetBrains.
Kotlin can be compiled to Java bytecode or JavaScript, making it easy to run on devices without a JVM.
In Google I/O 2017, Google announced Kotlin as the official Android development language.
website
assigned
Why can Kotlin write an Android app?
Because code written in the Java language eventually needs to be compiled into a.class file to execute, code written in Kotlin will eventually be compiled into a.class file. As far as the Android operating system is concerned, it doesn’t have to care what language the program is developed in, as long as it ends up executing legitimate. Class files.
Why are we using Kotlin for Android? 1. Meaningless semicolons at the end of every line of code. 2, The switch statement only supports the int condition (java1.8 also supports String), and the case must end with a break. Not a full-object language, but a semi-object-oriented language. 4, does not support string embedded expression, concatenation string complex.
My first Kotlin program
Kotlin program files end with.kt, such as hello.kt, app.kt.
The following test demos are available
The Kotlin online tool writes tests
We’ll start with HelloWord
package hello // Optional head
fun main(args: Array<String>) { // package-level visible function that takes an array of strings as an argument
println("Hello World!") // The semicolon can be omitted
}
Copy the code
object-oriented
class Greeter(val name: String) {
fun greet(a) {
println("Hello, $name")}}fun main(args: Array<String>) {
Greeter("World!").greet() // Create an object without the new keyword
}
Copy the code
Why Kotlin?
- Brevity: Greatly reduces the amount of boilerplate code.
- Safety: Avoid whole class errors such as null pointer exceptions.
- Interoperability: Leverage existing libraries of the JVM, Android, and browser.
- Tool-friendly: Build with any Java IDE or use the command line.
Since I learned Kotiln mainly for Android as a foundation
So I will mainly introduce Kotlin Android environment building
Kotlin Android environment setup
Install the Kotlin plug-in
The Kotlin plugin will be built into Android Studio starting with version 3.0 (Preview).
Open the Settings panel and find the Plugins on the right side (Ctrl+, Command +). Type “Kotlin” in the search box to find the Plugins. Click On Search in Repositories and install it. Once installed, you will need to restart Android Studio.
Create a new project
Select Start a new Android Studio project or File | new project, most of the options are the default values, you just need to press “enter” key several times.
Android Studio 3.0 provides options to enable Kotlin support in the current dialog box. If checked, you can skip the “Configuring Kotlin in the Project” step.
Select the Android version:
Select the Activity style you want to create:
After the Activity:
In Android Studio 3.0, you have the option of using Kotlin to create an activity, so the “Converting Java code to Kotlin (Converting Java code to Kotlin)” step is not required.
In earlier versions, the activity was created using Java and then converted using an automatic conversion tool.
Convert the Java code to Kotlin
Open Android Studio again, create a new Android project, and add a default MainActivity
Open the MainActivity. Java File, bring up the Code through the menu bar | Convert Java File to Kotlin File:
Once the transformation is complete, you can see the activity written using Kotlin.
Kotlin is configured in the project
When you start editing this file, Android Studio will tell you that Kotlin has not been configured for the current project. Just follow the prompts or you can select Tools from the menu bar
Select the latest version in the following dialog box.
After the Kotlin configuration is complete, the build.gradle file for the application is updated. You can see the new Apply plugin: ‘Kotlin-Android’ and its dependencies.
To synchronize the Project, click “Sync Now” in the prompt box or use Sync Project with Gradle Files.
Kotlin basic syntax
Kotlin files are suffixed with.kt.
Package declaration
Code files typically start with a package declaration:
com.breeze.main
import java.util.*
fun test() {}
class Breeze {}
Copy the code
Kotlin source files do not need matching directories and packages, and source files can be placed in any file directory.
In this example, the full name of test() is com.breeze.main.test, and the full name of breeze is com.breeze.main.breeze.
If no package is specified, the default package is used.
The default import
There are multiple packages imported into each Kotlin file by default:
- kotlin.*
- kotlin.annotation.*
- kotlin.collections.*
- kotlin.comparisons.*
- kotlin.io.*
- kotlin.ranges.*
- kotlin.sequences.*
- kotlin.text.*
The function definitions
The function definition uses the keyword fun and the parameter format is: parameter: type
fun sum(a: Int, b: Int): Int { // Int argument, return value Int
return a + b
}
Copy the code
Expression as function body, return type automatically inferred:
fun sum(a: Int, b: Int) = a + b
public fun sum(a: Int, b: Int): Int = a + b // The public method must specify the return type
Copy the code
A function that returns no value (similar to void in Java) :
fun printSum(a: Int, b: Int): Unit {
print(a + b)
}
// If it returns Unit, it can be omitted (also for public methods) :
public fun printSum(a: Int, b: Int) {
print(a + b)
}
Copy the code
Variable length argument function
Variable length arguments to a function can be identified by the vararg keyword:
Fun vars(vararg v:Int){for(vt in v){print(vt)}} fun main(args: Array<String>) {vars(1,2,3,4,5)Copy the code
Lambda (anonymous function)
Examples of lambda expressions:
Play.kotlinlang.org/#eyJ2ZXJzaW…
/ / test
fun main(args: Array<String>) {
val sumLambda: (Int.Int) - >Int = {x,y -> x+y}
println(sumLambda(1.2)) 3 / / output
}
Copy the code
Define constants and variables
Variable variable definition: var keyword
Var < identifier > : < type > = < initial value >Copy the code
Definition of immutable variable: the val keyword, a variable that can only be assigned once (similar to a final variable in Java)
Val < identifier > : < type > = < initialization value >Copy the code
Constants and variables can have no initialized value, but must be initialized before reference
The compiler supports automatic type determination, that is, the declaration can not specify the type, the compiler to determine.
Val a: Int = 1 val b = 1 Var x = 5 // The system automatically concludes that the variable type is Int x += 1 // The variable can be modifiedCopy the code
annotation
Kotlin supports single-line and multi-line comments, as shown in the following example:
// This is a single-line comment /* This is a multi-line block comment. * /Copy the code
Unlike Java, block annotations in Kotlin allow nesting.
String template
$represents a variable name or value
$varName represents the variable value
${varname.fun ()} denotes the return value of the variable’s method:
var a = 1
// Simple name in template:
val s1 = "a is $a"
a = 2
// Any expression in the template:
val s2 = "${s1.replace("is"."was")}, but now is $a"
Copy the code
NULL checking mechanism
Kotlin’s empty safety design for the declaration can be empty parameters, in the use of empty judgment processing, there are two processing methods, after the field plus!! Throw an empty exception, as in Java, followed by? Do not do processing return value null or cooperate? : Short judgment processing
// Type after? Indicates nullable
var age: String? = "23"
// Throw a null pointer exception
valages = age!! .toInt()// Returns null without processing
valages1 = age? .toInt()// If age is empty, -1 is returned
valages2 = age? .toInt() ? : -1
Copy the code
When a reference may be null, the corresponding type declaration must be explicitly marked as nullable.
Returns null if the string content in STR is not an integer:
fun parseInt(str: String): Int? {/ /... }Copy the code
The following example shows how to use a function that can return null:
Play.kotlinlang.org/#eyJ2ZXJzaW…
fun main(args: Array<String>) {
if (args.size < 2) {
print("Two integers expected")
return
}
val x = parseInt(args[0])
val y = parseInt(args[1])
// Using 'x * y' directly causes an error because they may be null.
if(x ! =null&& y ! =null) {
// The types of x and y are automatically converted to non-NULL variables after null-checking
print(x * y)
}
}
Copy the code
Type detection and automatic type conversion
We can use the is operator to detect whether an expression is an instanceof a type (similar to the instanceof keyword in Java).
fun getStringLength(obj: Any): Int? {
if (obj is String) {
// Obj is automatically converted to String
return obj.length
}
// There is another method here, unlike Java instanceof, which uses! is
// if (obj ! is String){
// // XXX
// }
// obj here is still a reference of type Any
return null
}
Copy the code
or
fun getStringLength(obj: Any): Int? {
if (obj !is String)
return null
// In this branch, the type 'obj' is automatically converted to 'String'
return obj.length
}
Copy the code
Maybe even
fun getStringLength(obj: Any): Int? {
// On the right side of the && operator, the type of 'obj' is automatically converted to 'String'
if (obj is String && obj.length > 0)
return obj.length
return null
}
Copy the code
interval
Interval expressions are given by operators of the form.. The rangeTo function is complemented by in and! The in formation.
An interval is defined for any comparable type, but for integer primitive types, it has an optimized implementation. Here are some examples of using ranges:
for (i in 1.4.) print(i) / / output "1234"
for (i in 4.1.) print(i) // Output nothing
if (i in 1.10.) { // The same thing as 1 <= I && I <= 10
println(i)
}
// Use step to specify the step size
for (i in 1.4. step 2) print(i) / / output "13"
for (i in 4 downTo 1 step 2) print(i) / / output "42"
// Use until to exclude the end element
for (i in 1 until 10) { // I in [1, 10]
println(i)
}
Copy the code
The instance test
fun main(args: Array<String>) {
print("Loop output:")
for (i in 1.4.) print(i) / / output "1234"
println("\n----------------")
print("Set step size:")
for (i in 1.4. step 2) print(i) / / output "13"
println("\n----------------")
print("Using downTo:")
for (i in 4 downTo 1 step 2) print(i) / / output "42"
println("\n----------------")
print("Use until:")
// Use until to exclude the end element
for (i in 1 until 4) { // I in [1, 4]
print(i)
}
println("\n----------------")}Copy the code
Output result:
Loop output: 1234 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - setting step: 13 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - use downTo: 42 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- use until: 123Copy the code
Kotlin basic data type
Kotlin’s basic numeric types include Byte, Short, Int, Long, Float, Double, and so on. Unlike Java, a character is not a numeric type, but a separate data type.
type | Bit width |
---|---|
Double | 64 |
Float | 32 |
Long | 64 |
Int | 32 |
Short | 16 |
Byte | 8 |
The literal constants
Here are all types of literal constants:
- Decimal: 123
- Long integers end with a capital L: 123L
- Hexadecimal starts with 0x: 0x0F
- Base 2 starts with 0B: 0b00001011
- Note: Base 8 is not supported
Kotlin also supports traditional symbolic floating-point values:
- 5. annual rainfall
123.5
.123.5 e10
- Floats use the f or f suffix:
123.5 f
You can use underscores to make numeric constants more readable:
val oneMillion = 1 _000_000
val creditCardNumber = 1234_5678_9012_3456L
val socialSecurityNumber = 999_99_9999L
val hexBytes = 0xFF_EC_DE_5E
val bytes = 0b11010010_01101001_10010100_10010010
Copy the code
Compare two numbers
There are no underlying data types in Kotlin, only encapsulated numeric types. For every variable you define, Kotlin actually wraps an object for you, so that there are no null Pointers. The same goes for numeric types, so when comparing two numbers, there is a difference between comparing data size and comparing whether two objects are the same.
In Kotlin, three equal signs === represent the address of the object being compared, and two == represent the size of two values.
fun main(args: Array<String>) {
val a: Int = 10000
println(a === a) // true, value equal, object address equal
// After boxing, two different objects are created
val boxedA: Int? = a
val anotherBoxedA: Int? = a
// The value is the same as that of 10000
println(boxedA === anotherBoxedA) // false, value equal, object address different
println(boxedA == anotherBoxedA) // true, the value is equal
}
Copy the code
Type conversion
Because of the different representations, a smaller type is not a subtype of a larger type, and a smaller type cannot be implicitly converted to a larger type. This means that we cannot assign a Byte value to an Int variable without an explicit conversion.
val b: Byte = 1 // OK, literals are statically checked
val i: Int = b / / error
Copy the code
We can use the toInt() method instead.
val b: Byte = 1 // OK, literals are statically checked
val i: Int = b.toInt() // OK
Copy the code
Each data type has the following methods that can be converted to other types:
toByte(): Byte
toShort(): Short
toInt(): Int
toLong(): Long
toFloat(): Float
toDouble(): Double
toChar(): Char
Copy the code
Automatic type conversion can be used in some cases, if the correct data type can be inferred from the context and the mathematical operators are overloaded accordingly. For example the following is true:
val l = 1L + 3 // Long + Int => Long
Copy the code
An operator
There are a number of bitwise operators available for Int and Long, respectively:
SHL (bits) - Left-shifted (Java's <<) SHR (bits) - right-shifted (Java's >>) USHR (bits) - unsigned right-shifted (Java's >>>) and(bits) - with or(bits) - or Xor (bits) - XOR or INV () - ReverseCopy the code
character
Unlike Java, a Char in Kotlin cannot be manipulated directly with numbers; it must be enclosed in single quotes. Ordinary characters like ‘0’, ‘a’.
fun check(c: Char) {
if (c == 1) { // Error: type incompatible
/ /...}}Copy the code
Character literals are enclosed in single quotes: ‘1’. Special characters can be escaped with backslashes. These escape sequences are supported: \t, \b, \n, \r, ‘, “, \ and $. Other characters are encoded using Unicode escape sequence syntax: ‘\uFF00’.
We can explicitly convert characters to Int numbers:
fun decimalDigitValue(c: Char): Int {
if (c !in '0'.'9')
throw IllegalArgumentException("Out of range")
return c.toInt() - '0'.toInt() // Explicitly convert to a number
}
Copy the code
When nullable references are required, things like numbers and characters are boxed. The boxing operation does not preserve identity.
Boolean
Boolean is represented by Boolean type, which has two values: true and false.
Empty reference booleans will be boxed if needed.
The built-in Boolean operations are:
| | - short circuit logic or && - short circuit logic and! Non - logicCopy the code
An array of
Array is implemented by Array class, and there is a size attribute and get and set methods, because [] overrides get and set methods, so we can easily get or set the value of the corresponding position of the Array by subscript.
Arrays can be created in two ways: using the function arrayOf(); The other is to use factory functions. As shown below, we create two arrays in two different ways:
fun main(args: Array<String>) {
/ / [1, 2, 3]
val a = arrayOf(1.2.3)
/ / 4-trichlorobenzene [0]
val b = Array(3, { i -> (i * 2)})// Read the contents of the array
println(a[0]) // Output: 1
println(b[1]) // Output: 2
}
Copy the code
As mentioned above, the [] operator represents calling the member functions get() and set().
Note: Unlike Java, arrays in Kotlin are uncovariant.
In addition to Array, there are ByteArray, ShortArray, and IntArray, which are used to represent arrays of various types without boxing, and therefore are more efficient. They are used in the same way as Array:
val x: IntArray = intArrayOf(1.2.3)
x[0] = x[1] + x[2]
Copy the code
string
Like Java, strings are immutable. The square brackets [] syntax makes it easy to get a character in a string, or to iterate through the for loop:
for (c in str) {
println(c)
}
Copy the code
Kotlin supports three quote “”” extended strings, and supports multi-line strings, such as:
Play.kotlinlang.org/#eyJ2ZXJzaW…
fun main(args: Array<String>) {
val text = """ Multi-line string Multi-line string
println(text) // The output has some leading whitespace
}
Copy the code
Strings can be removed with the trimMargin() method.
fun main(args: Array<String>) {
val text = "" |" multi-line string rookie tutorial | | multi-line string | Breeze ", "".trimMargin()
println(text) // The leading space was removed
}
Copy the code
The default | prefix is used as the boundary, but you can choose the other characters and passed as a parameter, such as trimMargin (” > “).
String template
Strings can contain template expressions, which are little pieces of code that evaluate and merge the results into the string. The template expression starts with a dollar character ($) and consists of a simple name:
fun main(args: Array<String>) {
val i = 10
val s = "i = $i" // I = 10
println(s)
}
Copy the code
Or any expression expanded with curly braces:
fun main(args: Array<String>) {
val s = "breeze"
val str = "$s.length is ${s.length}" // Evaluate to "breeze. Length is 6"
println(str)
}
Copy the code
Templates are supported inside both native and escaped strings. If you need to represent the literal $character in a native string (which does not support backslash escape), you can use the following syntax:
fun main(args: Array<String>) {
val price = "" "The ${'$'}9.99 "" "
println(price) // Evaluates to $9.99
}
Copy the code
Kotlin conditional control
IF expression
An if statement contains a Boolean expression and one or more statements.
// Traditional usage
var max = a
if (a < b) max = b
/ / use the else
var max: Int
if (a > b) {
max = a
} else {
max = b
}
// as an expression
val max = if (a > b) a else b
Copy the code
We can also assign the result of an IF expression to a variable.
val max = if (a > b) {
print("Choose a")
a
} else {
print("Choose b")
b
}
Copy the code
This also means that I don’t need a ternary operator like Java does, because we can use it to simply implement:
val c = if (condition) a else b
Copy the code
The instance
Play.kotlinlang.org/#eyJ2ZXJzaW…
fun main(args: Array<String>) {
var x = 0
if(x>0){
println("X" (greater than zero)}else if(x==0){
println("X equals 0")}else{
println("X" (less than zero)}var a = 1
var b = 2
val c = if (a>=b) a else b
println("Has a value of c$c")}Copy the code
The output is:
X is equal to the0A value of c2
Copy the code
Using interval
Uses the in operator to detect whether a number is in a specified interval of the format x.. Y:
The instance
fun main(args: Array<String>) {
val x = 5
val y = 9
if (x in 1.8.) {
println("X is in the interval.")}}Copy the code
The output is:
X is in the intervalCopy the code
When the expression
When compares its parameters to all the branch conditions in order until a branch satisfies the condition.
When can be used as either an expression or a statement. If it is used as an expression, the value of the eligible branch is the value of the entire expression; if it is used as a statement, the value of the individual branches is ignored.
When is similar to the switch operator in other languages. Its simplest form is as follows:
when (x) {
1 -> print("x == 1")
2 -> print("x == 2")
else- > {// Notice this block
print("X is not one, x is not two.")}}Copy the code
In when, else is the same as the default of switch. If none of the other branches meet the criteria, the else branch will be evaluated.
If many branches need to be treated in the same way, you can group multiple branch conditions together, separated by commas:
when (x) {
0.1 -> print("x == 0 or x == 1")
else -> print("otherwise")}Copy the code
We can also detect a value in (in) or not in (! In) an interval or set:
when (x) {
in 1.10. -> print("x is in the range")
in validNumbers -> print("x is valid")!in 10.20. -> print("x is outside the range")
else -> print("none of the above")}Copy the code
Another possibility is to detect whether a value is (is) or not (! Is) a value of a specific type. Note: Thanks to smart conversions, you can access methods and properties of this type without any additional detection.
fun hasPrefix(x: Any) = when(x) {
is String -> x.startsWith("prefix")
else -> false
}
Copy the code
When can also be used to replace if-else if chains. If no arguments are provided, all branch conditions are simple Boolean expressions, and a branch is executed when its condition is true:
when {
x.isOdd() -> print("x is odd")
x.isEven() -> print("x is even")
else -> print("x is funny")}Copy the code
The instance
Play.kotlinlang.org/#eyJ2ZXJzaW…
fun main(args: Array<String>) {
var x = 0
when (x) {
0.1 -> println("x == 0 or x == 1")
else -> println("otherwise")}when (x) {
1 -> println("x == 1")
2 -> println("x == 2")
else- > {// Notice this block
println("X is not one, x is not two.")}}when (x) {
in 0.10. -> println("X is within that range.")
else -> println("X is not within that range.")}}Copy the code
Output result:
x == 0 or x == 1X is not1, nor is it2X is within that rangeCopy the code
When uses the in operator to determine whether the collection contains an instance:
Play.kotlinlang.org/#eyJ2ZXJzaW…
fun main(args: Array<String>) {
val items = setOf("apple"."banana"."kiwi")
when {
"orange" in items -> println("juicy")
"apple" in items -> println("apple is fine too")}}Copy the code
Output result:
apple is fine too
Copy the code
Kotlin cycle control
The For loop
The for loop can iterate over any object that provides an iterator with the syntax:
for (item in collection) print(item)
Copy the code
The body of a loop can be a code block:
for (item: Int in ints) {
/ /...
}
Copy the code
As mentioned above, for can loop over any object that provides an iterator.
If you want to traverse an array or a list by index, you can do this:
for (i in array.indices) {
print(array[i])
}
Copy the code
Note that this “walking over an interval” compiles into an optimized implementation without creating additional objects.
Or you can use the withIndex library function:
for ((index, value) in array.withIndex()) {
println("the element at $index is $value")}Copy the code
The instance
Iterate over the collection:
fun main(args: Array<String>) {
val items = listOf("apple"."banana"."kiwi")
for (item in items) {
println(item)
}
for (index in items.indices) {
println("item at $index is ${items[index]}")}}Copy the code
Output result:
apple
banana
kiwi
item at 0 is apple
item at 1 is banana
item at 2 is kiwi
Copy the code
While with the do… The while loop
While is the most basic loop, and its structure is:
while(Boolean expression) {// Loop content
}
Copy the code
The do… While loop For a while statement, if the condition is not met, the loop cannot be entered. But sometimes we need to do it at least once, even if the conditions are not met.
The do… The while loop is similar to the while loop, except that do… The while loop is executed at least once.
do {
// Code statement
}while(Boolean expression);Copy the code
The instance
fun main(args: Array<String>) {
println(- while the use of -- -- -- -- --")
var x = 5
while (x > 0) {
println( x--)
}
println("----do... While the use of -- -- -- -- --")
var y = 5
do {
println(y--)
} while(y>0)}Copy the code
Output result:
5
4
3
2
1
----do.whileUse the -- -- -- -- -5
4
3
2
1
Copy the code
Return and jump
Kotlin has three structured jump expressions:
- return. The default is to return from the function that most directly surrounds it or from an anonymous function.
- break. Terminate the loop that most directly surrounds it.
- continue. Continue the next cycle that most directly surrounds it.
Kotlin supports the traditional break and continue operators in loops.
fun main(args: Array<String>) {
for (i in 1.10.) {
if (i==3) continue // if I is 3, skip the current loop and continue the next loop
println(i)
if (i>5) break // if I is 6, the loop is broken}}Copy the code
Output result:
1
2
4
5
6
Copy the code
Break and Continue tags
Any expression in Kotlin can be labeled with a label. Labels are in the format of an identifier followed by an @ sign, such as ABC @ and fooBar@. To label an expression, we simply label it.
loop@ for (i in 1.100.) {
/ /...
}
Copy the code
Now, we can restrict break or continue with tags:
loop@ for (i in 1.100.) {
for (j in 1.100.) {
if(...)break@loop}}Copy the code
The break restricted by the tag jumps to the execution point just after the loop specified by the tag. Continue continues the next iteration of the loop specified by the tag.
Return at the label
Kotlin has function literals, local functions, and object expressions. So Kotlin’s functions can be nested. The tag-restricted return allows us to return from the outer function. One of the most important uses is to return from lambda expressions. Think back to when we wrote this:
fun foo(a) {
ints.forEach {
if (it == 0) return
print(it)
}
}
Copy the code
This return expression returns from foo, the function that most directly surrounds it. (Note that this nonlocal return only supports lambda expressions passed to inline functions.) If we need to return from a lambda expression, we must label it and restrict the return.
fun foo(a) {
ints.forEach lit@ {
if (it == 0) return@lit
print(it)
}
}
Copy the code
Now, it only returns from the lambda expression. It is often more convenient to use implicit labels. The label has the same name as the function that accepts the lambda.
fun foo(a) {
ints.forEach {
if (it == 0) return@forEach
print(it)
}
}
Copy the code
Alternatively, we can replace lambda expressions with an anonymous function. A return statement inside an anonymous function is returned from the anonymous function itself
fun foo(a) {
ints.forEach(fun(value: Int) {
if (value == 0) return
print(value)
})
}
Copy the code
When a return value is required, the parser preferentially selects the tag-bound return, i.e
return@a 1
Copy the code
Return 1 from the tag @a instead of an expression for the tag tag (@a 1).
Kotlin classes and objects
The class definition
The Kotlin class can contain: constructors and initialization code blocks, functions, attributes, inner classes, and object declarations.
Kotlin uses the keyword class to declare a class, followed by the class name:
class Breeze { // The class name is Breeze
// Braces are the body of the class
}
Copy the code
We can also define an empty class:
class Empty
Copy the code
Member functions can be defined in a class:
class Breeze() {
fun foo(a) { print("Foo")}// Member functions
}
Copy the code
Attributes of a class
Attribute definitions
Attributes of a class can be declared mutable using the keyword var, or immutable using the read-only keyword val.
class Breeze {
varName: String =...varUrl: String =...varCity: String =... }Copy the code
We can use constructors to create class instances just like we would use normal functions:
val site = Breeze() // Kotlin has no new keyword
Copy the code
To use an attribute, you simply refer to it by name
site.name // Use the. Sign to reference
site.url
Copy the code
Classes in Koltin can have a primary constructor, and one or more secondary constructors, which are part of the class header and come after the class name:
class Person constructor(firstName: String) {}
Copy the code
If the main constructor does not have any annotations and does not have any visibility modifiers, the constructor keyword can be omitted.
class Person(firstName: String) {
}
Copy the code
Getter and setter
Complete syntax for property declarations:
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
Copy the code
Getters and setters are optional
If the property type can be inferred from the initialization statement or from the class’s member functions, the type can be omitted, and val does not allow setter functions to be set because it is read-only.
var allByDefault: Int? // error: an initialization statement is required. Getter and setter methods are implemented by default
var initialized = 1 // The type is Int and the getter and setter are implemented by default
val simple: Int? Getter is implemented by default, but must be initialized in the constructor
val inferredType = 1 // The type is Int and the getter is implemented by default
Copy the code
The instance
The following example defines a Person class with two mutable variables, lastName, which modifies the getter method, and no, which modifies the setter method.
class Person {
var lastName: String = "zhang"
get() = field.toUpperCase() // Convert the assigned variable to uppercase
set
var no: Int = 100
get() = field // Back-end variables
set(value) {
if (value < 10) { // Return the value if the value passed is less than 10
field = value
} else {
field = -1 // Returns -1 if the value passed is greater than or equal to 10}}var heiht: Float = 145.4 f
private set
}
/ / test
fun main(args: Array<String>) {
var person: Person = Person()
person.lastName = "wang"
println("lastName:${person.lastName}")
person.no = 9
println("no:${person.no}")
person.no = 20
println("no:${person.no}")}Copy the code
The output is:
lastName:WANG
no:9
no:-1
Copy the code
Classes in Kotlin cannot have fields. The Backing Fields mechanism is provided, with the Backing Fields declared using the field keyword, which can only be used by accessors to properties, as in the example above:
var no: Int = 100
get() = field // Back-end variables
set(value) {
if (value < 10) { // Return the value if the value passed is less than 10
field = value
} else {
field = -1 // Returns -1 if the value passed is greater than or equal to 10}}Copy the code
Non-empty attributes must be initialized when they are defined, and Kotlin provides a way to delay initialization by using the lateInit keyword:
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup(a) {
subject = TestSubject()
}
@Test fun test(a) {
subject.method() // dereference directly}}Copy the code
The main constructor
The primary constructor cannot contain any code, and the initialization code can be placed in the initialization code section, which is prefixed with the init keyword.
class Person constructor(firstName: String) {
init {
println("FirstName is $firstName")}}Copy the code
Note: The arguments to the primary constructor can be used in the initialization code snippet or in the property initialization code defined by the class body N. A concise syntax for defining properties and initializing property values (either var or val) via the primary constructor:
class People(val firstName: String, val lastName: String) {
/ /...
}
Copy the code
If the constructor has annotations or visibility modifiers, the constructor keyword is required and the annotations and modifiers come before it.
The instance
Create a Breeze class and pass in the site name via the constructor:
class Breeze constructor(name: String) { // The class name is Breeze
// Braces are the body of the class
var url: String = "http://www.Breeze.com"
var country: String = "CN"
var siteName = name
init {
println("Initialize the site name:${name}")}fun printTest(a) {
println("I'm a function of class.")}}fun main(args: Array<String>) {
val Breeze = Breeze("Rookie Tutorial")
println(Breeze.siteName)
println(Breeze.url)
println(Breeze.country)
Breeze.printTest()
}
Copy the code
The output is:
HTTP: initializing a web site//www.Breeze.comCN I'm a function of classCopy the code
subconstructor
Classes can also have secondary constructors, which need to be prefixed by constructor:
class Person {
constructor(parent: Person) {
parent.children.add(this)}}Copy the code
If a class has a primary constructor, each subconstructor must, either directly or indirectly, proxy the primary constructor through another subconstructor. Proiding another constructor in the same class using the this keyword:
class Person(val name: String) {
constructor (name: String, age:Int) : this(name) {
// Initialize...}}Copy the code
If a non-abstract class does not declare a constructor (primary constructor or secondary constructor), it produces a constructor with no arguments. The constructor is public. If you don’t want your class to have a public constructor, you have to declare an empty main constructor:
class DontCreateMe private constructor () {}Copy the code
Note: In the JVM virtual machine, if all arguments to the main constructor have default values, the compiler generates an additional constructor with no arguments, which uses the default values directly. This makes it easier for Kotlin to use libraries like Jackson or JPA that use no-argument constructors to create class instances.
class Customer(val customerName: String = "") Copy the code
The instance
class Breeze constructor(name: String) { // The class name is Breeze
// Braces are the body of the class
var url: String = "http://www.Breeze.com"
var country: String = "CN"
var siteName = name
init {
println("Initialize the site name:${name}")}// subconstructor
constructor (name: String, alexa: Int) : this(name) {
println("Alexa ranking$alexa")}fun printTest(a) {
println("I'm a function of class.")}}fun main(args: Array<String>) {
val Breeze = Breeze("Rookie Tutorial".10000)
println(Breeze.siteName)
println(Breeze.url)
println(Breeze.country)
Breeze.printTest()
}
Copy the code
The output is:
Initialization site name: Rookie tutorial Alexa ranking10000HTTP://www.Breeze.comCN I'm a function of classCopy the code
An abstract class
Abstract is one of the characteristics of object-oriented programming. A class itself, or some members of a class, can be declared abstract. An abstract member has no concrete implementation in a class.
Note: There is no need to annotate the open annotation for abstract classes or abstract members.
open class Base {
open fun f(a){}}abstract class Derived : Base() {
override abstract fun f(a)
}
Copy the code
Nested classes
We can nest a class within another class, as shown in the following example:
class Outer { / / outside class
private val bar: Int = 1
class Nested { / / nested classes
fun foo(a) = 2}}fun main(args: Array<String>) {
val demo = Outer.Nested().foo() // Call format: external class. Nested classes. Nested class methods/attributes
println(demo) / / = = 2
}
Copy the code
The inner class
Inner classes are represented by the inner keyword.
The inner class has a reference to the object of the outer class, so the inner class can access the attributes and functions of the member of the outer class.
class Outer {
private val bar: Int = 1
var v = "Member Attributes"
/** nested inner class **/
inner class Inner {
fun foo(a) = bar // Access the external class member
fun innerTest(a) {
var o = this@Outer // Get the member variables of the external class
println("Inner classes can refer to members of outer classes, for example:" + o.v)
}
}
}
fun main(args: Array<String>) {
val demo = Outer().Inner().foo()
println(demo) / / 1
val demo2 = Outer().Inner().innerTest()
println(demo2) // An inner class can refer to a member of an outer class, such as a member attribute
}
Copy the code
To disambiguously access this from an external scope, we use this@label, where @label is a label that refers to the source of this.
Anonymous inner class
Use object expressions to create anonymous inner classes:
class Test {
var v = "Member Attributes"
fun setInterFace(test: TestInterFace) {
test.test()
}
}
/** * defines the interface */
interface TestInterFace {
fun test(a)
}
fun main(args: Array<String>) {
var test = Test()
/** * Uses object expressions to create interface objects, which are instances of anonymous inner classes. * /
test.setInterFace(object : TestInterFace {
override fun test(a) {
println("Object expression creates instance of anonymous inner class")}}}Copy the code
Class modifier
Class modifiers include classModifier and _accessModifier_:
-
ClassModifier: Indicates the attribute modifier of a class.
abstract / / abstract classes final // Class is not inheritable, default property enum / / the enumeration classes open // Classes are inheritable and final by default annotation / / comment Copy the code
-
AccessModifier: Access permission modifier
private // Only visible in the same file protected // Visible in the same file or subclass public // All calls are visible internal // visible in the same module Copy the code
The instance
// File name: example.kt
package foo
private fun foo(a) {} // visible in example.kt
public var bar: Int = 5 // This property can be seen everywhere
internal val baz = 6 // Visible in the same module
Copy the code
Kotlin inheritance
All classes in Kotlin inherit from this Any class, which is the superclass of all classes, and the default superclass for classes that do not have a supertype declaration:
class Example/ / fromAnyImplicit inheritance
Copy the code
Any provides three functions by default:
equals()
hashCode()
toString()
Copy the code
Note: Any is not java.lang.object.
If a class is to be inherited, it can be decorated with the open keyword.
open class Base(p: Int) // Define the base class
class Derived(p: Int) : Base(p)
Copy the code
The constructor
Subclasses have primary constructors
If a subclass has a primary constructor, the base class must be initialized immediately in the primary constructor.
open class Person(var name : String, var age : Int) {/ / the base class
}
class Student(name : String, age : Int.var no : String, var score : Int) : Person(name, age) {
}
/ / test
fun main(args: Array<String>) {
val s = Student("Breeze".18."S12346".89)
println("Student name:${s.name}")
println("Age:${s.age}")
println("Student Number:${s.no}")
println("The result:${s.score}")}Copy the code
Output result:
Name: Breeze Age:18Student ID: S1234689
Copy the code
Subclasses have no main constructor
If the subclass does not have a primary constructor, then the base class must be initialized with the super keyword in each secondary constructor, or another constructor must be proxy. When initializing a base class, different constructors of the base class can be called.
class Student : Person {
constructor(ctx: Context) : super(ctx) {
}
constructor(ctx: Context, attrs: AttributeSet) : super(ctx,attrs) {
}
}
Copy the code
The instance
/** User base class **/
open class Person(name:String){
/** Secondary constructor **/
constructor(name:String,age:Int) :this(name){
/ / initialization
println("------- base class secondary constructor ---------")}}/** Subclasses inherit from the Person class **/
class Student:Person{
/** Secondary constructor **/
constructor(name:String,age:Int,no:String,score:Int) :super(name,age){
println("------- inherits class secondary constructor ---------")
println("Student name:${name}")
println("Age:${age}")
println("Student Number:${no}")
println("The result:${score}")}}fun main(args: Array<String>) {
var s = Student("Breeze".18."S12345".89)}Copy the code
Output result:
-- -- -- -- -- -- -- a secondary structure function of the base class -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - a secondary structure function of the derived class -- -- -- -- -- -- -- -- -- the student name: Breeze age: 18 students number: S12345 grade: 89Copy the code
rewrite
When you declare a function with fun ina base class, the function defaults to final and cannot be overridden by subclasses. If subclasses are allowed to override the function, then add the open modifier manually. The subclass override method uses the override keyword:
/** User base class **/
open class Person{
open fun study(a){ // Allow subclass overrides
println("I graduated.")}}/** Subclasses inherit from the Person class **/
class Student : Person() {
override fun study(a){ // Override the method
println("I'm in college.")}}fun main(args: Array<String>) {
val s = Student()
s.study();
}
Copy the code
The output is:
I'm in college.Copy the code
If there are multiple identical methods (inherited or implemented from other classes, such as class A or B), then the method must be overridden to use the super stereotype to selectively call the implementation of the parent class.
open class A {
open fun f (a) { print("A")}fun a(a) { print("a")}}interface B {
fun f(a) { print("B")}// Interface member variables are open by default
fun b(a) { print("b")}}class C() : A() , B{
override fun f(a) {
super<A>.f()/ / call A.f ()
super<B>.f()/ / call b. ()}}fun main(args: Array<String>) {
val c = C()
c.f();
}
Copy the code
C inherits from either a() or b(). Not only can C inherit functions from a or B, but C can inherit functions that are common to both a() and B(). At this point, the function has only one implementation in. To disambiguate, the function must call the implementation of the function in A() and B() and provide its own implementation.
The output is:
AB
Copy the code
Attribute to rewrite
Attribute overrides use the override keyword. The attribute must have compatible types. Each declared attribute can be overridden using an initializer or getter:
open class Foo {
open val x: Int get{... }}class Bar1 : Foo() {
override val x: Int = ……
}
Copy the code
You can override a val property with a var property, but not the other way around. Because the val property itself defines getter methods, rewriting to var declares an additional setter method in the derived class
You can use the override keyword as part of the attribute declaration in the main constructor:
interface Foo {
val count: Int
}
class Bar1(override val count: Int) : Foo
class Bar2 : Foo {
override var count: Int = 0
}
Copy the code
A few points to add:
When a subclass extends from its parent, it cannot have a variable with the same name as the parent unless the variable is private or open and the subclass overrides it with override:
open class Person(var name: String, var age: Int) {
open var sex: String = "unknow"
init {
println("Base class initialization")}}// a hides constructor for subclasses whose name is predated by var is not permitted and calls 'name' hides member of supertype and needs 'override'
class Student(var name: String, age: Int.var no: String, var score: Int) : Person(name, age) {
override var sex: String = "male"
}
Copy the code
In the code snippet above, adding the var keyword to the first field name in the subclass Student main constructor returns an error.
2. Subclasses need not call methods of the same name shared by their parent class and interface
To quote from the article: “C inherits from a() or b(). Not only can C inherit functions from A or B, but also C can inherit functions shared by a() and B(). At this point, there is only one implementation of the function in. To disambiguate, the function must call the implementation of the function in A() and B() and provide its own implementation.
It is not necessary to call the implementation of this function in A() and B() as follows:
open class A {
open fun f(a) {
println("A")}fun a(a) {
println("a")}}interface B {
fun f(a) {
println("B")}fun b(a) {
println("b")}}class C : A(), B {
override fun f(a) {
// super<A>.f()
// super<B>.f()
println("C")}}Copy the code
Comment out super.f() and super.f() as shown in the code snippet.
3. My guess is that a subclass cannot override var in its parent class with val: When a subclass overwrites the parent class property, it means that it must rewrite the getter and setter methods of the property. However, val in a subclass cannot have setter methods, so it cannot “override” the setter method of var in the parent class, which is equivalent to narrowing the scope of use of the corresponding property in the parent class, and is not allowed. Just like we can’t rewrite a public method in a parent class as a private method.
When a variable is being initialized, the backing field field must be present in the variable. The default getter and setter methods for the variable have a backing field field. But if we overwrite the getter and setter methods for this variable, and the field keyword is not present in the getter and setter methods, the compiler will report an error. Filed field Initializer is not allowed here because this property has no backing field If a variable is initialized, the field must be declared. If a variable is initialized, the field must be declared.
var aaa: Int = 0
get() {
field Var aaa: Int = 0 will return an error. Do not initialize it unless you remove the = 0 part
return 0
}
set(value) {}
Copy the code
Kotlin interface
The Kotlin interface is similar to Java 8 in that it uses the interface keyword to define interfaces, allowing methods to have default implementations:
interface MyInterface {
fun bar(a) / / unrealized
fun foo(a) { / / has been realized
// Optional method body
println("foo")}}Copy the code
Implementing an interface
A class or object can implement one or more interfaces.
class Child : MyInterface {
override fun bar(a) {
/ / the method body}}Copy the code
The instance
interface MyInterface {
fun bar(a)
fun foo(a) {
// Optional method body
println("foo")}}class Child : MyInterface {
override fun bar(a) {
/ / the method body
println("bar")}}fun main(args: Array<String>) {
val c = Child()
c.foo();
c.bar();
}
Copy the code
The output is:
foo
bar
Copy the code
Attributes in interfaces
Attributes in an interface can only be abstract and cannot be initialized. The interface does not store attribute values. When implementing an interface, attributes must be overridden:
interface MyInterface{
var name:String // Name attribute, abstract
}
class MyImpl:MyInterface{
override var name: String = "runoob" // Override attributes
}
Copy the code
The instance
interface MyInterface {
var name:String // Name attribute, abstract
fun bar(a)
fun foo(a) {
// Optional method body
println("foo")}}class Child : MyInterface {
override var name: String = "breeze" // Override attributes
override fun bar(a) {
/ / the method body
println("bar")}}fun main(args: Array<String>) {
val c = Child()
c.foo();
c.bar();
println(c.name)
}
Copy the code
The output is:
foo
bar
breeze
Copy the code
Kotlin extension
Kotlin can extend the properties and methods of a class without inheriting or using the Decorator pattern.
Extension is a static behavior that has no effect on the extended class code itself.
Extension function
An extension function can add a new method to an existing class without modifying the original class.
fun receiverType.functionName(params){
body
}
Copy the code
- ReceiverType: indicates the receiver of the function, that is, the object to which the function is extended
- FunctionName: The name of the extension function
- Params: Extension function parameter, can be NULL
The following examples extend the User class:
class User(var name:String)
/** Extension function **/
fun User.Print(a){
print(The user name"$name")}fun main(arg:Array<String>){
var user = User("Breeze")
user.Print()
}
Copy the code
Example execution output is as follows:
The user name BreezeCopy the code
Add a swap function to MutableList:
// Extend the function swap to swap values in different places
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // This should be a list of objects
this[index1] = this[index2]
this[index2] = tmp
}
fun main(args: Array<String>) {
val l = mutableListOf(1.2.3)
// Positions 0 and 2 are swapped
l.swap(0.2) // the 'this' inside 'swap()' will point to the value of 'l'
println(l.toString())
}
Copy the code
Example execution output is as follows:
[3.2.1]
Copy the code
The this keyword refers to the Receiver Object (that is, the object instance specified before the dot when the extension function is called).
Extension functions are resolved statically
Extension functions are statically parsed, not virtual members of the receiver type. When an extension function is called, it is determined by the object expression on which the function is called, not by the dynamic type:
open class C
class D: C(a)fun C.foo(a) = "c" // Extend function foo
fun D.foo(a) = "d" // Extend function foo
fun printFoo(c: C) {
println(c.foo()) // The type is class C
}
fun main(arg:Array<String>){
printFoo(D())
}
Copy the code
Example execution output is as follows:
c
Copy the code
If the extension function is the same as the member function, the member function takes precedence over the extension function.
class C {
fun foo(a) { println("Member function")}}fun C.foo(a) { println("Extension function")}fun main(arg:Array<String>){
var c = C()
c.foo()
}
Copy the code
Example execution output is as follows:
A member functionCopy the code
Extend an empty object
Within the extension function, this can be used to determine whether the receiver is NULL, so that the extension function can be called even if the receiver is NULL. Such as:
funAny? .toString(a): String {
if (this= =null) return "null"
// After null detection, "this" is automatically converted to a non-null type, so the following toString()
// parse as a member function of Any class
return toString()
}
fun main(arg:Array<String>){
var t = null
println(t.toString())
}
Copy the code
Example execution output is as follows:
null
Copy the code
Extended attributes
In addition to functions, Kotlin also supports attributes to extend attributes:
val <T> List<T>.lastIndex: Int
get() = size - 1
Copy the code
Extended attributes can be defined in a class or kotlin file, not in a function. Since a property has no backing field, it is not allowed to be initialized and can only be defined by an explicitly supplied getter/setter.
Val foo. bar = 1 // Error: Extended properties cannot have initializersCopy the code
An extended attribute can only be declared as val.
Extensions of associated objects
If a class definition has a companion object, you can also define extension functions and attributes for the companion object.
The associated object calls the associated object with the “class name.” form. The extension function declared by the associated object is called with the class name qualifier:
class MyClass {
companion object{}// Will be called "Companion"
}
fun MyClass.Companion.foo(a) {
println("Extension functions accompanying objects")}val MyClass.Companion.no: Int
get() = 10
fun main(args: Array<String>) {
println("no:${MyClass.no}")
MyClass.foo()
}
Copy the code
Example execution output is as follows:
no:10The extension function that accompanies the objectCopy the code
Extended scope
Usually extension functions or attributes are defined under the top-level package:
package foo.bar
fun Baz.goo(a) { …… }
Copy the code
To use an extension outside the defined package, import the extension’s function name to use:
package com.example.usage
import foo.bar.goo // Import all extensions named goo
/ / or
import foo.bar.* // Import everything from foo.bar
fun usage(baz: Baz) {
baz.goo()
}
Copy the code
Extensions are declared as members
Within a class you can declare extensions for another class.
In this extension, there are multiple implicit receivers, where instances of the class in which the extension method definition is defined are called distribution receivers and instances of the target type of the extension method are called extension receivers.
class D {
fun bar(a) { println("D bar")}}class C {
fun baz(a) { println("C baz")}fun D.foo(a) {
bar() / / call db ar
baz() / / call mount az
}
fun caller(d: D) {
d.foo() // Call the extension function}}fun main(args: Array<String>) {
val c: C = C()
val d: D = D()
c.caller(d)
}
Copy the code
Example execution output is as follows:
D bar
C baz
Copy the code
Within class C, an extension to class D is created. At this point, C is the distribution receiver and D is the extension receiver. From the above example, it is clear that in the extension function, the member function that dispatches the receiver can be called.
If you are calling a function that exists on both the distributor and the extension receiver, the extension receiver takes precedence. To reference members of the distributor, you can use the qualified this syntax.
class D {
fun bar(a) { println("D bar")}}class C {
fun bar(a) { println("C bar")}// The same name as bar of class D
fun D.foo(a) {
bar() // Call d.bar () to extend receiver preference
this@C.bar() / / call mount ar ()
}
fun caller(d: D) {
d.foo() // Call the extension function}}fun main(args: Array<String>) {
val c: C = C()
val d: D = D()
c.caller(d)
}
Copy the code
Example execution output is as follows:
D bar
C bar
Copy the code
Extension functions defined as members can be declared open and can be overridden in subclasses. That is, the dispatch of such extension functions is virtual to the distribution recipient, but static to the extension recipient.
open class D {}class D1 : D() {}open class C {
open fun D.foo(a) {
println("D.foo in C")}open fun D1.foo(a) {
println("D1.foo in C")}fun caller(d: D) {
d.foo() // Call the extension function}}class C1 : C() {
override fun D.foo(a) {
println("D.foo in C1")}override fun D1.foo(a) {
println("D1.foo in C1")}}fun main(args: Array<String>) {
C().caller(D()) // output "d.foo in C"
C1().caller(D()) // print "d.foo in C1" -- distribute receiver virtual parsing
C().caller(D1()) // output "d.foo in C" -- extend receiver static parsing
}
Copy the code
Example execution output is as follows:
D.foo in C
D.foo in C1
D.foo in C
Copy the code
A member in an associated object is equivalent to a static member in Java. Its life cycle is always with the class. Variables and functions can be defined inside the associated object, which can be referred to directly by the class name.
For associated object extension functions, there are two forms, one is to extend inside the class, one is to extend outside the class, the two forms of extension functions do not affect each other (even the name can be the same), even if the name is the same, they are completely different functions, and have the following characteristics:
- (1) The adjoint object function of the in-class extension and the adjoint object of the out-of-class extension can have the same name. They are two independent functions and do not affect each other.
- (2) When the adjoint object function of the in-class extension has the same name as the adjoint object of the out-class extension, other functions in the class preferentially refer to the adjoint object function of the in-class extension, that is, for other member functions in the class, the in-class extension masks the out-class extension;
- (3) The adjoint object functions extended within a class can only be referenced by functions within the class, not by functions outside the class or functions within the adjoint object;
- (4) The adjoint object functions extended outside the class can be referenced by functions inside the adjoint object.
For example:
class MyClass {
companion object {
val myClassField1: Int = 1
var myClassField2 = "this is myClassField2"
fun companionFun1(a) {
println("this is 1st companion function.")
foo()
}
fun companionFun2(a) {
println("this is 2st companion function.")
companionFun1()
}
}
fun MyClass.Companion.foo(a) {
println("Extension function accompanying object (internal)")}fun test2(a) {
MyClass.foo()
}
init {
test2()
}
}
val MyClass.Companion.no: Int
get() = 10
fun MyClass.Companion.foo(a) {
println("Foo comes with an extension function outside the object")}fun main(args: Array<String>) {
println("no:${MyClass.no}")
println("field1:${MyClass.myClassField1}")
println("field2:${MyClass.myClassField2}")
MyClass.foo()
MyClass.companionFun2()
}
Copy the code
Running results:
no:10
field1:1
field2:this isMyClassField2 foo comes with the object external extension functionthis is 2st companion function.
this is 1st companionFunction.foo comes with an extension function outside the objectCopy the code
Kotlin data class and seal class
Data classes
Kotlin can create a class that contains only data with the keyword data:
data class User(val name: String, val age: Int)
Copy the code
The compiler automatically extracts the following functions from the main constructor based on all declared attributes:
equals()
/hashCode()
toString()
Format such as"User(name=John, age=42)"
componentN() functions
Corresponding to attributes, in the order they are declaredcopy()
function
These functions are no longer generated if they are already explicitly defined in a class or inherit from a superclass.
In order for the generated code to be consistent and meaningful, the data class needs to meet the following criteria:
- The main constructor takes at least one argument.
- All primary constructors must have arguments identified as
val
orvar
; - Data classes cannot be declared as
abstract
.open
.sealed
orinner
; - Data classes cannot inherit from other classes (but can implement interfaces).
copy
Copy using the copy() function, we can copy objects and modify some properties. For the User class above, the implementation would look like this:
fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
Copy the code
The instance
Copy the User data class using the copy class and modify the age property:
data class User(val name: String, val age: Int)
fun main(args: Array<String>) {
val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)
println(jack)
println(olderJack)
}
Copy the code
The output is:
User(name=Jack, age=1)
User(name=Jack, age=2)
Copy the code
Data classes and deconstruction declarations
Component functions allow data classes to be used in destructuring declarations:
val jane = User("Jane".35)
val (name, age) = jane
println("$name.$age years of age") // prints "Jane, 35 years of age"
Copy the code
Standard data class
The standard library provides Pair and Triple. In most cases, naming data classes is a better design choice because the code is more readable and provides meaningful names and attributes.
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.
Declare a sealed class, using the sealed modifier. The sealed class can have subclasses, but all subclasses must be embedded within the sealed class.
Sealed cannot modify interface,abstract class(will report warning, but will not compile errors)
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
fun eval(expr: Expr): Double = when (expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
}
Copy the code
The key benefit of using a sealed class is that when you use a WHEN expression, you don’t need to add an else clause to the statement if you can verify that the statement covers all cases.
fun eval(expr: Expr): Double = when(expr) {
is Expr.Const -> expr.number
is Expr.Sum -> eval(expr.e1) + eval(expr.e2)
Expr.NotANumber -> Double.NaN
// The 'else' clause is no longer needed because we have covered all cases
}
Copy the code
notes
For example, if we have a View in Android and we want to use the when statement to do two things on the view: show and hide, we can do this:
sealed class UiOp {
object Show: UiOp()
object Hide: UiOp()
}
fun execute(view: View, op: UiOp) = when (op) {
UiOp.Show -> view.visibility = View.VISIBLE
UiOp.Hide -> view.visibility = View.GONE
}
Copy the code
Function actually can use enumerated above, but if we now want to add two operations: vertical and horizontal translation translation, and will carry some data, such as translation how much distance, animation data types in the process of translation, with the enumeration apparently is not so good, done, then the advantage of seal type can play, such as:
sealed class UiOp {
object Show: UiOp()
object Hide: UiOp()
class TranslateX(val px: Float): UiOp()
class TranslateY(val px: Float): UiOp()
}
fun execute(view: View, op: UiOp) = when (op) {
UiOp.Show -> view.visibility = View.VISIBLE
UiOp.Hide -> view.visibility = View.GONE
is UiOp.TranslateX -> view.translationX = op.px // The when branch not only tells the view to move horizontally, but also how far it needs to move, something that traditional Java ideas like enumerations can't easily implement
is UiOp.TranslateY -> view.translationY = op.px
}
Copy the code
In the code above, TranslateX is a class that can carry more than one message. It can also tell the view how many pixels it needs to translate horizontally, and it can even tell the type of animation it needs to translate.
In addition, if the branch of the WHEN statement does not need to carry information other than “show or hide the view” (that is, only the branch of the WHEN statement needs to carry no additional data), the singleton can be created using the object keyword, and the WHEN clause does not need the IS keyword. Subclasses of sealed classes are defined only when additional information is needed, and with sealed classes there is no need to use the else clause. Whenever we add another subclass or singleton to a sealed class, the compiler gives us a hint in the WHEN statement, so we can catch the error at compile time. This is something that switch-case statements and enumerations didn’t do in the past.
Finally, we can even wrap this set of operations into a function that we can call later, as follows:
// Encapsulate a list of UI actions
class Ui(val uiOps: List = emptyList()) {
operator fun plus(uiOp: UiOp) = Ui(uiOps + uiOp)
}
// Define a set of operations
val ui = Ui() +
UiOp.Show +
UiOp.TranslateX(20f) +
UiOp.TranslateY(40f) +
UiOp.Hide
// Define the function to call
fun run(view: View, ui: Ui) {
ui.uiOps.forEach { execute(view, it) }
}
run(view, ui) // Final call
Copy the code
For example, if we have a View in Android and we want to use the when statement to do two things on the view: show and hide, we can do this:
sealed class UiOp {
object Show: UiOp()
object Hide: UiOp()
}
fun execute(view: View, op: UiOp) = when (op) {
UiOp.Show -> view.visibility = View.VISIBLE
UiOp.Hide -> view.visibility = View.GONE
}
Copy the code
Function actually can use enumerated above, but if we now want to add two operations: vertical and horizontal translation translation, and will carry some data, such as translation how much distance, animation data types in the process of translation, with the enumeration apparently is not so good, done, then the advantage of seal type can play, such as:
sealed class UiOp {
object Show: UiOp()
object Hide: UiOp()
class TranslateX(val px: Float): UiOp()
class TranslateY(val px: Float): UiOp()
}
fun execute(view: View, op: UiOp) = when (op) {
UiOp.Show -> view.visibility = View.VISIBLE
UiOp.Hide -> view.visibility = View.GONE
is UiOp.TranslateX -> view.translationX = op.px // The when branch not only tells the view to move horizontally, but also how far it needs to move, something that traditional Java ideas like enumerations can't easily implement
is UiOp.TranslateY -> view.translationY = op.px
}
Copy the code
In the code above, TranslateX is a class that can carry more than one message. It can also tell the view how many pixels it needs to translate horizontally, and it can even tell the type of animation it needs to translate.
In addition, if the branch of the WHEN statement does not need to carry information other than “show or hide the view” (that is, only the branch of the WHEN statement needs to carry no additional data), the singleton can be created using the object keyword, and the WHEN clause does not need the IS keyword. Subclasses of sealed classes are defined only when additional information is needed, and with sealed classes there is no need to use the else clause. Whenever we add another subclass or singleton to a sealed class, the compiler gives us a hint in the WHEN statement, so we can catch the error at compile time. This is something that switch-case statements and enumerations didn’t do in the past.
Finally, we can even wrap this set of operations into a function that we can call later, as follows:
// Encapsulate a list of UI actions
class Ui(val uiOps: List = emptyList()) {
operator fun plus(uiOp: UiOp) = Ui(uiOps + uiOp)
}
// Define a set of operations
val ui = Ui() +
UiOp.Show +
UiOp.TranslateX(20f) +
UiOp.TranslateY(40f) +
UiOp.Hide
// Define the function to call
fun run(view: View, ui: Ui) {
ui.uiOps.forEach { execute(view, it) }
}
run(view, ui) // Final call
Copy the code
Kotlin generic
Generics, or “parameterized types”, parameterize types and can be used on classes, interfaces, and methods.
Like Java, Kotlin provides generics to ensure type safety and eliminate the hassle of type coercion.
Declare a generic class:
class Box<T>(t: T) {
var value = t
}
Copy the code
To create an instance of a class we need to specify a type parameter:
Val box: box <Int> = box <Int>(1) // Or val box = box (1) // The compiler does type inference, 1 is Int, so the compiler knows we are talking about box <Int>.Copy the code
The following example passes integer data and a string to the generic class Box:
class Box<T>(t : T) {
var value = t
}
fun main(args: Array<String>) {
var boxInt = Box<Int>(10)
var boxString = Box<String>("Breeze")
println(boxInt.value)
println(boxString.value)
}
Copy the code
The output is:
10
Breeze
Copy the code
Define generic type variables that specify type parameters completely, or omit type parameters if the compiler can automatically assume them.
Kotlin generic functions are declared in the same way as Java, with type arguments placed before the function name:
Fun <T> boxIn(value: T) = Box(value) val box4 = boxIn<Int>(1) val box5 = boxIn(1Copy the code
When calling a generic function, you can omit the generic parameter if you can infer the type parameter.
The following example creates the generic function doPrintln, which does what it needs to do based on the type passed in:
fun main(args: Array<String>) {val age = 23 val name = "Breeze" val bool = true doPrintln(age) // doPrintln(name) // Character String DoPrintln (bool)} fun <T> doPrintln(content: T) {when (content) {is Int -> println(" integer number $content") is String -> println(" String converts to uppercase: ${content.toupperCase ()}") else -> println("T is not an integer, is not a string ")}}Copy the code
The output is:
Integer number 23 string conversion to uppercase: Breeze T is not an integer and is not a stringCopy the code
Generic constraint
We can use generic constraints to specify the types allowed for a given parameter.
Used in Kotlin: To constrain the type upper limit of a generic type.
The most common constraint is upper bound:
Fun <T: Comparable<T>> sort(list: list <T>) {//... }Copy the code
The Comparable subtype can be used instead of T. Such as:
Sort (listOf(1, 2, 3)) // OK. Type sort(listOf(HashMap<Int, String>())) HashMap<Int, String> is not a subtype of Comparable<HashMap<Int, String>>Copy the code
The default upper bound is Any? .
For multiple upper bound constraints, we can use the WHERE clause:
fun <T> copyWhenGreater(list: List<T>, threshold: T): List<String>
where T : CharSequence,
T : Comparable<T> {
return list.filter { it > threshold }.map { it.toString() }
}
Copy the code
Type variable
There is no wildcard type in Kotlin. There are two other things: declaration-site variance and type projections.
Declaration type changes
Type variations at declarations use covariant annotation modifiers: in, out, consumer in, producer out.
Using out makes a type parameter covariant. The covariant type parameter can only be used as output, as return value types but not as input types:
Class Breeze<out A>(val A: A) {fun foo(): A {return A}} fun main(args: Array<String>) {var strCo: Breeze<String> = Breeze("a") var anyCo: Breeze<Any> = Breeze<Any>("b") anyCo = strCo println(anyco.foo ())Copy the code
In inverts a type parameter that can only be used as input, as the type of the input parameter but not as the type of the return value:
Class Breeze<in A>(A: A) {fun foo(A: A) {}} fun main(args: Array<String>) { var strDCo = Breeze("a") var anyDCo = Breeze<Any>("b") strDCo = anyDCo }Copy the code
The asterisk projection
Sometimes, you might want to indicate that you don’t know anything about a type parameter, but still want to be able to use it safely. By “safe use” I mean defining a type projection of a generic type that requires all entity instances of the generic type to be subtypes of the projection.
Kotlin provides a syntax for this problem called star-projection:
- If the type is defined as Foo, where T is a covariant type argument, the upper bound is TUpper, and Foo<> is equivalent to Foo. It says that when T is unknown, you can safely get from Foo<> reads a value of type TUpper.
- Suppose the type is defined as Foo, where T is a reverse-covariant type parameter, and Foo<> is equivalent to Foo. It says that when T is unknown, you cannot safely call Foo<> write anything.
- If the type is defined as Foo, where T is a covariant type argument and the upper bound is TUpper, Foo<*> is equivalent to Foo for reading values and Foo for writing values.
If more than one type parameter exists in a generic type, each type parameter can be projected separately. For example, if the type is defined as interface Function<in T, out U>, the following asterisk projections can occur:
- Function<*, String> = Function
- Function
,>
= Function
,>
;
- Function<. > , 代表 Function<in Nothing, out Any?> .
Note: Asterisk casts are very similar to Java raw types, but can be used safely
And the thing about asterisk projection is that * refers to all types, the same thing as Any, right?
It is easy to understand that…
class A<T>(val t: T, val t2 : T, val t3 : T) class Apple(var name : String) fun main(args: Array<String>) {// Use class val a1: A<*> = A(12, "String", Apple(" Apple ")) val a2: A<Any? > = A(12, "String", Apple(" Apple ")) // the same as a1 val Apple = a1. T3 // The argument type is Any println(Apple) val apple2 = Apple as Apple // strong conversion to Apple class Println (apple2.name) // Use array val l:ArrayList<*> = arrayListOf("String",1,1.2f,Apple(" Apple ")) for (item in l){println(item) }}Copy the code
Kotlin enumeration class
The most basic use of enumeration classes is to implement a type-safe enumeration.
Enumeration constants are separated by commas and each enumeration constant is an object.
enum class Color{
RED,BLACK,BLUE,GREEN,WHITE
}
Copy the code
Enumeration initialization
Each enumeration is an instance of an enumeration class that can be initialized:
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF)
}
Copy the code
The default name is the enumeration character name and the value starts at 0. If you need to specify a value, you can use its constructor:
enum class Shape(value:Int){
ovel(100),
rectangle(200)
}
Copy the code
Enumerations also support the ability to declare their own anonymous classes and corresponding methods, as well as methods that override base classes. Such as:
enum class ProtocolState {
WAITING {
override fun signal() = TALKING
},
TALKING {
override fun signal() = WAITING
};
abstract fun signal(): ProtocolState
}
Copy the code
If an enumeration class defines any members, separate the enumeration constant definitions in the member definition with a semicolon
Using enumerated constants
Enumeration classes in Kotlin have synthetic methods that allow you to iterate over defined enumeration constants and get enumeration constants by their names.
Enumclass.valueof (value: String): EnumClass // If name is the enumeration value, IllegalArgumentException EnumClass.values() will be thrown: Array<EnumClass> // Returns an enumeration value in the form of an ArrayCopy the code
To get enumeration information:
Val name: String // Gets the enumeration name val ordinal: Int // Gets the order in which the enumeration values are defined in all enumeration arraysCopy the code
The instance
enum class Color{
RED,BLACK,BLUE,GREEN,WHITE
}
fun main(args: Array<String>) {
var color:Color=Color.BLUE
println(Color.values())
println(Color.valueOf("RED"))
println(color.name)
println(color.ordinal)
}
Copy the code
Since Kotlin 1.1, constants in an enumerated class can be accessed generically using the enumValues
() and enumValueOf
() functions:
enum class RGB { RED, GREEN, BLUE } inline fun <reified T : Enum<T>> printAllValues() { print(enumValues<T>().joinToString { it.name }) } fun main(args: Array<String>) {printAllValues<RGB>() // RED, GREEN, BLUE}Copy the code
Kotlin object expression and object declaration
Kotlin uses object expressions and object declarations to create an object that makes a slight change to a class without declaring a new subclass.
Object expression
An object that implements an anonymous inner class through an object expression is used in method arguments:
window.addMouseListener(object : MouseAdapter() { override fun mouseClicked(e: MouseEvent) { // ... } override fun mouseEntered(e: MouseEvent) { // ... }})Copy the code
Objects can inherit from a base class, or implement other interfaces:
Open class A(x: Int) {public open val y: Int = x} interface B {... } val ab: A = object : A(1), B { override val y = 15 }Copy the code
If the supertype has a constructor, arguments must be passed to it. Multiple supertypes and interfaces can be separated by commas.
Object expressions can override the class definition to get an object directly:
Fun main(args: Array<String>) {val site = object {var name: String = "" var url: String = "www.Breeze.com" } println(site.name) println(site.url) }Copy the code
Note that anonymous objects can be used as types declared only in local and private scopes. If you use an anonymous object as the return type of a public function or as the type of a public property, the actual type of the function or property will be the supertype declared by the anonymous object, or Any if you don’t declare Any. Members added to anonymous objects will not be accessible.
Class C {// Private function, so its return type is anonymous object type private fun foo() = object {val x: String = "x"} // Public function, so its return type is Any fun publicFoo() = object {val x: String = "x"} fun bar() {val x1 = foo().x // no problem val x2 = publicFoo().Copy the code
Other variables in scope can be easily accessed in object representation:
fun countClicks(window: JComponent) { var clickCount = 0 var enterCount = 0 window.addMouseListener(object : MouseAdapter() { override fun mouseClicked(e: MouseEvent) { clickCount++ } override fun mouseEntered(e: MouseEvent) {enterCount++}}) //... }Copy the code
Object statement
Kotlin uses the object keyword to declare an object.
In Kotlin we can easily obtain a singleton through object declarations.
Object DataProviderManager {fun registerDataProvider(provider: DataProvider) {//... } val allDataProviders: Collection<DataProvider> get() = //... }Copy the code
To reference this object, we simply use its name:
DataProviderManager. RegisterDataProvider (...)Copy the code
Of course you can define a variable to get the object, but when you define two different variables to get the object, you can’t get two different variables. In other words, in this way, we get a singleton.
var data1 = DataProviderManager
var data2 = DataProviderManager
data1.name = "test"
print("data1 name = ${data2.name}")
Copy the code
The instance
In the following example, both objects output the same URL:
Object Site {var url:String = "" val name: String = ""} fun main(args: Array<String>) { var s1 = Site var s2 = Site s1.url = "www.Breeze.com" println(s1.url) println(s2.url) }Copy the code
The output is:
www.Breeze.com
www.Breeze.com
Copy the code
Objects can have supertypes:
Object DefaultListener: MouseAdapter() {override fun mouseClicked(e: MouseEvent) { } Override fun mouseEntered(e: MouseEvent) {//... }}Copy the code
Unlike an object expression, when an object is declared inside another class, the object cannot be accessed by an instance of an external class, but only by the class name. Likewise, the object cannot be accessed directly by the methods and variables of the external class.
Class Site {var name = "www.Breeze.com" fun showName(){print{"desk legs $name"} // Error, cannot access methods and variables of external class}}} Fun main(args: Array<String>) {var site = site () site.desktop. url // error, object site.desktop. url cannot be accessed through an instance of an external class // Correct}Copy the code
Associated object
An object declaration within a class can be marked with the Companion keyword, so that it is associated with an external class through which we can directly access the object’s internal elements.
class MyClass { companion object Factory { fun create(): MyClass = MyClass()}} val instance = myclass.create () // Access the inner element of the objectCopy the code
We can omit the object name of this object and use Companion instead of the object name to declare:
class MyClass {
companion object {
}
}
val x = MyClass.Companion
Copy the code
Note: Only one internal object can be declared within a class, and the keyword Companion can be used only once.
The members of the companion object look like static members of other languages, but at run time they are still instance members of the real object. For example, you can also implement interfaces:
interface Factory<T> {
fun create(): T
}
class MyClass {
companion object : Factory<MyClass> {
override fun create(): MyClass = MyClass()
}
}
Copy the code
Semantic differences between object expressions and object declarations
There is an important semantic difference between object expressions and object declarations:
- Object expressions are executed immediately where they are used
- Object declarations are lazily initialized when they are first accessed
- The initialization of the associated object matches the semantics of the Java static initializer when the corresponding class is loaded (parsed)
Kotlin commissioned
Delegation pattern is a basic skill in software design pattern. In the delegate pattern, two objects participate in processing the same request, and the receiving object delegates the request to the other object.
Kotlin directly supports the delegation model, which is more elegant and concise. Kotlin implements delegation through the keyword BY.
Commissioned by class
Class delegates are methods defined in one class that are actually implemented by calling methods of objects of another class.
In the following example, the Derived class Derived inherits all the methods of the interface Base and delegates an object from the passed Base class to execute those methods.
Interface Base {fun print()} class BaseImpl(val x: Int) : Base {override fun print() {print(x)}} class Derived(b: Base) : Base by fun main(args: Array<String>) {val b = BaseImpl(10) Derived(b).print()Copy the code
In the Derived declaration, the BY clause says that b is stored inside an object instance of Derived, and that the compiler will generate all methods inherited from the Base interface and forward the calls to B.
Attribute to entrust
Attribute delegation means that the value of an attribute of a class is not directly defined in the class, but entrusted to a proxy class, so as to achieve unified management of the attributes of the class.
Attribute delegate syntax format:
Var < attribute name >: < type > by < expression >Copy the code
- Var /val: property type (variable/read-only)
- Attribute name: Attribute name
- Type: The data type of the property
- Expression: delegate proxy class
The expression after the by keyword is the delegate, and the property’s get() method (and set() method) will be delegated to the object’s getValue() and setValue() methods. The property delegate does not have to implement any interface, but it must provide the getValue() function (and, for the var attribute, the setValue() function).
Define a delegated class
This class needs to contain the getValue() and setValue() methods, and the arguments thisRef is the object of the delegate class and prop is the object of the delegate property.
Import kotlin.reflect.KProperty class Example {var p: String by Delegate()} class Delegate {operator fun getValue(thisRef: Any? , property: KProperty<*>): String {return "$thisRef, where ${property.name} attribute "} operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {println("$thisRef ${property.name} attribute = $value")}} fun main(args: Array<String>) {val e = Example() println(e.p) Call getValue() function e.p = "Breeze" // Call setValue() function println(e.p)}Copy the code
The output is:
Example@433c675d, where the p attribute is delegated to Example@433c675d, is assigned to Breeze Example@433c675d, where the p attribute is delegatedCopy the code
The standard delegation
Kotlin’s library already has a number of factory methods built into it to delegate attributes.
The delay attribute Lazy
Lazy () is a function that takes a Lambda expression as an argument and returns an instance of lazy that can be used as a delegate to implement the delay attribute: The first call to get() executes the LAMda expression passed to lazy() and records the result, and subsequent calls to get() simply return the result of the record.
val lazyValue: String by lazy { println("computed!" ) // First call output, second call does not execute "Hello"} fun main(args: Array<String>) {println(lazyValue) {println(lazyValue)}Copy the code
Execution output:
computed!
Hello
Hello
Copy the code
Observable property Observable
Observables can be used to implement the observer pattern.
The Delegates.Observable () function receives two parameters: the first is the initialization value and the second is the handler for the attribute value change event.
A handler that executes the event after an attribute is assigned and takes three parameters: the assigned attribute, the old value, and the new value:
import kotlin.properties.Delegates class User { var name: {if ($old -> $new)}} Fun main(args: $Delegates: {if ($old -> $new)}}... Array<String>) {val user = user () user.name = "first assignment" user.name = "second assignment"}Copy the code
Execution output:
Old value: initial value -> New value: first assignment Old value: first assignment -> new value: second assignmentCopy the code
Store attributes in a map
A common use case is to store property values in a map. This is often seen in applications like parsing JSON or doing other “dynamic” things. In this case, you can implement delegate properties using the mapping instance itself as the delegate.
class Site(val map: Map<String, Any? >) { val name: String by map val url: String by map } fun main(args: Array<String>) {// The constructor takes a mapping parameter val site = site (mapOf("name" to" Println (site.name) println(site.url)}Copy the code
Execution output:
Rookie tutorial www.Breeze.comCopy the code
If you use the var attribute, you need to change the Map to a MutableMap:
class Site(val map: MutableMap<String, Any? >) { val name: String by map val url: String by map } fun main(args: Array<String>) { var map:MutableMap<String, Any? Word-wrap: break-word! Important; "> = mutableMapOf("name" to" "url" to "www.Breeze.com" ) val site = Site(map) println(site.name) println(site.url) println("--------------") map.put("name", "Google") map.put("url", "www.google.com") println(site.name) println(site.url) }Copy the code
Execution output:
Novice tutorial www.Breeze.com -- -- -- -- -- -- -- -- -- -- -- -- -- -- Google www.google.comCopy the code
Not Null
NotNull is suitable for situations where attribute values cannot be determined during initialization.
class Foo {
var notNullBar: String by Delegates.notNull<String>()
}
foo.notNullBar = "bar"
println(foo.notNullBar)
Copy the code
Note that an exception will be thrown if the property is accessed before the assignment.
Local delegate property
You can declare local variables as delegate properties. For example, you can lazily initialize a local variable:
fun example(computeFoo: () -> Foo) { val memoizedFoo by lazy(computeFoo) if (someCondition && memoizedFoo.isValid()) { memoizedFoo.doSomething() }}Copy the code
The memoizedFoo variable is only evaluated on the first access. If someCondition fails, this variable is not evaluated at all.
Attribute delegate requirements
For a read-only attribute (that is, the val attribute), its delegate must provide a function called getValue(). This function takes the following arguments:
- ThisRef – must be the same as the attribute owner type (for extended attributes – the extended type) or its supertype
- Property — Must be type KProperty<*> or its supertype
This function must return the same type (or subtype) as the property.
For a mutable property (that is, the var property), in addition to the getValue() function, its delegate must also provide a function called setValue() that takes the following arguments:
Property — must be of type KProperty<*> or its supertype New value — must be of the same type as the property or its supertype.
Translation rules
Behind the implementation of each delegate property, the Kotlin compiler generates and delegates a secondary property to it. For example, for the property prop, generate the hidden property prop$delegate, and the accessor’s code simply delegates to this additional property:
class C { var prop: Class C {private val prop$delegate = MyDelegate() var prop: class C {private val prop$delegate = MyDelegate() var prop: Type get() = prop$delegate.getValue(this, this::prop) set(value: Type) = prop$delegate.setValue(this, this::prop, value) }Copy the code
The Kotlin compiler provides all the necessary information about prop in parameters: The first parameter, this, refers to an instance of the external class C and this:: Prop is a reflection object of type KProperty that describes the prop itself.
Provide commissioned
By defining the provideDelegate operator, you extend the logic for creating a property implementation to delegate. If the object used to the right of BY defines provideDelegate as a member or extension function, that function is called to create the property delegate instance.
One possible use scenario for provideDelegate is to check for consistency when a property is created, not just in its getter or setter.
For example, if you want to check the property name before binding, you could write:
class ResourceLoader<T>(id: ResourceID<T>) { operator fun provideDelegate( thisRef: MyUI, prop: KProperty<*> ): ReadOnlyProperty<MyUI, T> {checkProperty(thisRef, prop.name)} private fun checkProperty(thisRef: MyUI, name: String) {... Fun <T> bindResource(id: ResourceID<T>): ResourceLoader<T> {...... } class MyUI { val image by bindResource(ResourceID.image_id) val text by bindResource(ResourceID.text_id) }Copy the code
The provideDelegate argument is the same as getValue:
- ThisRef – must be the same as the attribute owner type (for extended attributes – the extended type) or its supertype
- Property — Must be type KProperty<*> or its supertype.
During the creation of the MyUI instance, the provideDelegate method is called for each property and the necessary validation is performed immediately.
Without this ability to intercept the binding between a property and its delegate, you would have to explicitly pass the property name to achieve the same functionality, which is not very convenient:
Class MyUI {val image by bindResource(resourceid.image_id, "image") val text by bindResource(ResourceID.text_id, "text") } fun <T> MyUI.bindResource( id: ResourceID<T>, propertyName: String): ReadOnlyProperty<MyUI, T> {checkProperty(this, propertyName) // Create delegate}Copy the code
In the generated code, the provideDelegate method is called to initialize the auxiliary Prop $delegate property. Compare the code generated for the property declaration val Prop: Type by MyDelegate() with the code generated above (when the provideDelegate method does not exist) :
class C { var prop: Type by MyDelegate()} // This code is generated by the compiler when the "provideDelegate" function is available: Class C {// Call "provideDelegate" to create additional "delegate" properties private val prop$delegate = MyDelegate().provideDelegate(this, this::prop) val prop: Type get() = prop$delegate.getValue(this, this::prop) }Copy the code
Note that the provideDelegate method only affects the creation of auxiliary properties, not the code generated for getters or setters.