A constructor

A class in Kotlin can have a primary constructor and multiple sub-constructors. What is a column constructor?

Principal construction method

The main constructor is part of the class header: it follows the class name (along with optional type arguments).

class KotlinClass constructor(name: String) { / *... * / }
Copy the code

If the main constructor does not have any annotations or visibility modifiers, omit the constructor keyword

class KotlinClass(name: String) { / *... * / }
Copy the code

The main constructor cannot contain any code. Initialized code can be placed in initializer blocks prefixed with the init keyword.

Initialization sequence

During instance initialization, initialization blocks are executed in the order in which they appear in the class body, interleaved with property initializers:

class InitDemo(name: String) {
    Also (::println) * :: creates a member reference or class reference that passes println methods as arguments to also. Source code follow -> Kotlin extension */
    val firstProp = "First Prop: $name".also(::println)

    init {
        println("First initializer block that prints $name")}val secondProp = "Second Prop: ${name.length}".also(::println)

    init {
        println("Second initializer block that prints ${name.length}")}}Copy the code

The parameters of the main construct can be used in addition to the initializer block when declaring properties inside the class body.

A more concise way to declare attributes and initialize them from the main constructor:

// Constructor parameters are assigned as class attributes. KotlinClass2's name and score attributes are assigned when initialized
class KotlinClass2(val name: String, var score: Int) { / *... * / }
Copy the code

As with normal properties, properties declared in the main constructor can be mutable (var) or read-only (val).

Subconstruction method

We can also declare the subconstructor of a class inside the class body by using the constructor prefix:

class KotlinClass3 {
    var views: MutableList<View> = mutableListOf()

    constructor(view: View) {
        views.add(view)
    }
}
Copy the code

If a class has a primary constructor, each subconstructor needs to delegate to the primary constructor, either directly or indirectly through another subconstructor. Delegate to another constructor using the this keyword:

class KotlinClass3(val view: View) {
    var views: MutableList<View> = mutableListOf()

    constructor(view: View, index: Int) : this(view) {
        views.add(view)
    }
}
Copy the code

Because the code in the initialization block is actually part of the main constructor, the initialization code block is executed before the secondary constructor.

Inheritance and override

In Kotlin, all classes are final by default, and if you need to allow it to be inherited, you need to use the open declaration:

open class Animal(age: Int) {
    init {
        println(age)
    }
}
Copy the code

In Kotlin all classes have a common superclass, Any, which has three methods: equals(), hashCode(), and toString().

To inherit a class, place the superclass after the colon in the class header:

// Derived classes have column constructors
class Dog(age: Int) : Animal(age)
Copy the code

If a derived class has a primary constructor, its base class must be initialized with the parameters of the derived class’s primary constructor.

If a derived class does not have a primary constructor, then each subconstructor must initialize its base type with the super keyword.

// Derived classes without column constructors
class Cat : Animal {
    constructor(age: Int) : super(age)
}
Copy the code

Covers the rules

Override method

Kotlin class members are hidden by default, that is, cannot be overwritten. To do so, we need to use the explicit modifier (open) :

open class Animal(age: Int) {
    init {
        println(age)
    }

    open fun eat(a){}}/** * inherits */
// A derived class has a main constructor
class Dog(age: Int) : Animal(age) {
    override fun eat(a){}}Copy the code

The override modifier must be added to the eat() method. If not, the compiler will report an error. If a method is not marked open such as eat(), then subclasses are not allowed to define methods with the same signature, with or without override.

Covering properties

Attribute override is similar to method override; Properties declared in a superclass and then redeclared in a derived class must begin with Override, and they must have a compatible type.

open class Animal(age: Int) {
    open val foot: Int = 0
}

class Dog(age: Int) : Animal(age) {
    override val foot = 4
}
Copy the code

attribute

Declaration of attributes

Attributes in the Kotlin class can be declared either mutable with the keyword var or read-only with the keyword val.

class Shop {
    var name: String = "Android"
    var address: String? = null
}

fun copyShop(shop: Shop): Shop {
    val shop = Shop()
    shop.name = shop.name
    shop.address = shop.address
    / /...
    return shop
}
Copy the code

Getters and Setters

The full syntax for declaring an attribute is

var <propertyName>[: <PropertyType>] [= <property_initializer>]
    [<getter>]
    [<setter>]
Copy the code

Initializers, getters, and setters are optional. If the property type can be inferred from the initializer (or from its getter return value), it can also be omitted.

Case 1:

val simple: Int? // Type Int, default getter, must be initialized in constructor
Copy the code

Case 2:

We can define custom accessors for attributes. If we define a custom getter, it will be called every time the property is accessed:

val isClose: Boolean
        get() = Calendar.getInstance().get(Calendar.HOUR_OF_DAY) > 11
Copy the code

If we define a custom setter, it will be called every time we assign a value to a property. A custom setter looks like this:

var score: Float = 0.0 f
    get() = if (field < 0.2 f) 0.2 f else field * 1.5 f
    set(value) {
        println(value)
    }
Copy the code

Lazy initialization of properties

Normally an attribute declared as a non-null type must be initialized in the constructor. However, this is often inconvenient. For example, properties can be initialized through dependency injection or in the setup method of a unit test.

To handle this, you can mark the property with the Lateinit modifier:

class Test {
    lateinit var shop: Shop
    fun setup(a) {
        shop = Shop()
    }
}
Copy the code

Accessing an LateInit property before initialization throws a specific exception.

We can check if a lateInit var attribute has been initialized using the.isInitialized API of the attribute:

if (::shop.isInitialized)
            println(shop.address)
Copy the code

Abstract classes and interfaces

An abstract class

Class and some of its members can be declared as abstract:

abstract class Printer {
    abstract fun print(a)
}

class FilePrinter : Printer() {
    override fun print(a){}}Copy the code

interface

Kotlin’s interface can contain both declarations and implementations of abstract methods. Unlike abstract classes, interfaces cannot hold state; they can have properties but must be declared abstract or provide accessor implementations.

Kotlin’s interface can be thought of as a special abstract class

Definition and implementation of interfaces

interface Study {
    var time: Int/ / in the abstract
    fun discuss(a)
    fun earningCourses(a) {
        println("Android Architect")}}// Override the fields of the interface in the main constructor
class StudyAS(override var time: Int) : Study {
    // Overwrite the interface fields in the class body
    // override var time: Int = 0
    override fun discuss(a){}}Copy the code

Resolving coverage Conflicts

When implementing multiple interfaces, you may encounter the problem of inheriting multiple implementations from the same method:

interface A {
    fun foo(a) {
        println("A")}}interface B {
    fun foo(a) {
        print("B")}}class D : A.B {
    override fun foo(a) {
        super<A>.foo()
        super<B>.foo()
    }
}
Copy the code

In the above example we use super<>. To resolve the overwrite conflict.

Data classes

We often create classes that only hold data. In Kotlin, we can declare a data class with data:

data class Address(val name: String, val number: Int) {
    var city: String = ""
    fun print(a) {
        println(city)
    }
}
Copy the code

Data class requirements

  • The main constructor needs to have at least one parameter;
  • All arguments to the main constructor need to be marked with val or var;
  • Data classes cannot be abstract, open, sealed, or internal;

Data classes and deconstruction declarations

The Component methods generated for the data classes make them usable in destructible declarations:

val address = Address("Android".1000)
address.city = "Beijing"
val (name, city) = address
println("name:$name city:$city")
Copy the code

Object expressions and object declarations

Object expressions are provided in Kotlin to allow us to make minor changes to a class and create its objects without explicitly declaring new subclasses.

Object expression

To create an object that inherits from an anonymous class of some type (or some type), we write:

open class Address2(name: String) {
    open fun print(a){}}class Shop2 {
    var address: Address2? = null
    fun addAddress(address: Address2) {
        this.address = address
    }

}

fun test3(a) {
    If the supertype has a constructor, the appropriate constructor parameters must be passed to it
    Shop2().addAddress(object : Address2("Android") {
        override fun print(a) {
            super.print()
        }
    })
}
Copy the code

If we only need “one object” and don’t need a special supertype, we can simply write:

fun foo(a) {
    val adHoc = object {
        var x: Int = 0
        var y: Int = 0
    }
    print(adHoc.x + adHoc.y)
}
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 method or as the type of a public property, the actual type of the method or property will be the supertype declared by the anonymous object. If no supertype is declared, it will be Any, and members added to the anonymous object will not be accessible.

class Shop2 {
    var address: Address2? = null
    fun addAddress(address: Address2) {
        this.address = address
    }

    // Private method, so its return type is an anonymous object type
    private fun foo(a) = object {
        val x: String = "x"
    }

    // Public method, so its return type is Any
    fun publicFoo(a) = object {
        val x: String = "x"
    }

    fun bar(a) {
        val x1 = foo().x        / / no problem
// val x2 = publicFoo().x // error: failed to parse reference 'x'}}Copy the code

Object statement

| of the class class | modifier to | object | becomes the object declaration:

object DataUtil {
    fun <T> isEmpty(list: ArrayList<T>?: Boolean {
        returnlist? .isEmpty() ? :false}}fun testDataUtil(a) {
    val list = arrayListOf("1")
    println(DataUtil.isEmpty(list))
}
Copy the code

This is called an object declaration, and it is always followed by a name by the object keyword. Like variable declarations, object declarations are not expressions and cannot be used on the right side of assignment statements.

As we mentioned earlier, Kotlin does not have static members. We usually use object declarations to achieve the effect of static methods, and object declarations are also a good way to create singletons (decompiling demo).

public final class DataUtil {
   public static final DataUtil INSTANCE;

   public final boolean isEmpty(@Nullable ArrayList list) {
      returnlist ! =null ? list.isEmpty() : false;
   }

   privateDataUtil() { } static { DataUtil var0 = new DataUtil(); INSTANCE = var0; }}Copy the code

Associated object

Object declarations within a class can be marked with the Companion keyword:

class Student(val name: String) {
    companion object {
        val student = Student("Android")
        fun study(a) {
            println("Android Architect")}}var age = 16
    fun printName(a) {
        println("My name is $name")}}fun testStudent(a) {
    println(Student.student)
    Student.study()
}
Copy the code

Decompiler see principle:

public final class Student {
   private int age;
   @NotNull
   private final String name;
   @NotNull
   private static final Student student = new Student("Android");
   public static final Student.Companion Companion = new Student.Companion((DefaultConstructorMarker)null);

   public final int getAge(a) {
      return this.age;
   }

   public final void setAge(int var1) {
      this.age = var1;
   }

   public final void printName(a) {
      String var1 = "My name is " + this.name;
      boolean var2 = false;
      System.out.println(var1);
   }

   @NotNull
   public final String getName(a) {
      return this.name;
   }

   public Student(@NotNull String name) {
      Intrinsics.checkParameterIsNotNull(name, "name");
      super(a);this.name = name;
      this.age = 16; }...public static final class Companion {
      @NotNull
      public final Student getStudent(a) {
         return Student.student;
      }

      public final void study(a) {
         String var1 = "Android Architect";
         boolean var2 = false;
         System.out.println(var1);
      }

      private Companion(a) {}// $FF: synthetic method
      public Companion(DefaultConstructorMarker $constructor_marker) {
         this(a); }}}Copy the code

We see that using Companion objects actually creates a static singleton inner class called Companion inside the class, where properties defined in the Companion object are compiled directly into the static fields of the external class, and methods are compiled into the Companion object’s methods.

On the JVM platform, if you use the @jVMStatic annotation, you can generate the members of the associated object as true static methods and fields

Finally, we summarize the semantic differences between object expressions and object declarations:

  • Object expressions are executed (and initialized) immediately where they are used;
  • Object declarations are lazily initialized when they are first accessed;
  • The initialization of the associated object is consistent with the behavior of the Java static initializer when the corresponding class is loaded (parsed)

Practice:

Kotlin implements several scenarios for static members