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
            }
        }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,408评论 4 371
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,690评论 2 307
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 112,036评论 0 255
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,726评论 0 221
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,123评论 3 296
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,037评论 1 225
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,178评论 2 318
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,964评论 0 213
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,703评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,863评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,333评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,658评论 3 263
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,374评论 3 244
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,195评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,988评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,167评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,970评论 2 279

推荐阅读更多精彩内容