Kotlin面向對象編程筆記

Kotlin語言基礎筆記

Kotlin流程控制語句筆記

Kotlin操作符重載與中綴表示法筆記

Kotlin擴展函數和擴展屬性筆記

Kotlin空指針安全(null-safety)筆記

Kotlin類型系統筆記

Kotlin面向對象編程筆記

Kotlin委托(Delegation)筆記

Kotlin泛型型筆記

Kotlin函數式編程筆記

Kotlin與Java互操作筆記

Kotlin協程筆記

Kotlin是基于Java的語言,所以Kotlin也是一種面向對象的語言,如果你熟悉Java,你應該很快可以上手,這篇文章假設你已經熟悉Java語言了。

1. 類與構造函數

Kotlin中的類與接口和Java中的有些區別。

  • Kotlin中接口可以包含屬性申明。
  • Kotlin的類申明,默認是final和public的。
  • Kotlin的嵌套類并不是默認在內部的,他們不包含外部類的隱私引用。
  • Kotlin的構造函數,分為主構造函數和次構造函數。
  • Kotlin中可以使用data關鍵字來申明一個數據類。
  • Kotlin中可以使用object關鍵字來表示單例對象、伴生對象等。

Kotlin的類由下面幾部分組成:

  • 構造函數和初始化塊。
  • 屬性
  • 函數
  • 嵌套類和內部類
  • 對象申明

1.1 類的聲明

使用class關鍵字聲明,這個跟Java是一樣的,如果你申明的類不包含任何東西的話,可以自己class后面直接寫上類名。

class Person

1.2 構造函數

在Kotlin中可以有一個主構造函數,一個或者多個次構造函數。

主構造函數

主構造函數直接跟在類名后面,如下:

open class Person constructor(var name: String, var age: Int) : Any() {
...
}

主構造函數中申明的屬性可以是可變的(var)也可以是不變的(val)。如果主構造函數沒有任何注解或者可見性修飾符,可以省略constructor關鍵字,而且Koltin中的類默認就是繼承Any的,也可以省略。所以可以簡化成如下:

open class Person(var name: String, var age: Int) {
...
}

如果這個類是有注解或者可見性修飾符,那么constructor關鍵字不可少,如下:

class Student @MyAutoware public constructor(name: String, age: Int) : Person(name, age) {
...
}

主構造函數不能包括任何代碼。初始化代碼可以放到以init關鍵字作為前綴的初始化塊中:

class Student @MyAutoware public constructor(name: String, age: Int) : Person(name, age) {
    init {
        println("Student(name = $name, age = $age) created")
    }
}

主構造函數的參數可以在初始化塊中使用,也可以在類體內申明的屬性初始化器中使用。

次構造函數

我們也可以在類體中使用constructor申明次構造函數,次構造函數的參數不能使用val或者var申明。

annotation class MyAutoware

class Student @MyAutoware public constructor(name: String, age: Int) : Person(name, age) {
    var grade: Int = 1

    init {
        println("Student(name = $name, age = $age) created")
    }

    constructor(name: String, age: Int, grade: Int) : this(name, age){
        this.grade = grade
    }
}

如果一個類有主構造函數,那么每個次構造函數都需要委托給主構造函數,委托到同一個類的另一個構造函數可以使用this關鍵字,如上面這個例子this(name, age)

如果一個非抽象類沒有申明任何構造函數(包括主或者次),它會生成一個不帶參數的主構造函數。構造函數的可見性是public。

私有的主構造函數

如果我們不希望這個類被實例化,我們可以申明如下:

class CantCreateMe private constructor()
cancreateme

當然我們可以通過次構造函數來創建。

class CantCreateMe private constructor(){
    
    var name: String = ""
    
    constructor(name: String) : this() {
        this.name = name
    }
}

1.3 屬性

class Point{
    var x:Int = 0
    var y:Int = 0
}


fun main(args: Array<String>) {
    val p = Point()
    p.x = 8
    p.y = 10
    println("Point(x = ${p.x}, y = ${p.y})") 
}

可以看出Kotlin中的類的字段自動帶有getter和setter方法,比Java會簡潔很多。

1.3 方法(函數)

class Point{
    var x:Int = 0
    var y:Int = 0

    operator fun plus(p: Point) {
        x += p.x
        y += p.y
    }
}


fun main(args: Array<String>) {
    val p = Point()
    p.x = 8
    p.y = 10

    val p1 = Point()
    p1.x = 2
    p1.y = 3

    val p2 = p + p1
    println("Point(x = ${p.x}, y = ${p.y})")
}

2. 抽象類

下面就是一個抽象類,類需要用abstract修飾,其中也有抽象方法,跟Java有區別的是Kotlin的抽象類可以包含抽象屬性。

abstract class Person(var name: String, var age: Int){

    abstract var addr: String
    abstract val weight: Float

    abstract fun doEat()
    abstract fun doWalk()

    fun doSwim() {
        println("I am Swimming ... ")
    }

    open fun doSleep() {
        println("I am Sleeping ... ")
    }
}

2.1 抽象函數(方法)

在上面這個抽象類中,有doEat和doWalk抽象函數,同時還有具體的實現函數doSwim,在Kotlin中如果類和方法沒有修飾符的化,默認就是final的。這個跟Java是不一樣的。所以doSwim其實是final的,也就是說Person的子類不能覆蓋這個方法。如果一個類或者類的方法想要設計成被覆蓋(override)的,那么就需要加上open修飾符。下面是一個Person的子類:

class Teacher(name: String, age: Int) : Person(name, age) {

    override var addr:String = "Guangzhou"
    override val weight: Float = 100.0F

    override fun doEat() {
        println("Teacher is Eating ... ")
    }

    override fun doWalk() {
        println("Teacher is Walking ... ")
    }

    override fun doSleep() {
        super.doSleep()
        println("Teacher is Sleeping ... ")
    }
    
//編譯錯誤
//    override fun doSwim() {
//        println("Teacher is Swimming ... ")
//    }

}

如果子類覆蓋了父類的方法或者屬性,需要使用override關鍵字修飾。如果子類沒有實現父類的抽象方法,則必須把子類也定義成

抽象函數的特征有以下幾點:

  • 抽象函數、抽象屬性必須使用abstract關鍵字修飾。
  • 抽象函數或者抽象類不用手動添加open關鍵字,默認就是open類型。
  • 抽象函數沒有具體的實現,抽象屬性不用賦值。
  • 含有抽象函數或者抽象屬性的類,必須要使用abstract關鍵字修飾。抽象類可以有具體實現的函數,這樣的函數默認是final的(不能被覆蓋)。如果想要被覆蓋,需要手工加上open關鍵字。

3. 接口

Kotlin中的接口跟Java8的接口類似。他們都可以包含抽象方法以及實現的方法。

interface UserService{
    var version: String

    fun save(user: User)

    fun log(){
        println("UserService log...")
    }
}

3.1 接口的實現

接口是沒有構造函數的,和繼承一樣,我們也是使用冒號:來實現一個接口,如果要實現多個接口,使用逗號,分開,如下:

data class User(var name: String, var password: String)
data class Product(var pid: String, var name: String)

interface UserService{
    val version: String
    val defaultPassword: String
        get() = "123456" 

    fun save(user: User)

    fun log(){
        println("UserService log...")
    }
}

interface ProductService{
    val version: String

    fun save(product : Product)

    fun log(){
        println("ProductService log...")
    }
}

class UserProductServiceImpl : UserService, ProductService{

    override val version: String = "1.0"
    override val defaultPassword: String
        get() = "@WSX1qaz"

    override fun save(user: User) {
        println("save user...")
    }

    override fun save(product: Product) {
        println("save product")
    }

    override fun log() {
        super<UserService>.log()
        super<ProductService>.log()
    }

}

3.2 覆蓋沖突

在上面的例子中,UserService和ProductService都有log()這個方法的實現,我們在重寫UserProductServiceImpl的log()方法時,如果我們調用super.log() 。編譯器是不知道我們要調用那個父類的log()方法的,這樣就產生了覆蓋沖突。

覆蓋沖突

解決的辦法也很簡單,我們使用下面的這種語法來指定調用那個父類的log()方法。

super<UserService>.log()
super<ProductService>.log()

3.3 接口中的屬性

接口中的屬性,可以是抽象的,或者是提供訪問器的實現。因為接口沒有狀態,所以接口的屬性是無狀態的。請看上面的例子。

4. 繼承

在Kotlin中,所有的類都默認繼承Any這個類,Any并不是跟Java的java.lang.Object一樣,因為它只有equals(),hashCode()和toString()這三個方法。
除了抽象類和接口默認是可被繼承外,其他類默認是不可以被繼承的(相當于默認都帶有final修飾符)。而類中的方法也是默認不可以被繼承的。

  • 如果你要繼承一個類,你需要使用open關鍵字修飾這個類。
  • 如果你要繼承一個類的某個方法,這個方法也需要使用open關鍵字修飾。
open class Base
class SubClass :Base()

如果Base有構造函數,那么子類的主構造函數必須繼承。如下:

open class Base(type: String){
    open fun canBeOverride() {
        println("I can be override.")
    }

    fun cannotBeOverride() {
        println("I can't be override.")
    }
}

class SubClass(type: String) :Base(type){
    override fun canBeOverride() {
        super.canBeOverride()
        println("Override!!!")
    }

//    override fun cannotBeOverride() {  編譯錯誤。
//        super.cannotBeOverride()
//        println("Override!!!")
//    }
}

override重寫的函數也是open的,如果不希望被重寫,可以加上final:

open class Base(type: String){
    open fun canBeOverride() {
        println("I can be override.")
    }

    fun cannotBeOverride() {
        println("I can't be override.")
    }
}

open class SubClass(type: String) :Base(type){
//如果某個類繼承了SubClass,那么這個類將不可以重寫canBeOverride方法。
    final override fun canBeOverride() {
        super.canBeOverride()
        println("Override!!!")
    }
}

繼承實現接口:

abstract class Animal {
    fun doEat() {
        println("Animal Eating")
    }
}

abstract class Plant {
    fun doEat() {
        println("Plant Eating")
    }
}

interface Runnable {
    fun doRun()
}

interface Flyable {
    fun doFly()
}

class Dog : Animal(), Runnable {
    override fun doRun() {
        println("Dog Running")
    }
}

class Eagle : Animal(), Flyable {
    override fun doFly() {
        println("Eagle Flying")
    }
}

// 始祖鳥, 能飛也能跑
class Archaeopteryx : Animal(), Runnable, Flyable {
    override fun doRun() {
        println("Archaeopteryx Running")
    }

    override fun doFly() {
        println("Archaeopteryx Flying")
    }

}

fun main(args: Array<String>) {
    val d = Dog()
    d.doEat()
    d.doRun()

    val e = Eagle()
    e.doEat()
    e.doFly()

    val a = Archaeopteryx()
    a.doEat()
    a.doFly()
    a.doRun()
}

5. 枚舉類

Kotlin的枚舉類基本上跟Java的差不多。

enum class Direction{
    NORTH, SOUTH, WEST, EAST
}


fun main(args: Array<String>) {
    println(Direction.NORTH.name)  //打印NORTH
    println(Direction.NORTH.ordinal)  //打印0
    println(Direction.NORTH is Direction)  //打印true
    println(Direction.valueOf("NORTH"))  打印NORTH
    println(Direction.values().joinToString(prefix = "[", postfix = "]"))  //打印[NORTH, SOUTH, WEST, EAST]
}

枚舉類也可以有構造函數,如下:

enum class Color(val rgb: Int) {
    RED(0xFF0000),
    GREEN(0x00FF00),
    BLUE(0x0000FF)
}

fun main(args: Array<String>) {
    println(Color.RED.rgb)  //打印16711680
}

枚舉常量也可以聲明自己的匿名類:

enum class ActivtyLifeState {
    onCreate {
        override fun signal() = onStart
    },

    onStart {
        override fun signal() = onStop
    },

    onStop {
        override fun signal() = onStart
    },

    onDestroy {
        override fun signal() = onDestroy
    };

    abstract fun signal(): ActivtyLifeState
}

fun main(args: Array<String>) {
    println(ActivtyLifeState.onCreate)  //打印onCreate
}

我們也可以使用enumValues ()函數來列舉所有的值:

enum class Color(val rgb: Int) {
    RED(0xFF0000),
    GREEN(0x00FF00),
    BLUE(0x0000FF)
}

fun main(args: Array<String>) {
    println(enumValues<Color>().joinToString { "${it.rgb} : ${it.name} : ${it.ordinal} " })  //打印16711680 : RED : 0 , 65280 : GREEN : 1 , 255 : BLUE : 2 
}

6. 注解類

Kotlin中的注解與Java的的注解完全兼容。下面示例如何聲明一個注解:

annotation class 注解名

代碼示例:

@Target(AnnotationTarget.CLASS,
        AnnotationTarget.FUNCTION,
        AnnotationTarget.EXPRESSION,
        AnnotationTarget.FIELD,
        AnnotationTarget.LOCAL_VARIABLE,
        AnnotationTarget.TYPE,
        AnnotationTarget.TYPEALIAS,
        AnnotationTarget.TYPE_PARAMETER,
        AnnotationTarget.VALUE_PARAMETER)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
@Repeatable
annotation class MagicClass

@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
@Repeatable
annotation class MagicFunction

@Target(AnnotationTarget.CONSTRUCTOR)
@Retention(AnnotationRetention.SOURCE)
@MustBeDocumented
@Repeatable
annotation class MagicConstructor

在上面的代碼中,我們通過向注解類添加元注解(meta-annotation)的方法來指定其他屬性:

  • @Target :指定這個注解可被用于哪些元素(類, 函數, 屬性, 表達式, 等等.);
  • @Retention :指定這個注解的信息是否被保存到編譯后的 class 文件中, 以及在運行時是否可以通過反 射訪問到它;
  • @Repeatable:允許在單個元素上多次使用同一個注解;
  • @MustBeDocumented : 表示這個注解是公開 API 的一部分, 在自動產生的 API 文檔的類或者函數簽名中, 應該包含這個注解的信息。

注解可以用在類、函數、參數、變量(成員變量、局部變量)、表達式、類型上等。這個由該注解的元注解@Target定義。

@MagicClass class Foo @MagicConstructor constructor() {

    constructor(index: Int) : this() {
        this.index = index
    }

    @MagicClass var index: Int = 0
    @MagicFunction fun magic(@MagicClass name: String) {

    }
}

7. 單例模式(Singleton)與伴生對象(companion object)

7.1 單例模式

Kotlin中沒有靜態屬性和方法,但是也提供了實現類似單例的功能,使用object關鍵字聲明一個object對象。

object StringUtils{
    val separator: String = """\"""
    fun isDigit(value: String): Boolean{
        for (c in value) {
            if (!c.isDigit()) {
                return false
            }
        }
        return true
    }
}

fun main(args: Array<String>) {
    println("C:${StringUtils.separator}Users${StringUtils.separator}Denny") //打印c:\Users\Denny
    println(StringUtils.isDigit("12321231231"))  //打印true
}

我們反編譯后可以知道StringUtils轉成了Java代碼:

public final class StringUtils {
   @NotNull
   private static final String separator = "\\";
   public static final StringUtils INSTANCE;

   @NotNull
   public final String getSeparator() {
      return separator;
   }

   public final boolean isDigit(@NotNull String value) {
      Intrinsics.checkParameterIsNotNull(value, "value");
      String var4 = value;
      int var5 = value.length();

      for(int var3 = 0; var3 < var5; ++var3) {
         char c = var4.charAt(var3);
         if (!Character.isDigit(c)) {
            return false;
         }
      }

      return true;
   }

   static {
      StringUtils var0 = new StringUtils();
      INSTANCE = var0;
      separator = "\\";
   }
}

7.2 伴生對象

使用companion object來聲明:

class Utils{

    object StringUtils{
        val separator: String = """\"""
        fun isDigit(value: String): Boolean{
            for (c in value) {
                if (!c.isDigit()) {
                    return false
                }
            }
            return true
        }
    }

    companion object MiscUtils{
        fun isEmpty(value: String): Boolean {
            return value.isEmpty()
        }
    }
}

class Class{
    companion object {
        fun isEmpty(value: String): Boolean {
            return value.isEmpty()
        }
    }
}

fun main(args: Array<String>) {
    println(Utils.isEmpty("xx"))
    println(Utils.MiscUtils.isEmpty("xx"))

    println(Class.isEmpty("xx"))
}

一個類只能有一個伴生對象。

class Utils{

    val index:Int = 100

    object StringUtils{
        val separator: String = """\"""
        fun isDigit(value: String): Boolean{
            for (c in value) {
                if (!c.isDigit()) {
                    return false
                }
            }
            return true
        }
    }

    companion object MiscUtils{
        fun isEmpty(value: String): Boolean {
            return value.isEmpty()
        }
    }
}

class Class{

    companion object {
        
        val index:Int = 100
        
        fun isEmpty(value: String): Boolean {
            return value.isEmpty()
        }
    }
}

fun main(args: Array<String>) {
    println(Utils.MiscUtils.isEmpty("xx"))
    println(Utils.isEmpty("xx"))  //因為只能有一個伴生對象,所以可以省略到伴生對象的類名

    println(Class.Companion.isEmpty("xx"))  //可以使用默認的Companion來訪問伴生對象。
    println(Class.Companion.index)
    println(Class.isEmpty("xx"))  //當然也可以省略
}

伴生對象的初始化是在相應的類被加載解析時,即使伴生對象的成員看起來像其他語言的靜態成員,在運行時他們仍然是真實的對象的實例成員。另外:
@JvmField注解,會在生成的Java類中使用靜態字段。

class ClassA{
    @JvmField val separator: String = """\"""
}

上面這段會被轉成下面的Java代碼:

class ClassA{
    @JvmField val separator: String = """\"""
}

@JvmStatic注解,會讓你在單例對象或者伴生對象中生成對應的靜態方法。

jvmstatic

jvmstatic

8. 密封類(sealed)

聲明一個密封類,需要在類名前面添加sealed修飾符,密封類的所有子類必須與密封類在同一個文件中聲明:

sealed class Expression

class Unit : Expression()
data class Const(val number: Double) : Expression()
data class Sum(val e1: Expression, val e2: Expression) : Expression()
data class Multiply(val e1: Expression, val e2: Expression) : Expression()
object NaN : Expression()

密封類的主要使用場景其實更像是枚舉類的擴展,比如使用when表達式時。

fun eval(expr: Expression): Double = when (expr) {
    is Unit -> 1.0
    is Const -> expr.number
    is Sum -> eval(expr.e1) + eval(expr.e2)
    is Multiply -> eval(expr.e1) * eval(expr.e2)
    NaN -> Double.NaN
    // 不再需要 `else` 子句,因為我們已經覆蓋了所有的情況
}

9. 數據類(data class)

在Kotlin中從語言層面上支持創建一個領域實體類,也叫做數據類:

data class Book(val name: String)
data class Fook(var name: String)
data class User(val name: String, val gender: String, val age: Int) {
    fun validate(): Boolean {
        return true
    }
}

定義好數據類后,會自動生成以下函數:

  • equals()、hashCode()和toString()
  • componentN() 函數 : 按聲明順序對應于所有屬性component1()、component2() ...
  • copy()函數
    如果我們自己實現了以上函數,那么Kotlin就不會生成,會使用我們實現的方法。

數據類有以下限制:

  • 主構造函數必須至少有一個參數。
  • 主構造函數所有參數必須要標記為val或者var。
  • 數據類不能是抽象、開放、密封或者內部類,

9.1 數據類的解構

因為數據類自動生成componentN()函數,所以我們可以使用解構功能:

val helen = User("Helen", "Female", 15)
val (name, gender, age) = helen
println("$name, $gender, $age years of age")

輸出:

Helen, Female, 15 years of age

10. 嵌套類(Nested Class)

class NestedClassesDemo {
    class Outer {
        private val zero: Int = 0
        val one: Int = 1

        class Nested {
            fun getTwo() = 2
            class Nested1 {
                val three = 3
                fun getFour() = 4
            }
        }
    }
}

我們可以這樣來訪問內部類:

val one = NestedClassesDemo.Outer().one
val two = NestedClassesDemo.Outer.Nested().getTwo()
val three = NestedClassesDemo.Outer.Nested.Nested1().three
val four = NestedClassesDemo.Outer.Nested.Nested1().getFour()

但是普通的嵌套類,并不會持有外部類的引用:

class NestedClassesDemo {
class Outer {
        private val zero: Int = 0
        val one: Int = 1

        class Nested {
            fun getTwo() = 2

            fun accessOuter() = {
                println(zero) // error, cannot access outer class
                println(one)  // error, cannot access outer class
            }
        }
    }
}

如果要訪問Outer類的變量,那么我們需要把Nested類標記為內部類。

class NestedClassesDemo {
class Outer {
        private val zero: Int = 0
        val one: Int = 1

        inner class Inner {
            fun accessOuter() = {
                println(zero) // works
                println(one) // works
            }
        }
}
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容