Kotlin 学习笔记 (Classes and Objects)

本文详细介绍了Kotlin中的类和继承,包括构造函数、类的实例化、继承规则、抽象类、伴生对象等。此外,还涵盖了属性和字段、接口、可见性修饰符、扩展、数据类、嵌套和内部类、枚举类以及对象表达式和声明等核心概念,为深入理解Kotlin的面向对象编程提供了全面的知识。
摘要由CSDN通过智能技术生成

Classes and Objects

Classes and Inheritance

The class declaration consists of the class name, the class header (specifying its type parameters, the primary constructor etc.) and the class body, surrounded by curly braces. Both the header and the body are optional; if the class has no body, curly braces can be omitted.

class Invoice { /*...*/ }
class Empty

Constructors

primary constructor

The primary constructor is part of the class header,

class Person constructor(firstName: String) { /*...*/ }

If the primary constructor does not have any annotations or visibility modifiers, the constructor keyword can be omitted:

class Person(firstName: String) { /*...*/ }

The primary constructor cannot contain any code. Initialization code can be placed in initializer blocks, which are prefixed with the init keyword.

During an instance initialization,the initializer blocks are executed in the same order as they appear in the class body, interleaved with the property initializers:

class InitOrderDemo(name: String) {
    val firstProperty = "First property: $name".also(::println)
    
    init {
        println("First initializer block that prints ${name}")
    }
    
    val secondProperty = "Second property: ${name.length}".also(::println)
    
    init {
        println("Second initializer block that prints ${name.length}")
    }
}

fun main() {
    InitOrderDemo("hello")
}

and the result is :

First property: hello
First initializer block that prints hello
Second property: 5
Second initializer block that prints 5

Note that parameters of the primary constructor can be used in the initializer blocks. They can also be used in property initializers declared in the class body:

class Customer(name: String) {
    val customerKey = name.toUpperCase()
}

In fact, for declaring properties and initializing them from the primary constructor, Kotlin has a concise syntax:
Much the same way as regular properties, the properties declared in the primary constructor can be mutable (var) or read-only (val).

If the constructor has annotations or visibility modifiers, the constructor keyword is required, and the modifiers go before it:

class Person(val firstName: String, val lastName: String, var age: Int) { /*...*/ }
class Customer public @Inject constructor(name: String) { /*...*/ }

Secondary constructors

The class can also declare secondary constructors, which are prefixed with constructor:

class Person {
    var children: MutableList<Person> = mutableListOf<>()
    constructor(parent: Person) {
        parent.children.add(this)
    }
}

If the class has a primary constructor, each secondary constructor needs to delegate to the primary constructor, either directly or indirectly through another secondary constructor(s). Delegation to another constructor of the same class is done using the this keyword:

class Person(val name: String) {
    var children: MutableList<Person> = mutableListOf<>()
    constructor(name: String, parent: Person) : this(name) {
        parent.children.add(this)
    }
}

Note that code in initializer blocks effectively becomes part of the primary constructor.== Delegation to the primary constructor happens as the first statement of a secondary constructor==, so the code in all initializer blocks and property initializers is executed before the secondary constructor body. Even if the class has no primary constructor, the delegation still happens implicitly, and the initializer blocks are still executed:

class Constructors {
    init {
        println("Init block")
    }

    constructor(i: Int) {
        println("Constructor")
    }
}

fun main() {
    Constructors(1)
}

result is :

Init block
Constructor

NOTE: On the JVM, if all of the parameters of the primary constructor have default values, the compiler will generate an additional parameterless constructor which will use the default values. This makes it easier to use Kotlin with libraries such as Jackson or JPA that create class instances through parameterless constructors.

class Customer(val customerName: String = "")

Creating instances of classes

val invoice = Invoice()

val customer = Customer("Joe Smith")

Inheritatnce

Any is superclass

Any has three methods:equals(), hashCode() and toString(). Thus, they are defined for all Kotlin classes.

class Example // Implicitly inherits from Any

Syntax of class inheritatance

By default, Kotlin classes are final: they can’t be inherited. To make a class inheritable, mark it with the open keyword.

open class Base //Class is open for inheritance

open class Base(p: Int)

class Derived(p: Int) : Base(p)

If the derived class has a primary constructor, the base class can (and must) be initialized right there, using the parameters of the primary constructor.

If the derived class has no primary constructor, then each secondary constructor has to initialize the base type using the super keyword, or to delegate to another constructor which does that. Note that in this case different secondary constructors can call different constructors of the base type:

class MyView : View {
    constructor(ctx: Context) : super(ctx)

    constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}

Overriding methods

As we mentioned before, we stick to making things explicit in Kotlin. So, Kotlin requires explicit modifiers for overridable members (we call them open) and for overrides:

open class Shape {
    open fun draw() { /*...*/ }
    fun fill() { /*...*/ }
}

class Circle() : Shape() {
    override fun draw() { /*...*/ }
}

A member marked override is itself open, i.e. it may be overridden in subclasses. If you want to prohibit re-overriding, use final:

open class Rectangle() : Shape() {
    final override fun draw() { /*...*/ }
}

Overriding properties

Each declared property can be overridden by a property with an initializer or by a property with a get method.

open class Shape {
    open val vertexCount: Int = 0
}

class Rectangle : Shape() {
    override val vertexCount = 4
}

You can also override a val property with a var property, but not vice versa

you can use the override keyword as part of the property declaration in a primary constructor.

interface Shape {
    val vertexCount: Int
}

class Rectangle(override val vertexCount: Int = 4) : Shape // Always has 4 vertices

class Polygon : Shape {
    override var vertexCount: Int = 0  // Can be set to any number later
}

Derived class initialization order

During construction of a new instance of a derived class,the base class initialization is done as the first step (preceded only by evaluation of the arguments for the base class constructor) and thus happens before the initialization logic of the derived class is run.

open class Base(val name: String) {

    init { println("Initializing Base") }

    open val size: Int = 
        name.length.also { println("Initializing size in Base: $it") }
}

class Derived(
    name: String,
    val lastName: String
) : Base(name.capitalize().also { println("Argument for Base: $it") }) {

    init { println("Initializing Derived") }

    override val size: Int =
        (super.size + lastName.length).also { println("Initializing size in Derived: $it") }
}

fun main() {
    println("Constructing Derived(\"hello\", \"world\")")
    val d = Derived("hello", "world")
}

result is :

Constructing Derived("hello", "world")
Argument for Base: Hello
Initializing Base
Initializing size in Base: 5
Initializing Derived
Initializing size in Derived: 10

It means that, by the time of the base class constructor execution, the properties declared or overridden in the derived class are not yet initialized. If any of those properties are used in the base class initialization logic (either directly or indirectly, through another overridden open member implementation), it may lead to incorrect behavior or a runtime failure. When designing a base class, you should therefore avoid using open members in the constructors, property initializers, and init blocks.

Calling the superclass implementation

Code in a derived class can call its superclass functions and property accessors implementations using the super keyword:
nside an inner class, accessing the superclass of the outer class is done with the super keyword qualified with the outer class name: super@Outer:

open class Rectangle {
    open fun draw() { println("Drawing a rectangle") }
    val borderColor: String get() = "black"
}

class FilledRectangle : Rectangle() {
    override fun draw() {
        super.draw()
        println("Filling the rectangle")
    }
	//override property with get method
    val fillColor: String get() = super.borderColor 
}
class FilledRectangle: Rectangle() {
    fun draw() { /* ... */ }
    val borderColor: String get() = "black"
    
    inner class Filler {
        fun fill() { /* ... */ }
        fun drawAndFill() {
            super@FilledRectangle.draw() // Calls Rectangle's implementation of draw()
            fill()
            println("Drawn a filled rectangle with color ${super@FilledRectangle.borderColor}") // Uses Rectangle's implementation of borderColor's get()
        }
    }
}

Overriding rules

open class Rectangle {
    open fun draw() { /* ... */ }
}

interface Polygon {
    fun draw() { /* ... */ } // interface members are 'open' by default
}

class Square() : Rectangle(), Polygon {
    // The compiler requires draw() to be overridden:
    override fun draw() {
        super<Rectangle>.draw() // call to Rectangle.draw()
        super<Polygon>.draw() // call to Polygon.draw()
    }
}

Abstract classes

open class Polygon {
    open fun draw() {}
}

abstract class Rectangle : Polygon() {
    abstract override fun draw()
}

Companion objects

Properties and Fields

Declaring Properties val and var

class Address {
    var name: String = "Holmes, Sherlock"
    var street: String = "Baker"
    var city: String = "London"
    var state: String? = null
    var zip: String = "123456"
}

fun copyAddress(address: Address): Address {
    val result = Address() // there's no 'new' keyword in Kotlin
    result.name = address.name // accessors are called
    result.street = address.street
    // ...
    return result
}

Getters and Setters

syntax

var <propertyName>[: <PropertyType>] [= <property_initializer>]
    [<getter>]
    [<setter>]
var allByDefault: Int? // error: explicit initializer required, default getter and setter implied
var initialized = 1 // has type Int, default getter and setter

val simple: Int? // has type Int, default getter, must be initialized in constructor
val inferredType = 1 // has type Int and a default getter

val isEmpty: Boolean
    get() = this.size == 0

var stringRepresentation: String
    get() = this.toString()
    set(value) {
        setDataFromString(value) // parses the string and assigns values to other properties
    }

val isEmpty get() = this.size == 0  // has type Boolean

var setterVisibility: String = "abc"
    private set // the setter is private and has the default implementation

var setterWithAnnotation: Any? = null
    @Inject set // annotate the setter with Inject

Backing Fields

Fields cannot be declared directly in Kotlin classes. However, when a property needs a backing field, Kotlin provides it automatically. This backing field can be referenced in the accessors using the field identifier:

var counter = 0 // Note: the initializer assigns the backing field directly
    set(value) {
        if (value >= 0) field = value
    }

The field identifier can only be used in the accessors of the property.

A backing field will be generated for a property if it uses the default implementation of at least one of the accessors, or if a custom accessor references it through the field identifier.

For example, in the following case there will be no backing field:

val isEmpty: Boolean
    get() = this.size == 0

Backing Properties

If you want to do something that does not fit into this “implicit backing field” scheme, you can always fall back to having a backing property:

private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
    get() {
        if (_table == null) {
            _table = HashMap() // Type parameters are inferred
        }
        return _table ?: throw AssertionError("Set to null by another thread")
    }

the more detailed understanding the backing properties ,please ref to here

Compile-Time Constants

f the value of a read-only property is known at the compile time, mark it as a compile time constant using the const modifier. Such properties need to fulfil the following requirements:

-Top-level, or member of an object declaration or a companion object.
-Initialized with a value of type String or a primitive type
-No custom getter
Such properties can be used in annotations:

const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"

@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ... }

Late-Initialized Properties and Variables

Normally, properties declared as having a non-null type must be initialized in the constructor. However, fairly often this is not convenient. For example, properties can be initialized through dependency injection, or in the setup method of a unit test. In this case, you cannot supply a non-null initializer in the constructor, but you still want to avoid null checks when referencing the property inside the body of a class.

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

public class MyTest {
    lateinit var subject: TestSubject

    @SetUp fun setup() {
        subject = TestSubject()
    }

    @Test fun test() {
        subject.method()  // dereference directly
    }
}

The modifier can be used on var properties declared inside the body of a class (not in the primary constructor, and only when the property does not have a custom getter or setter) and, since Kotlin 1.2, for top-level properties and local variables. The type of the property or variable must be non-null, and it must not be a primitive type.

Accessing a lateinit property before it has been initialized throws a special exception that clearly identifies the property being accessed and the fact that it hasn’t been initialized.

if (foo::bar.isInitialized) {
    println(foo.bar)
}

Interfaces

Interfaces in Kotlin can contain declarations of abstract methods, as well as method implementations. What makes them different from abstract classes is that interfaces cannot store state. They can have properties but these need to be abstract or to provide accessor implementations.

interface MyInterface {
    fun bar()
    fun foo() {
      // optional body
    }
}

class Child : MyInterface {
    override fun bar() {
        // body
    }
}

interface MyInterface {
    val prop: Int // abstract

    val propertyWithImplementation: String
        get() = "foo"

    fun foo() {
        print(prop)
    }
}

class Child : MyInterface {
    override val prop: Int = 29
}

Interfaces Inheritance

interface Named {
    val name: String
}

interface Person : Named {
    val firstName: String
    val lastName: String
    
    override val name: String get() = "$firstName $lastName"
}

data class Employee(
    // implementing 'name' is not required
    override val firstName: String,
    override val lastName: String,
    val position: Position
) : Person

Resolving overriding conflicts

interface A {
    fun foo() { print("A") }
    fun bar()
}

interface B {
    fun foo() { print("B") }
    fun bar() { print("bar") }
}

class C : A {
    override fun bar() { print("bar") }
}

class D : A, B {
    override fun foo() {
        super<A>.foo()
        super<B>.foo()
    }

    override fun bar() {
        super<B>.bar()
    }
}

Visibility Modifiers

Classes, objects, interfaces, constructors, functions, properties and their setters can have visibility modifiers. (Getters always have the same visibility as the property.) There are four visibility modifiers in Kotlin: private, protected, internal and public. The default visibility, used if there is no explicit modifier, is public.

Packages

Functions, properties and classes, objects and interfaces can be declared on the “top-level”, i.e. directly inside a package:

// file name: example.kt
package foo

fun baz() { ... }
class Bar { ... }

-If you do not specify any visibility modifier, public is used by default, which means that your declarations will be visible everywhere;
-If you mark a declaration private, it will only be visible inside the file containing the declaration;
-If you mark it internal, it is visible everywhere in the same module;
-protected is not available for top-level declarations.

// file name: example.kt
package foo

private fun foo() { ... } // visible inside example.kt

public var bar: Int = 5 // property is visible everywhere
    private set         // setter is visible only in example.kt
    
internal val baz = 6    // visible inside the same module

Classes and Interfaces

For members declared inside a class:

-private means visible inside this class only (including all its members);
-protected — same as private + visible in subclasses too;
-internal — any client inside this module who sees the declaring class sees its internal members;
-public — any client who sees the declaring class sees its public members.
Note that in Kotlin, outer class does not see private members of its inner classes.

If you override a protected member and do not specify the visibility explicitly, the overriding member will also have protected visibility.

open class Outer {
    private val a = 1
    protected open val b = 2
    internal val c = 3
    val d = 4  // public by default
    
    protected class Nested {
        public val e: Int = 5
    }
}

class Subclass : Outer() {
    // a is not visible
    // b, c and d are visible
    // Nested and e are visible

    override val b = 5   // 'b' is protected
}

class Unrelated(o: Outer) {
    // o.a, o.b are not visible
    // o.c and o.d are visible (same module)
    // Outer.Nested is not visible, and Nested::e is not visible either 
}

Constructors and Local declarations

class C private constructor(a: Int) { ... }

Local variables, functions and classes can not have visibility modifiers

Modules

The internal visibility modifier means that the member is visible within the same module. More specifically, a module is a set of Kotlin files compiled together:

-an IntelliJ IDEA module;
-a Maven project;
-a Gradle source set (with the exception that the test source set can access the internal declarations of main);
-a set of files compiled with one invocation of the Ant task.

Extensions

Mainly for the Utils classes, for example StirngUtils or FileUtils etc. refor to here

fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] // 'this' corresponds to the list
    this[index1] = this[index2]
    this[index2] = tmp
}

Extensions are resolved statically

Extnestions are resolved as a final static method in the files or classes
ExtensionsTest.kt:

package com.gbd.mykttestapp


open class Shape

class Rectangle : Shape() {
    fun forTest(){
        TestPrint.println("for test in Rectangle")
    }
}

fun Shape.getNameTest(): String {
    TestPrint.println("call shape.getName")
    return "Sharp"
}

fun Rectangle.getNameTest(msg: String): String {
    forTest()
    TestPrint.println("call Rectangle.getName")
    return "Rectangle"
}

fun printClassName(s: Shape) {
    TestPrint.println(s.getNameTest())
}

fun <T> MutableList<T>.swap(index1: Int, index2: Int) {
    val tmp = this[index1] // 'this' corresponds to the list
    this[index1] = this[index2]
    this[index2] = tmp
}

and the java code is :

public final class ExtensionsTestKt {
   @NotNull
   public static final String getNameTest(@NotNull Shape $this$getNameTest) {
      Intrinsics.checkParameterIsNotNull($this$getNameTest, "$this$getNameTest");
      TestPrint.INSTANCE.println("call shape.getName");
      return "Sharp";
   }

   @NotNull
   public static final String getNameTest(@NotNull Rectangle $this$getNameTest, @NotNull String msg) {
      Intrinsics.checkParameterIsNotNull($this$getNameTest, "$this$getNameTest");
      Intrinsics.checkParameterIsNotNull(msg, "msg");
      $this$getNameTest.forTest();
      TestPrint.INSTANCE.println("call Rectangle.getName");
      return "Rectangle";
   }

   public static final void printClassName(@NotNull Shape s) {
      Intrinsics.checkParameterIsNotNull(s, "s");
      TestPrint.INSTANCE.println(getNameTest(s));
   }

   public static final void swap(@NotNull List $this$swap, int index1, int index2) {
      Intrinsics.checkParameterIsNotNull($this$swap, "$this$swap");
      Object tmp = $this$swap.get(index1);
      $this$swap.set(index1, $this$swap.get(index2));
      $this$swap.set(index2, tmp);
   }
}

Attantion points

members always win:

fun main() {
    class Example {
        fun printFunctionType() { println("Class method") }
    }

    fun Example.printFunctionType() { println("Extension function") }

    Example().printFunctionType()
}

result is :

Class method

Nullable receiver

Such extensions can be called on an object variable even if its value is null, and can check for this == null inside the body. This is what allows you to call toString() in Kotlin without checking for null: the check happens inside the extension function.

fun Any?.toString(): String {
    if (this == null) return "null"
    // after the null check, 'this' is autocast to a non-null type, so the toString() below
    // resolves to the member function of the Any class
    return toString()
}

Extension properties :

no back fields, can only be defined by explicitly providing getters/setters.

val <T> List<T>.lastIndex: Int
    get() = size - 1

Companion object extensions

If a class has a companion object defined, you can also define extension functions and properties for the companion object. Just like regular members of the companion object, they can be called using only the class name as the qualifier:

class MyClass {
    companion object { }  // will be called "Companion"
}

fun MyClass.Companion.printCompanion() { println("companion") }

fun main() {
    MyClass.printCompanion()
}

Scope of extensions

To use such an extension outside its declaring package, we need to import it at the call site:

package org.example.declarations
 
fun List<String>.getLongestString() { /*...*/}
package org.example.usage

import org.example.declarations.getLongestString  //import the declaration file name.

fun main() {
    val list = listOf("red", "green", "blue")
    list.getLongestString()
}

Inside a class, you can declare extensions for another class. Inside such an extension, there are multiple implicit receivers - objects members of which can be accessed without a qualifier. The instance of the class in which the extension is declared is called dispatch receiver, and the instance of the receiver type of the extension method is called extension receiver.

class Host(val hostname: String) {
    fun printHostname() { print(hostname) }
}

class Connection(val host: Host, val port: Int) {
     fun printPort() { print(port) }

     fun Host.printConnectionString() {
         printHostname()   // calls Host.printHostname()
         print(":")
         printPort()   // calls Connection.printPort()
     }

     fun connect() {
         /*...*/
         host.printConnectionString()   // calls the extension function
     }
}

fun main() {
    Connection(Host("kotl.in"), 443).connect()
    //Host("kotl.in").printConnectionString(443)  // error, the extension function is unavailable outside Connection
}

In case of a name conflict between the members of the dispatch receiver and the extension receiver, the extension receiver takes precedence. To refer to the member of the dispatch receiver you can use the qualified this syntax.

class Connection {
    fun Host.getConnectionString() {
        toString()         // calls Host.toString()
        this@Connection.toString()  // calls Connection.toString()
    }
}

Extensions declared as members can be declared as open and overridden in subclasses. This means that the dispatch of such functions is virtual with regard to the dispatch receiver type, but static with regard to the extension receiver type.

open class Base { }

class Derived : Base() { }

open class BaseCaller {
    open fun Base.printFunctionInfo() {
        println("Base extension function in BaseCaller")
    }

    open fun Derived.printFunctionInfo() {
        println("Derived extension function in BaseCaller")
    }

    fun call(b: Base) {
        b.printFunctionInfo()   // call the extension function
    }
}

class DerivedCaller: BaseCaller() {
    override fun Base.printFunctionInfo() {
        println("Base extension function in DerivedCaller")
    }

    override fun Derived.printFunctionInfo() {
        println("Derived extension function in DerivedCaller")
    }
}

fun main() {
    BaseCaller().call(Base())   // "Base extension function in BaseCaller"
    DerivedCaller().call(Base())  // "Base extension function in DerivedCaller" - dispatch receiver is resolved virtually
    DerivedCaller().call(Derived())  // "Base extension function in DerivedCaller" - extension receiver is resolved statically
}

Data Classes

We frequently create classes whose main purpose is to hold data. In such a class some standard functionality and utility functions are often mechanically derivable from the data. In Kotlin, this is called a data class

data class User(val name: String, val age: Int)

Attantion points

Automatically derives memebers

  • equals()/hashCode() pair;
  • toString() of the form “User(name=John, age=42)”;
  • componentN() functions corresponding to the properties in their order of declaration;
  • copy() function (see below).

other rules

  • The primary constructor needs to have at least one parameter;
  • All primary constructor parameters need to be marked as val or var;
  • Data classes cannot be abstract, open, sealed or inner;
    On the JVM, if the generated class needs to have a parameterless constructor, default values for all properties have to be specified
data class User(val name: String = "", val age: Int = 0)

Properties Declared in the Class Body

Note that the compiler only uses the properties defined inside the primary constructor for the automatically generated functions. To exclude a property from the generated implementations, declare it inside the class body:

data class Person(val name: String) {
    var age: Int = 0
}

Copying

It’s often the case that we need to copy an object altering some of its properties, but keeping the rest unchanged. This is what copy() function is generated for. For the User class above, its implementation would be as follows:

fun copy(name: String = this.name, age: Int = this.age) = User(name, age)

This allows us to write:

val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)

Data Classes and Destructuring Declarations

val jane = User("Jane", 35) 
val (name, age) = jane
println("$name, $age years of age") // prints "Jane, 35 years of age"

Nested and Inner Classes

Nested classes:

class Outer {
   private val bar: Int = 1
   class Nested {
       fun foo() = 2
   }
}

val demo =Outer.Nested().foo() // == 2
just like the static inner classes in java

Inner classes

class Outer {
   private val bar: Int = 1
   inner class Inner {
       fun foo() = bar
   }
}

val demo =Outer().Inner().foo() // == 1
like the normal inner classes in java

Annonymous inner classes

window.addMouseListener(object : MouseAdapter() {

    override fun mouseClicked(e: MouseEvent) { ... }

    override fun mouseEntered(e: MouseEvent) { ... }
})

Note: on the JVM, if the object is an instance of a functional Java interface (i.e. a Java interface with a single abstract method), you can create it using a lambda expression prefixed with the type of the interface:

val listener = ActionListener { println("clicked") }

Enum classes

the enum classes is mostly like the enum in java :

  • Define the enum classes with the enum
enum class Direction {
    NORTH, SOUTH, WEST, EAST
}
  • Initialization
enum class Color(val rgb: Int) {
        RED(0xFF0000),
        GREEN(0x00FF00),
        BLUE(0x0000FF)
}
  • Anonymous Classes : enum instance also could implement the abstract method
enum class ProtocolState {
    WAITING {
        override fun signal() = TALKING
    },

    TALKING {
        override fun signal() = WAITING
    };

    abstract fun signal(): ProtocolState
}
  • Implementing Interfaces in Enum Classes
import java.util.function.BinaryOperator
import java.util.function.IntBinaryOperator

enum class IntArithmetics : BinaryOperator<Int>, IntBinaryOperator {
    PLUS {
        override fun apply(t: Int, u: Int): Int = t + u
    },
    TIMES {
        override fun apply(t: Int, u: Int): Int = t * u
    };

    override fun applyAsInt(t: Int, u: Int) = apply(t, u)
}

fun main() {
    val a = 13
    val b = 31
    for (f in IntArithmetics.values()) {
        println("$f($a, $b) = ${f.apply(a, b)}")
    }
}

so the result is :

PLUS(13, 31) = 44
TIMES(13, 31) = 403

Enum Constants

There are two constants methods of enumCalss:

EnumClass.valueOf(value: String): EnumClass
EnumClass.values(): Array<EnumClass>

it’s possible to access the constants in an enum class in a generic way, 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 })
}

printAllValues<RGB>() // prints RED, GREEN, BLUE
  • Every enum constant has properties to obtain its name and position in the enum class declaration:
val name: String
val ordinal: Int

The difference between enum and sealed classes could be seen here

Object Expressions and Declarations

Sometimes we need to create an object of a slight modification of some class, without explicitly declaring a new subclass for it. Kotlin handles this case with object expressions and object declarations.

Object expressions : used as anonymous objects in java

inherits from some type

window.addMouseListener(object : MouseAdapter() {
   override fun mouseClicked(e: MouseEvent) { /*...*/ }

   override fun mouseEntered(e: MouseEvent) { /*...*/ }
})

if a supertype has a constructor with parameters

open class A(x: Int) {
    public open val y: Int = x
}

interface B { /*...*/ }

val ab: A = object : A(1), B {
    override val y = 15
}

“just a object”, with no nontruvial supertypes

fun foo() {
    val adHoc = object {
        var x: Int = 0
        var y: Int = 0
    }
    print(adHoc.x + adHoc.y)
}

anonymous objects scope

Note that anonymous objects can be used as types only in local and private declarations. If you use an anonymous object as a return type of a public function or the type of a public property, the actual type of that function or property will be the declared supertype of the anonymous object, or Any if you didn’t declare any supertype. Members added in the anonymous object will not be accessible.

class C {
    // Private function, so the return type is the anonymous object type
    private fun foo() = object {
        val x: String = "x"
    }

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

    fun bar() {
        val x1 = foo().x        // Works
        val x2 = publicFoo().x  // ERROR: Unresolved reference 'x'
    }
}

The code in object expressions can access variables from the enclosing scope.

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++
        }
    })
    // ...
}

Object declarations : used as Singleton

Singleton may be useful in several cases, and Kotlin makes it easy to declare singletons:

object DataProviderManager {
    fun registerDataProvider(provider: DataProvider) {
        // ...
    }

    val allDataProviders: Collection<DataProvider>
        get() = // ...
}

Object declaration’s initialization is thread-safe and done at first access.

how to use:

DataProviderManager.registerDataProvider(...)

Such objects can have supertypes:

object DefaultListener : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) { ... }

    override fun mouseEntered(e: MouseEvent) { ... }
}

Companion Objects :used as the static memebers in java

defination

class MyClass {
    companion object Factory {
        fun create(): MyClass = MyClass()
    }
}

Members of the companion object can be called by using simply the class name as the qualifier:

val instance = MyClass.create()

The name of the companion object can be omitted, in which case the name Companion will be used:

class MyClass {
    companion object { }
}

val x = MyClass.Companion

The name of a class used by itself (not as a qualifier to another name) acts as a reference to the companion object of the class (whether named or not):

class MyClass1 {
    companion object Named { }
}

val x = MyClass1

class MyClass2 {
    companion object { }
}

val y = MyClass2

Note that, even though the members of companion objects look like static members in other languages, at runtime those are still instance members of real objects, and can, for example, implement interfaces:

interface Factory<T> {
    fun create(): T
}

class MyClass {
    companion object : Factory<MyClass> {
        override fun create(): MyClass = MyClass()
    }
}

val f: Factory<MyClass> = MyClass

However, on the JVM you can have members of companion objects generated as real static methods and fields, if you use the @JvmStatic annotation.

Semantic difference between object expressions and declarations

There is one important semantic difference between object expressions and object declarations:

  • object expressions are executed (and initialized) immediately, where they are used;
  • object declarations are initialized lazily, when accessed for the first time;
  • a companion object is initialized when the corresponding class is loaded (resolved), matching the semantics of a Java static initializer.

Type aliases

  • shorten long generic types:
typealias NodeSet = Set<Network.Node>

typealias FileTable<K> = MutableMap<K, MutableList<File>>
  • for function types:
typealias MyHandler = (Int, String, Any) -> Unit

typealias Predicate<T> = (T) -> Boolean
  • for inner an nested classes:
class A {
    inner class Inner
}
class B {
    inner class Inner
}

typealias AInner = A.Inner
typealias BInner = B.Inner
  • Type aliases do not introduce new types. They are equivalent to the corresponding underlying types. When you add typealias Predicate<T>and use Predicate<Int>in your code, the Kotlin compiler always expands it to (Int) -> Boolean. Thus you can pass a variable of your type whenever a general function type is required and vice versa:
typealias Predicate<T> = (T) -> Boolean

fun foo(p: Predicate<Int>) = p(42)

fun main() {
    val f: (Int) -> Boolean = { it > 0 }
    println(foo(f)) // prints "true"

    val p: Predicate<Int> = { it > 0 }
    println(listOf(1, -2).filter(p)) // prints "[1]"
}

Inline classes

please ref to here , it is very clear artical of inline class in kotlin.

Delegation

Used as the delegation design pattern in java

using ‘by’ for the grammar

interface Base {
    fun print()
}

class BaseImpl(val x: Int) : Base {
    override fun print() { print(x) }
}

class Derived(b: Base) : Base by b  //using 'by'

fun main() {
    val b = BaseImpl(10)
    Derived(b).print()
}

can override a method of the delegated class

interface Base {
    fun printMessage()
    fun printMessageLine()
}

class BaseImpl(val x: Int) : Base {
    override fun printMessage() { print(x) }
    override fun printMessageLine() { println(x) }
}

class Derived(b: Base) : Base by b {
    override fun printMessage() { print("abc") }
}

fun main() {
    val b = BaseImpl(10)
    Derived(b).printMessage()
    Derived(b).printMessageLine()
}

result is :

abc10

can’t override the members of the delegated class

interface Base {
    val message: String
    fun print()
}

class BaseImpl(val x: Int) : Base {
    override val message = "BaseImpl: x = $x"
    override fun print() { println(message) }
}

class Derived(b: Base) : Base by b {
    // This property is not accessed from b's implementation of `print`
    override val message = "Message of Derived"
}

fun main() {
    val b = BaseImpl(10)
    val derived = Derived(b)
    derived.print()
    println(derived.message)
}

and the result is :

BaseImpl: x = 10
Message of Derived
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值