convention

If we define a special function of plus, then we can use the + operator on instances of that class, and that’s the convention

Kotlin prescribs many of these rules, but the programmer doesn’t need to know them, just rely on IDEA’s intelligent cues

Overloading arithmetic operators

Overloading binary arithmetic operations

Defines a member of the plus operator overload function

class Point(val x: Int.val y: Int) {
   operator fun plus(other: Point): Point {
      return Point(this.x + other.x, this.y + other.y)
   }
   
   override fun toString(a): String {
      return "Point{x = $x, y = $y}"}}fun main(a) {
   val point2 = Point(1.2) + Point(3.4)
   println(point2)
}
Copy the code
  1. You can see that the modifier operator is used to define an operator-overloaded function

  2. The plus function corresponds to the + of the operator according to the convention concept

  3. The corresponding overloaded functions include:

  1. How the operator overload function is written does not affect the precedence of the operator

Does not result in * / having a lower priority than + –

  1. Operator overloading of extension functions can be defined
operator fun Point.plus(other: Point): Point {
   return Point(this.x + other.x, this.y + other.y)
}
Copy the code

Kotlin can override operators with either member functions or extension functions

  1. The types of the left and right sides of the operator can be different
operator fun Point.times(d: Double): Point {
    return Point((this.x * d).toInt(), (this.y * d).toInt())
}

println(p * 1.5)
Copy the code

Note: The order of the overloaded functions can not be reversed. The Point type defined above is left and the Double type is right, so (1.5 * p) is not allowed

private operator fun Double.times(point: Point): Point {
   return Point((this * point.x).toInt(), (this * point.y).toInt())
}
Copy the code
  1. To define overloaded operator extension functions, you can do this:

If I want to write a variable * 3 with the value ‘a’ to get a String of ‘aaa’, I would write val STR: String = ‘a’ * 3

Then we can create extension functions

private operator fun Char.times(count: Int): String {
   TODO("Not yet implemented")}Copy the code

Now add the functions and return values we need

private operator fun Char.times(count: Int): String {
   return toString().repeat(count)
}
Copy the code

Note, however, that the generated operator overload extension function defaults to private and you can remove the private visibility modifier if you don’t need it

The receiver is a Char, the argument is an Int, but the return value is a String, so Char + Int = String looks odd

  1. Kotlin does not define bitwise operators, so none of the bitwise operators can be overloaded, but Kotlin provides many infix calling functions
  • SHL moves left with sign
  • SHR moves right with sign
  • Ushr unsigned shift to the right
  • And the bitwise and
  • The or or by location
  • Xor (exclusive or
  • Inv is reversed by bit
println(0x0F and 0x0F)
Copy the code

Overloaded compound operators

The += and -= operators are called compound operators in Kotlin

  1. Compound operator+ =In the case of operator overloading of mutable objects, it defines new reference objects that can be reused directly from previously written pairs+Operator to override the function
var point = Point(1.1)
point += Point(2.3) Point = point + point (2, 3) = point + point (2, 3)
Copy the code
  1. use+ =When the compound operator changes the contents of the container and does not reassign new references, it is necessary to define operator overload functions
val arrayList = arrayListOf(1.2.3.4.5)
arrayList += 6
Copy the code

The += operator overload defines a function called plusAssign

public inline operator fun <T> MutableCollection<in T>.plusAssign(element: T) {
    this.add(element)
}
Copy the code
  1. When the same class is writtenplusplusAssignBoth operators overload functions. In theory, both functions should be called, so they should not exist at the same time. If they do exist at the same time, then you can change the receiver tovalType, like thisplusAssignIt doesn’t work becausevalReassignment is not supported

Override the unary operator

Follow the previous tips, first write unary operator

val point = Point(1.2)
println(-point)
Copy the code

You can then use the IDE to generate operators to override extension functions (you can also select member functions)

private operator fun Point.unaryMinus(a): Point {
   return Point(-this.x, -this.y)
}
Copy the code

  1. Unary operators take no arguments

  2. Function overloading of the increment and decrement operators

operator fun Point.inc(a): Point {
    return Point(this.x++, this.y++)
}
Copy the code

The increment operator has operators such as I ++ and ++ I, both of which use the same extension function as the overloaded operator in Kotlin

var decimal = BigDecimal(0)
decimal++
println(decimal)
++decimal
println(decimal)
Copy the code
public inline operator fun BigDecimal.inc(a): BigDecimal = this.add(BigDecimal.ONE)

public inline operator fun BigDecimal.inc(a): BigDecimal = this.add(BigDecimal.ONE)
Copy the code

It was thought that ++ I and I ++ would become the same when the operator overloads the function

0
2
Copy the code

The decompilation turns out to be done by the powerful Kotlin compiler

I++ decompilation will look like this:

// i++ will look like this:
int i = 0;
System.out.println(i);
i = i + 1;
// ++ I would look like this:
int i = 0;
i = i + 1;
System.out.println(i);
Copy the code

See? One is print and then +1, the other is print and then +1, kotlin compiler YYds

Overload the comparison operator

= = = = =! = > < >= <= etc. These are comparison operators

Equal operator:equals

  1. By kotlin’s agreement,= =equalsThe corresponding
  2. = =! =This can be compared to null, for examplea.equals(b)A will check for null before callingequalsjudge

  1. = = =Identity operator

(1) The identity operator, like the Java == operator, compares addresses, called references

(2) The identity operator === cannot be overloaded

  1. = =Operator does not support extended function operator overloading

The convention for == is equals, which already exists in Any. An extension function that defines operator overloading will never be called because the Any member function takes precedence over the extension function

  1. If you wrote= =If the extension function is overloaded with the operator of! =The kotlin compiler will help you
override fun equals(obj: Any?).: Boolean {
   // Compare references (addresses)
   if (obj === this) return true
   // Compare types
   if (obj !is Point) return false
   return (this.x == obj.x) && (this.y == obj.y)
}
Copy the code
val point1 = Point(1.2)
val point2 = Point(1.2)
if (point1 == point2) {
   println(true) // true
} else {
   println(false)}Copy the code

If you look closely, equals is not an overridden function. It is an overridden function, so there is no way to override equals

Sort operator: compareTo

The sort operator can be implemented in two ways

  1. implementationComparable

  1. Operator overloads functions

We’ll see the compareValuesBy function, which takes two comparison objects, selects the fields of the comparison object, and compares them in the order in which the arguments are passed. If the Person::firstName comparison results (if not equal), the Person::lastName comparison is not followed

Collections and conventions (operator overloading of collections)

[]Operator overloads manipulate objects with the help of GET /set

In Kotlin we can do this:

Val value = map[key]

Mutable collection writes: mutableMap[key] = value

All of these operations are kotlin’s low-level operations. They are implemented using get and set functions. Kotlin changes the read to get(key) and the write to PUT (key). Value (a function like set)

Now how do we add similar operations to our custom classes?

Taking the previous Point class as an example, p[0] gets the x variable and P [1] gets the Y variable

Using our previous cleverness, we generated the following two functions using the IDE

private operator fun Point.set(index: Int, value: Int) {
   when(index) {
      1 -> this.x = value
      2 -> this.y = value
   }
}

private operator fun Point.get(index: Int): Int? {
   return when(index) {
      1 -> this.x
      2 -> this.y
      else -> null}}fun main(a) { 
   val point = Point(1.2)
   println(point[0])
   println(point[1])
   
   point[0] = 10
   point[1] = 20
}
Copy the code

Index corresponds to the index of p[index], so we can use the convention rules to overload the function with the GET operator

In convention (Contains function)

private class Rectangle(val upperLeft: Point, val lowerRight: Point) {
   operator fun contains(point: Point): Boolean {
      return point.x in min(upperLeft.x, lowerRight.y) until max(lowerRight.x, upperLeft.x) &&
            point.y in min(upperLeft.y, lowerRight.y) until max(lowerRight.y, upperLeft.y)
   }
}

fun main(a) {
   val rectangle = Rectangle(Point(4.4), Point(0.0))
   val point = Point(1.1)
   println(point in rectangle)
}
Copy the code

RangTo agreedn.. n+1

val now = LocalDateTime.now()
valvacation = now.. now.plusDays(10)
println(now.plusWeeks(1) in vacation)
Copy the code

now.. Now. PlusDays (10) will be compiled to

ClosedRange vacation = RangesKt.rangeTo((Comparable)now, (Comparable)now.plusDays(10L));
Copy the code

Used in the for loopiteratorconventionin

fun main(a) {
   for (c in "abcd") {
      println(c)
   }
}
Copy the code

In source code:

public operator fun CharSequence.iterator(a): CharIterator = object : CharIterator() {
    private var index = 0

    public override fun nextChar(a): Char = get(index++)

    public override fun hasNext(a): Boolean = index < length
}
Copy the code

Deconstruct declarations and component functionscomponentN

To expand a compound value to initialize multiple variables is called a destruct declaration

But if you want to destruct a normal object, you need to add component functions,

The following figure shows that normal functions cannot use destruct declarations. You need to create member component functions or extend component functions. Of course, you can change the class to data Class Point

private operator fun Point.component1(a): Int = x
private operator fun Point.component2(a): Int = y

fun main(a) {
   val p = Point(10.20)
   val (x, y) = p
   println("x = $x, y = $y")}Copy the code

Our destruct declaration is to assign the value of the composition function to the component function

data class NameComponents(val name: String, val extension: String)

fun splitFileName(fullName: String): NameComponents {
   val split = fullName.split(".")
   return NameComponents(split[0], split[1])}fun main(a) {
   val (name, extension) = splitFileName("1.txt")
   println("name = $name, extension = $extension")}Copy the code

A function can return multiple values, but the destruct declaration is not infinite. It only allows parsing the first five fields of an object

Reuse property access logic: delegate events

Basic usage of delegate attributes (conventionsbygetValue/setValueFunction)

In the previous delegate class, we learned that the essence of a delegate is to borrow a chicken from an egg

The essence of class delegation is that the client inherits an interface, but the implementation of the interface function is delegated to another subclass object that also implements the interface, and calls the function as a class combination

class C : InterfaceB {
    override fun doSomething(a): Unit {
        // do something}}class A(val cObject: InterfaceB = C()) : InterfaceB by cObject {
    override fun doSomething(a): Unit {
        cObject.doSomething()
    }
}
Copy the code

The essence of attribute delegation in this section is that an attribute gives a get/set function to another object that also implements get/set(getValue/setValue)

class Foo {
    var p:Type by Delegate()
}
Copy the code

In the code above Deletgate() generates an object to initialize the P property during delegation, and the client needs to be defined by convention to be able to be delegated by

And here’s the deal:

class Delegate : ReadWriteProperty<Foo, String> {
   
   override fun getValue(thisRef: Foo, property: KProperty< * >): String {
      TODO("Not yet implemented")}override fun setValue(thisRef: Foo, property: KProperty<*>, value: String) {
      TODO("Not yet implemented")}}Copy the code

The convention states that the convention object needs to implement the ReadWriteProperty interface

Or the agreement goes something like this:

class Delegate {
   operator fun getValue(foo: Foo, property: KProperty< * >): String {
      TODO("Not yet implemented")}operator fun setValue(foo: Foo, property: KProperty<*>, s: String) {
      TODO("Not yet implemented")}}Copy the code

Two operators need to be defined to override the getValue/setValue functions

Either of these conventions is fine

Use delegate properties: lazy initialization andby lazy()

Use another property to implement lazy loading ▲

In the past, if we want to achieve lazy loading of attributes, we need to use the temporary nullable attribute. When we need to load this attribute for the first time, we need to determine whether the temporary attribute is null

class Person(val name: String) {
   private var _emails: List<String>? = null
   
   val email: List<String>
      get() {
         if (_emails == null) {
            _emails = loadEmail(this)}return _emails!!
      }
   
   private fun loadEmail(person: Person): List<String>? {
      return listOf("[email protected]"."[email protected]")}}Copy the code

This method is used more, do not need any concept, directly make a lazy loading attribute, and from the code to judge that our email attribute is completely dependent on _email so when translated into Java source must be only _email attribute, Email only has a get/set function (val, so only get)

Kotlin providedlazyFunction to implement lazy loading

class Person(val name: String) {
   val emails by lazy { loadEmail() }
   
   private fun loadEmail(a): List<String> {
      println("LoadEmail called")
      return listOf("[email protected]"."[email protected]")}}Copy the code
  1. Lazy is a library function that takes lambda () -> T and the return value of lazy is the return value of lambda,

  2. Lazy is thread-safe. Lazy can switch the thread lock you want, or turn it off entirely, as needed

  3. The lazy function finally returns an object with a getValue function

Lazy source code analysis

  • So let’s start here
val emails by lazy { loadEmail() }
Copy the code
  • usebyProperty, normally calledbyOf the following objectgetValue/setValueDelta function, depending on the situation,lazyThere should be implementationsgetValuefunction

Lazy {loadEmail()} This returns absolutely an object and should have either getValue or setValue functions

public actual fun <T> lazy(initializer: () -> T): Lazy<T> = SynchronizedLazyImpl(initializer)
Copy the code

It’s got a new SynchronizedLazyImpl class object

private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy<T>, Serializable {
    private var initializer: (() -> T)? = initializer
    @Volatile private var _value: Any? = UNINITIALIZED_VALUE
    private vallock = lock ? :this

    override val value: T
        get() { / / slightly}
}
Copy the code

The above is the core algorithm, to analyze the above code is also analyzed, but getValue such a function??

You can choose to install the extSee plug-in for IDEA and then view the extension functions

public inline operator fun <T> Lazy<T>.getValue(thisRef: Any? , property:KProperty< * >): T = value
Copy the code

Find that it calls the get function of value

Just analyze his core method now

override val value: T
    get() {
        val _v1 = _value
        if(_v1 ! == UNINITIALIZED_VALUE) {@Suppress("UNCHECKED_CAST")
            return _v1 as T // return _v1 assigns to the email variable
        }
        // add a thread lock. The lock here is actually this object
        return synchronized(lock) {
            val _v2 = _value
            if(_v2 ! == UNINITIALIZED_VALUE) {@Suppress("UNCHECKED_CAST") (_v2 as T) // return _v2 assigns to the email variable
            } else {
                // the return value of lambda is returned
                valtypedValue = initializer!! (a)// store in _value, wait for next judgment, if! UNINITIALIZED_VALUE returns the object directly
                _value = typedValue
                // Initialize lambda
                initializer = null
                // Returns the lambda return value object
                typedValue  // return typedValue assigns to the email variable}}}Copy the code

UNINITIALIZED_VALUE, initialized at the beginning of _value in the above code, is assigned to the return value of the lambda. == UNINITIALIZED_VALUE Checks whether the value has been assigned

The implementation of _value and value is exactly the same as the lazy implementation of ▲ in the previous section with another attribute

So when we start delegating, _value is initialized, but value is still empty (no, value doesn’t actually have that field)

Implementing delegate properties

Now, we learned that we can implement delay with another object, and we can implement delegate that way

class ObservableProperty(val propName: String, var propValue: Number, val supportChange: PropertyChangeSupport) {
   fun getValue(a): Number {
      return propValue
   }
   
   fun setValue(value: Number) {
      supportChange.firePropertyChange(propName, propValue, value)
      propValue = value
   }
   
}

class Person(_name: String, _age: Int, _scope: Double) {
   private val supportChange = PropertyChangeSupport(this)
   
   val name: String = _name
   private val __age = ObservableProperty("age", _age, supportChange)
   var age: Int
      get() = __age.getValue() as Int
      set(value) {
         __age.setValue(value)
      }
   private val __scope = ObservableProperty("scope", _scope, supportChange)
   var scope: Double
      get() = __scope.getValue() as Double
      set(value) {
         __scope.setValue(value)
      }
   
   fun addPropertyChangeEvent(listener: PropertyChangeListener) {
      supportChange.addPropertyChangeListener(listener)
   }
   
   fun removePropertyChangeEvent(listener: PropertyChangeListener) {
      supportChange.removePropertyChangeListener(listener)
   }
}
fun main(a) {
   val person = Person("zhazha".23.98798.0)
   person.addPropertyChangeEvent {
      PropertyChangeListener {
         println("field ${it.propertyName} changed from ${it.oldValue} to ${it.newValue}")
      }
   }
   person.age = 22
   person.scope = 1000000.0
}
Copy the code

The above example uses PropertyChangeSupport to monitor property changes. If the property value changes, it will be monitored.

class ObservableProperty(_propValue: Int, _supportChange: PropertyChangeSupport) : ReadWriteProperty<Person, Int> {
   var propValue: Int = _propValue
   val supportChange = _supportChange
   
   override fun getValue(thisRef: Person, property: KProperty< * >): Int {
      return propValue
   }
   
   override fun setValue(thisRef: Person, property: KProperty<*>, value: Int) {
      supportChange.firePropertyChange(property.name, propValue, value)
      propValue = value
   }
}

open class PropertyChangeAware {
   protected val supportChange = PropertyChangeSupport(this)
   
   fun addPropertyChangeEvent(listener: PropertyChangeListener) {
      supportChange.addPropertyChangeListener(listener)
   }
   
   fun removePropertyChangeEvent(listener: PropertyChangeListener) {
      supportChange.removePropertyChangeListener(listener)
   }
   
}

class Person(_name: String, _age: Int, _salary: Int) : PropertyChangeAware() {
   val name: String = _name
   var age: Int by ObservableProperty(_age, supportChange)
   var salary: Int by ObservableProperty(_salary, supportChange)
}

fun main(a) {
   val person = Person("zhazha".22.17000)
   person.addPropertyChangeEvent {
      PropertyChangeListener {
         println("field ${it.propertyName} changed ${it.oldValue} to ${it.newValue}")
      }
   }
   person.age = 23
   person.salary = 500000
}
Copy the code

Delegate (getValue/setValue) ¶ Delegate (getValue/setValue) ¶ Delegate (getValue/setValue) ¶ Delegate (getValue/setValue) ¶ Delegate (getValue/setValue) ¶

We can also use a built-in delegate class to do this, so we don’t have to write it ourselves.

open class PropertyChangeAware {
   protected val supportChange = PropertyChangeSupport(this)
   
   fun addPropertyChangeEvent(listener: PropertyChangeListener) {
      supportChange.addPropertyChangeListener(listener)
   }
   
   fun removePropertyChangeEvent(listener: PropertyChangeListener) {
      supportChange.removePropertyChangeListener(listener)
   }
}

class Person(_name: String, _age: Int, _salary: Int) : PropertyChangeAware() {
   val name: String = _name
   private val observer = { property: KProperty<*>, oldValue: Int, newValue: Int ->
      supportChange.firePropertyChange(property.name, oldValue, newValue)
   }
   var age: Int by Delegates.observable(_age, observer)
   var salary: Int by Delegates.observable(_salary, observer)
}

fun main(a) {
   val person = Person("zhazha".22.20000)
   person.addPropertyChangeEvent {
      PropertyChangeListener {
         println("field ${it.propertyName} changed ${it.oldValue} to ${it.newValue}")
      }
   }
   person.age = 23
   person.salary = 5000000
}
Copy the code

As far as we can tell, the right side of the by keyword can be a function call, another property, or any other expression that satisfies the delegate function

Change rules for attribute delegates

class C {
    var prop: Type by MyDelegate()
}
Copy the code

Where MyDelegate is going to generate a property

and use an object of type KProperty to represent the type of that object, which is called

The compiler generates code:

class C {
    private val <delegate> = MyDelegate()
    var prop: Type
        get() = <delegate>.getValue(this, <property>)
        set(value) = <delegate>.setValue(this, <property>, value)
}
Copy the code

Save attribute values in map

Delegate by to a Map object

class Person {
   private val _attributes = hashMapOf<String, String>()
   fun setAttributes(attrName: String, value: String) {
      _attributes[attrName] = value
   }
   
   // get() = _attributes["name"]!!
   val name: String by _attributes
   // get() = _attributes["company"]!!
   val company: String by _attributes
}

fun main(a) {
   val person02 = MapDemo.Person()
   val data = mapOf("name" to "Dmitry"."company" to "Jetbrain")
   for ((attrName, value) in data) {
      person02.setAttributes(attrName, value)
   }
   println(person02.name)
   println(person02.company)
}
Copy the code

The core code is here:

// get() = _attributes["name"]!!
val name: String by _attributes
// get() = _attributes["company"]!!
val company: String by _attributes
Copy the code

In plain English, the name of the variable is used as the key of the HashMap and the value is obtained