Kotlin基础——接口和类

接口

  • 使用 : 表示继承关系,只能继承一个类,但可以实现多个接口

  • override修饰符表示重写

  • 可以有默认方法,若父类的默认方法冲突,则需要子类重写,使用super<XXX>.xxx()调用某一父类方法

    interface Focusable {
    fun focus()
    fun show() = println("Focusable")
    }

    class Button : Clickable, Focusable {
    override fun click() {
    TODO("Not yet implemented")
    }

    复制代码
      override fun focus() {
          TODO("Not yet implemented")
      }
    
      override fun show() {
          super<Clickable>.show()
          super<Focusable>.show()
      }

    }

接口中可以声明域,每个子类都要初始化接口中的域

复制代码
interface User {
    val name: String
}

class Person(override val name: String) : User

class Man(val email: String) : User {
    override val name: String
        get() = email.substringBefore("@")
}

class WonMan() : User {
    override val name: String = "A"
}

接口中也可以使用getter和setter,前提是不引用变量

复制代码
interface User {
    val email:String
    val name: String
    get() = email.substringBefore('@')
}

bean类

只有数据没有其他代码的对象通常叫做值对象,如JavaBean

复制代码
public class Person {
    private String name;

    private final int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }
}

使用过程如下

复制代码
Person person = new Person("java", 8);

person.setName("kotlin");
System.out.println(person.getName());
System.out.println(person.getAge());

将上述Java复制到代码.kt文件,会触发自动转换(.kt不要求类名和文件名一致,可将多个类放到同一文件,且文件名随意

转换后的代码如下,name为var变量(默认带有getter和setter),而age为val变量(只有getter)

复制代码
class Person(
    var name: String,
    val age: Int
) 

使用方法如下

复制代码
val person = Person("java", 8);

person.name = "kotlin"
println(person.name)
println(person.age)

如果一个属性可以根据其他属性计算,可使用自定义getter

复制代码
class Rectangle(val height: Int, val width: Int) {
    val isSquare: Boolean
        get() {
            return height == width
        }
}

类的重写

类和方法默认都是final的,否则需要使用open修饰符

复制代码
open class Button {
    fun click() {}
    open fun focus() {}
}

open class MyButton : Button() {
    override fun focus() {
        super.focus()
    }
}

抽象类

抽象方法默认为open,其他的可有可无

复制代码
abstract class Button {
    abstract fun click()
    open fun focus() {}
    fun press() {}
}

open class MyButton : Button() {
    override fun click() {
        TODO("Not yet implemented")
    }
}

可见性

Kotlin没有包的概念,internal为模块可见性

  • private类在Java中会被编译成protect
  • internal类或域在Java中会被编译成public

嵌套类

在一个类中声明另一个类

  • 在Java中,未用static声明的类为内部类(含有外部类的隐式引用),加上static的类为嵌套类(不含有外部类的隐式引用)
  • 而Kotlin相反,未用inner声明的类为嵌套类(不含有外部类的隐式引用),加上inner的类为内部类(含有外部类的隐式引用)
  • 嵌套类不能访问外部类的实例,外部类不能访问嵌套类中的private域

如果需要使用内部类存储序列化信息,需要声明为static转为嵌套类,否则将无法序列化

复制代码
public class Button {

    private boolean isClickable;

    public ButtonState getCurrentState() {
        return new ButtonState(this.isClickable);
    }

    public void restoreState(ButtonState state) {
        this.isClickable = state.canClick;
    }

    public static class ButtonState implements Serializable {
        private boolean canClick;

        public ButtonState(boolean isClickable) {
            this.canClick = isClickable;
        }
    }
}

而在Kotlin中正好相反,默认为加上static的嵌套类,如果需要成为内部类则应加上inner修饰符,使用this@Outer访问外部类实例

复制代码
class Button {
    private var isClickable = false

    val currentState: ButtonState
        get() = ButtonState(isClickable)

    fun restoreState(state: ButtonState) {
        isClickable = state.canClick
    }

    class ButtonState(val canClick: Boolean) : Serializable

    inner class TestState(val canClick: Boolean) {
        fun getOuterReference(): Button = this@Button
    }
}

受限的类继承结构

在如下结构和判断中,若新增了类,但却没有新增分支,会导致其调用走到else

复制代码
interface Expr
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr

fun eval(e: Expr): Int =
    when (e) {
        is Num -> {
            e.value
        }
        is Sum -> {
            eval(e.left) + eval(e.right)
        }
        else -> {
            throw IllegalArgumentException("")
        }
    }

通过sealed修饰父类,将子类嵌套在父类,可避免额外的else分支,当新增子类时,when表达式会提示编译失败

复制代码
sealed class Expr {
    class Num(val value: Int) : Expr()
    class Sum(val left: Expr, val right: Expr) : Expr()
}

fun eval(e: Expr): Int =
    when (e) {
        is Expr.Num -> {
            e.value
        }
        is Expr.Sum -> {
            eval(e.left) + eval(e.right)
        }
    }

主构造函数

class+类名+[修饰符]+constructor()表示主构造函数,init表示初始化语句块

复制代码
class User private constructor(name: String) {
    val name: String

    init {
        this.name = name
    }
}

若主构造函数没有注解或可见性修饰符,上述还可以省略为

复制代码
class User(name: String) {
    val name: String = name
}

如果参数用相应的构造方法参数来初始化,还可以简化为

复制代码
class User(val name: String) 

也可以加上默认参数,若所有构造方法参数都有默认值,会生成一个额外的不带参数的构造方法来使用所有的默认值

复制代码
class User(val name: String = "A")

若类有父类,可在继承列表中调用父类构造函数初始化父类,如下传入Person的参数name会被用于构造User

复制代码
open class User(val name: String)

class Person(name: String) : User(name) {

}

从构造函数

当需要多个构造函数时,可使用从构造函数

复制代码
open class User {
    private val name: String
    private var age: Int = 0

    constructor(name: String) {
        this.name = name
    }

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

可使用this和super调用自身及父类的构造从构造函数,若类没有主构造函数,那么每个从构造函数必须初始化父类

复制代码
open class User {
    private val name: String
    private var age: Int = 0

    constructor(name: String) {
        this.name = name
    }

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

class Person : User {
    constructor(name: String) : this(name, 18) {
    }

    constructor(name: String, age: Int) : super(name, age) {

    }
}

通过setter/getter访问域的值

在setter中可以通过标识符field读写域的值,getter中只能读

复制代码
class User(val name: String) {
    var address: String = "unspecified"
        set(value: String) {
            println(
                """
                |Address was changed for $name:
                |"$field" -> "$name"
            """.trimMargin()
            )
            field = value
        }
}

如上,在更新address值时

复制代码
val user = User("Tom")
user.address = "A Street"

额外打印一些信息

复制代码
Address was changed for Tom:
"unspecified" -> "Tom"

修改getter/setter可见性

如下,使用内部计数字符个数,不能对其setter

复制代码
class lengthCounter {
    var counter: Int = 0
        private set

    fun addWord(word: String) {
        counter += word.length
    }
}

data类

data修饰符将会为类自动生成通用方法的实现,如toString()、equals()和hashCode(),会将主构造函数中申明的属性纳入考虑

复制代码
data class Person(val name: String, val age: Int)

Kotlin还为data类新增了copy(),用于返回相同的对象,且互不影响

复制代码
val A = Person("A", 18)
val B = A.copy();
println(A)
println(B)

通过by实现类委托

在新增对象功能时,原有功能通常会被委托给原对象,这样会出现大量样板代码

复制代码
class MyCollection<T> : Collection<T> {
    private val innerList = arrayListOf<T>()
    override val size: Int
        get() = innerList.size

    override fun contains(element: T): Boolean {
        return innerList.contains(element)
    }

    override fun containsAll(elements: Collection<T>): Boolean {
        return innerList.containsAll(elements)
    }

    override fun isEmpty(): Boolean {
        return innerList.isEmpty()
    }

    override fun iterator(): Iterator<T> {
        return innerList.iterator()
    }
}

可通过 by 将接口的实现委托到另一个对象,编译器会自动实现上面的方法,也可自行重写方法

复制代码
class MyCollection<T>(innerList: Collection<T> = ArrayList<T>()) : Collection<T> by innerList {

}

object关键字

定义一个类并同上创建一个实例

使用对象声明创建单例

一个对象声明不允许有构造方法,在定义时即被创建,不需要再调用构造方法

复制代码
object Person {
    val name: String = ""
    fun printName() {

    }
}

可使用对象.属性/方法来访问

复制代码
Person.name = "A"
Person.printName()

当实现一个接口但不包含任何状态时,通常使用对象声明,如实现一个比较器

复制代码
object CaseInsensitiveFileComparator : Comparator<File> {
    override fun compare(p0: File, p1: File): Int {
        return p0.path.compareTo(p1.path, true)
    }
}

在使用时可直接传递到接收Comparator的函数

复制代码
println(CaseInsensitiveFileComparator.compare(File("/User"), File("/user")))

val files = listOf(File("/Z"), File("/a"))
println(files.sortedWith(CaseInsensitiveFileComparator))

在Java中被编译成通过静态字段持有单例,则通过如下调用

复制代码
CaseInsensitiveFileComparator.INSTANCE.compare(File("/User"), File("/user"));

伴生对象代替静态方法和字段

创建工厂方法

复制代码
class User {
    val name: String

    constructor(email: String) {
        name = email.substringBefore('@')
    }

    constructor(id: Int) {
        name = id.toString()
    }
}

使用companion定义伴生对象,将上面改成工厂方法实现

复制代码
class User private constructor(val name: String) {
    companion object {
        fun newUserByEmail(email: String) = User(email.substringBefore('@'))
        fun newUserById(id: Int) = User(id.toString())
    }
}

在使用时,可通过类名称访问对象的方法和属性

复制代码
val user1 = User.newUserByEmail("xxx@gmail.com")
val user2 = User.newUserById(123)

伴生对象在Java中通过如下方式调用,若存在名字,则代替Companion

复制代码
User.Companion.newUserById(123)

实现接口

伴生对象也可实现接口

复制代码
interface Factory<T> {
    fun newUserByEmail(email: String): T
    fun newUserById(id: Int): T
}

class User(val name: String) {
    companion object : Factory<User> {
        override fun newUserByEmail(email: String) = User(email.substringBefore('@'))
        override fun newUserById(id: Int) = User(id.toString())
    }
}

对于如下方法

复制代码
fun <T> createUser(factory: Factory<T>) {

}

可直接将伴生对象所在类名字当作实现了该接口的对象实例来使用

复制代码
createUser(User)

扩展函数

当需要定义通过类调用的方法时,可以通过伴生对象函数来实现

复制代码
class User(val name: String) {
    companion object {

    }
}

fun User.Companion.printName() {
    
}

使用如下

复制代码
User.printName()

对象表达式创建匿名对象

object声明匿名对象代替Java的匿名内部类

复制代码
interface OnClickListener {
    fun onclick()
}

fun setOnClickListener(listener: OnClickListener) {

}

对于上面常用的监听事件,可传入声明匿名对象

复制代码
setOnClickListener(object : OnClickListener {
    override fun onclick() {
    
    }
})

若需要命名则提取出来,匿名对象可以实现多个接口

复制代码
val listener = object : OnClickListener {
    override fun onclick() {
    }
}
setOnClickListener(listener)
相关推荐
Kapaseker44 分钟前
一杯美式搞懂 Any、Unit、Nothing
android·kotlin
Kapaseker1 天前
一杯美式搞定 Kotlin 空安全
android·kotlin
FunnySaltyFish2 天前
什么?Compose 把 GapBuffer 换成了 LinkBuffer?
算法·kotlin·android jetpack
Kapaseker2 天前
Compose 进阶—巧用 GraphicsLayer
android·kotlin
Kapaseker3 天前
实战 Compose 中的 IntrinsicSize
android·kotlin
A0微声z5 天前
Kotlin Multiplatform (KMP) 中使用 Protobuf
kotlin
alexhilton5 天前
使用FunctionGemma进行设备端函数调用
android·kotlin·android jetpack
lhDream6 天前
Kotlin 开发者必看!JetBrains 开源 LLM 框架 Koog 快速上手指南(含示例)
kotlin
RdoZam6 天前
Android-封装基类Activity\Fragment,从0到1记录
android·kotlin
Kapaseker6 天前
研究表明,开发者对Kotlin集合的了解不到 20%
android·kotlin