来自 电脑知识 2019-11-08 01:17 的文章
当前位置: 威尼斯国际官方网站 > 电脑知识 > 正文

Kotlin面向对象编程笔记,详解Kotlin中的面向对象

Kotlin中的面向对象

Kotlin是基于Java的语言,所以Kotlin也是一种面向对象的语言,如果你熟悉Java,你应该很快可以上手,这篇文章假设你已经熟悉Java语言了。

面向对象

1. 类与构造函数

Kotlin中的类与接口和Java中的有些区别。

  • Kotlin中接口可以包含属性申明。
  • Kotlin的类申明,默认是final和public的。
  • Kotlin的嵌套类并不是默认在内部的,他们不包含外部类的隐私引用。
  • Kotlin的构造函数,分为主构造函数和次构造函数。
  • Kotlin中可以使用data关键字来申明一个数据类。
  • Kotlin中可以使用object关键字来表示单例对象、伴生对象等。

Kotlin的类由下面几部分组成:

  • 构造函数和初始化块。
  • 属性
  • 函数
  • 嵌套类和内部类
  • 对象申明

面向对象的含义大家应该并不陌生,通过将事物抽象成对象,大大简化了程序的开发难度。我们常用的Java、Python、C++都属于面向对象的编程语言。Kotlin和java很相似,也是一种面向对象的语言。作为Kotlin中最重要的一部分,我们可以通过了解Kotlin的OOP进而了解这门语言,本文将从类、属性、接口、对象等多个方面介绍Kotlin的面向对象的特性。

1.1 类的声明

使用class关键字声明,这个跟Java是一样的,如果你申明的类不包含任何东西的话,可以自己class后面直接写上类名。

class Person

1.2 构造函数

在Kotlin中可以有一个主构造函数,一个或者多个次构造函数。

和大部分语言类似,Kotlin使用class作为类的关键字,当我们声明一个类时,需要通过class加类名的方式实现:

主构造函数

主构造函数直接跟在类名后面,如下:

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")
    }
}

主构造函数的参数可以在初始化块中使用,也可以在类体内申明的属性初始化器中使用。

class ClassDemo{ 

} 
次构造函数

我们也可以在类体中使用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关键字以及后面的类名,同时也可以根据需要增加类头(用来声明一些参数)和类体(花括号{}中包含的内容,可省略)。

私有的主构造函数

如果我们不希望这个类被实例化,我们可以申明如下:

class CantCreateMe private constructor()

图片 1

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会简洁很多。

与java不同的是,虽然都可以包含多个构造函数,但java中的各个构造函数都是平级的,而Kotlin中却分为了两级(主构造函数和二级构造函数),主构造函数是包含在类头中的,需要在init方法中实现额外的操作,而二级构造函数可以在函数体中实现所有操作

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})")
}
//主构造函数,constructor和init都可省略 
class ClassDemo constructor(type:String){ 
 init{ 
 System.out.println(type) 
 } 
} 

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关键字。
class ClassDemo(type:String, var prop2: String = type){ 
 var prop1 = type 
} 

3. 接口

Kotlin中的接口跟Java8的接口类似。他们都可以包含抽象方法以及实现的方法。

interface UserService{
    var version: String

    fun save(user: User)

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

二级构造函数必须包含constructor

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()
    }

}
class ClassDemo{ 
 constructor(type:String){ 
  System.out.println(type) 
 } 
} 

3.2 覆盖冲突

在上面的例子中,UserService和ProductService都有log()这个方法的实现,我们在重写UserProductServiceImpl的log()方法时,如果我们调用super.log() 。编译器是不知道我们要调用那个父类的log()方法的,这样就产生了覆盖冲突。

图片 2

覆盖冲突

解决的办法也很简单,我们使用下面的这种语法来指定调用那个父类的log()方法。

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

对于有主构造函数的类而言,每个二级构造函数都要,或直接或间接通过另一个二级构造函数代理主构造函数。在同一个类中代理另一个构造函数使用 this 关键字:

3.3 接口中的属性

接口中的属性,可以是抽象的,或者是提供访问器的实现。因为接口没有状态,所以接口的属性是无状态的。请看上面的例子。

//包含主构造函数的二级构造函数 
class ClassDemo(type:String){ 
 constructor(type:String, param:String):this(type){ 
  System.out.println(type + param) 
 } 
} 

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()
}

无论有没有声明主构造函数,Kotlin都会提供一个构造函数的方法,有时可能不希望将某个类的构造函数暴露出来(比如实现单例模式时),那么就需要对主构造函数进行处理

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 
}
class ClassDemo private constructor(){ 

} 

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)

var demo = ClassDemo("hello world") 
val demo = ClassDemo("hello","world") 

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:UsersDenny
    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 = "\";
   }
}

我们注意到,在Kotlin中无论是声明属性还是创建实例,会有两种方式,var和val,val类似于java中的final,通过它声明的内容是无法变化的,而var和val相反,声明的内容既可以读取也可以修改。

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注解,会让你在单例对象或者伴生对象中生成对应的静态方法。

图片 3

jvmstatic

图片 4

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` 子句,因为我们已经覆盖了所有的情况
}

本文由威尼斯国际官方网站发布于电脑知识,转载请注明出处:Kotlin面向对象编程笔记,详解Kotlin中的面向对象

关键词: