入行没几年的小码农,近期学习Kotlin,做一份笔记记录,此文依据《Kotlin实战》这本书的流程记录,部分示例内容均摘自《Kotlin实战》,记下自己的理解,本篇记录Kotlin中对类,对象,接口的使用。
Kotlin学习笔记系列
新手上路,Kotlin学习笔记(一)-- Kotlin入门介绍
新手上路,Kotlin学习笔记(二)---方法(函数)部分
新手上路,Kotlin学习笔记(三)---类、对象、接口
新手上路,Kotlin学习笔记(四)---Lambda表达式在Kotlin中的使用
新手上路,Kotlin学习笔记(五)---Kotlin中的类型系统
新手上路,Kotlin学习笔记(六)---运算符重载和其它约定
新手上路,Kotlin学习笔记(七)---Lambda作为形参和返回值的使用
新手上路,Kotlin学习笔记(八)---泛型的使用
新手上路,Kotlin学习笔记(九)---注解和反射
一、接口和类的继承
在我们的开发过程中,不可能没有接口的,那么,我们就先来看一下Kotlin中接口是如何使用的
Kotlin中声明接口仍然使用interface关键字,示例如下
interface PrintListener {
fun printByListener()
}
实现接口也很简单,只需要在类的声明后面加上冒号“:”接口名称即可,实现方法的关键字是override,示例如下
class Student (var name : String) : PrintListener
{
override fun printByListener() {
println(this.toString())
}
}
在Kotlin中,实现接口和继承父类均以冒号表示,但是和Java一样,只能继承一个父类,可以实现多个接口。
同时,Kotlin中的接口可以拥有一个默认的实现,示例如下
interface PrintListener {
fun printByListener()
fun printDefault() = println("default print")
}
那么,如果有两个接口,都写了相同名称的默认方法,同时实现这两个接口的类,会怎么样呢?
interface PrintCallback {
fun printDefault() = println("default printCallback")
}
class Student (var name : String) : PrintListener,PrintCallback
{
override fun printByListener() {
println(this.toString())
}
}
此时,编译器将提示错误:Class 'Student' must override public open fun printDefault(): Unit defined in com.mtf.kotlin.PrintListener because it inherits multiple interface methods of it
我们必须给该类提供一个重名方法的实现
override fun printDefault() {
super.printDefault()
super.printDefault()
println("Stduent printDefault")
}
在调用接口的默认实现时,可以在super后使用<>区分是哪个接口的实现。
二、修饰符
Java中如果不想让一个类被继承或者一个方法被子类重写,那么会使用final关键字进行修饰。在Kotlin中,final拥有同样的效果,不同的是,Kotlin中的类和方法,默认都是final的,如果我们想让这个类被继承,某个方法被重写,我们需要使用关键字open进行修饰。
open class Person(var age : Int = 0, var name : String = "", var addr : String = "") //open 修饰之后可以被子类继承
{
open fun showName() = println(name) //open修饰之后的可以被子类重写
fun showAddr() = println(addr) //默认为final的,不能被子类重写
}
class Student : Person(),PrintListener
{
final override fun printByListener() = println(this.toString())//实现接口open的方法,默认是open的,如果不想被再次重写,需要显式使用final
override fun showName() {
print("Student")
super.showName()
}
}
抽象类用abstract声明,抽象方法默认是open的,不再举例了。
相对于Java中的权限修饰符,public,private等,Kotlin中也有自己的修饰符,其中public、private与Java中的使用效果一样,不同的是,Java中的default修饰符在Kotlin中没有,Kotlin中使用了新的修饰符internal,该修饰符的效果是只在模块内部课件,这个模块就是一起编译的一组Kotlin文件,可以理解为一个项目,这样外部代码将类和你的包名一致,也无法使用该权限下的内容。protected在Kotlin中只是子类可见,不再同一个包中可见。
另外,Kotlin中的默认声明是public的,并且允许在顶层声明为private这样该内容只能在当前文件内可见。
三、内部类和嵌套类
和Java中相同,Kotlin也允许在一个类的内部声明另一个类,Kotlin中有两种声明的方式,这两种方式将有不同的作用。
class MyKotlinView(context: Context? = null, attrs: AttributeSet? = null
, defStyleAttr: Int = 0, defStyleRes: Int = 0)
: View(context, attrs, defStyleAttr, defStyleRes) {
var content : String = ""
class Bean {
val name : String
get() = ""
}
inner class Entry{
val name : String
get() = [email protected]
}
}
由上面示例可以看到,两种声明的方法,区别在于第二个类声明时使用了inner关键字。
在Kotlin中,默认声明的叫做嵌套类,嵌套类是不持有外部类的对象的,所以我们无法获取到外部类MyKotlinView的content内容,类似于Java中的静态内部类。而如果使用inner声明,就是我们Java中通常使用的内部类,将会持有外部类的对象,使用 this@外部类名 的方式获取外部类的对象
四、密封类
当我们在Java中使用instance of 去判断一个对象是某个子类的时候,会使用if ... else if ... else这种方式,在最后总会写一个空的else,因为我们知道已经把所有子类都遍历完了。同时,如果添加了一个新的子类,又有可能在某个地方忘记这些if判断,引起Bug出现,现在,Kotlin就为这种烦恼提供了一个很好的解决方案,让我们一起看一下密封类的使用。
Kotlin中在声明类的时候,可以使用sealed关键字,使用之后这个类就是一个密封类,然后在密封类中,将所有的子类都使用嵌套类的方式列出来。这样做之后,在使用when的时候,我们可以只写子类的分支,不再需要默认的else分支,同时,如果我们新添加了子类,那么when语句没有对应添加的话,编译会报错!这样就避免了上文中提到的风险和隐患。
密封类的声明方式如下,使用sealed关键字后,该类默认是open的
sealed class Paper {
class Book() : Paper()
class Newspaper() : Paper()
}
这样在对该类型的对象使用when循环时,不需要写最后的else分支,因为编译器已经认为我们覆盖了所有的可能性。
fun checkPaper(paper : Paper)
{
when(paper)
{
is Paper.Book -> println("is Book")
is Paper.Newspaper -> println("is Newspaper")
}
}
五、constructor和init关键字
顾名思义,constructor就是构造方法的意思,我们可以用constructor声明不同的构造方法,虽说我们应该使用直接指定默认值的方式,让类只有一个构造方法比较好。
init关键字后可以跟一个代码块,这个代码块就是在执行完构造方法之后执行的代码,也是我们通常在Java类中构造方法执行之后初始化该类一些数据的地方。
class Teacher
{
constructor(name : String , age : Int)
constructor(nickName : String, id : String)
init {
println("init")
}
}
当我们需要向Java那样使用单例模式的时候,如何声明一个私有构造方法的类呢?让我们看下面的实现方式
class User private constructor()
{
}
很简单,在constructor关键字前加上权限关键字private即可声明一个构造方式是私有的类。
六、接口中的抽象属性(变量)
在Kotlin中,接口也可以拥有抽象属性,这样写的话,实现该接口的类,必须提供获取这个属性的方式,可以使用多种方法进行这个赋值,示例如下
interface User
{
val nickName : String
}
class PrivateUser(override val nickName: String) : User //构造方法中声明
class SubscribingUser(val email : String) : User
{
override val nickName: String
get() = email.substringBefore("@") //通过get方法赋值
}
class FacebookUser(val accountId : Int) : User
{
override val nickName: String = getFacebookName() //属性初始化的时候赋值
fun getFacebookName() : String
{
return ""
}
}
七、data关键字
在Java中,对于toString(),equals(),hashCode()三个方法我们都很熟悉了,每次在建立一个数据类的时候,我们都要重写这三个方法,如果当数据类中新添加了数据,还要再次改一次,非常麻烦。
对于这种没有重复性极高的工作,Kotlin提供了data关键字作为替代,从此我们可以告别这些额外的工作量。
当我们对一个类声明为data类型,Kotlin就会自动帮我们生成这三个方法,简单而快捷!
data class Client(val username : String , val password : String)
在使用data关键字后,Kotlin还会额外帮我们创建一个copy的方法,该方法可以创建一个相同的实例,修改我们传入的参数,因为为了安全性,Kotlin建议我们将存储数据的变量设置为val的,此时的copy方法就可以在需要修改某个属性的时候使用,可以讲其他的原有属性均保留下载,并且是创建一个新的实例,不会影响到原有对象在其他地方的使用。
使用方式如下
val client = Client("123","123")
val copyClient = client.copy("234") //没有标注修改的对象,默认是第一个
println(client) //打印Client(usernam=123,password=123)
println(copyClient)//打印Client(usernam=234,password=123)
八、by关键字
对于一个已经封装好的类,我们在使用的时候,又需要在这个类上面扩展一些功能方法,特别是需要和某些东西结合在一起的时候,如我们对某一个接口进行包装。创建一个类,这个类中有一个变量类型是这个接口,其他还会有一些数据之类的变量,这种方式我们在开发中经常使用,在使用的时候,通常我们又会为需要包装的那个接口提供很多同名方法的入口,只为调用这个接口真正的方法,书写了很多额外的代码,很不方便,看起来也不美观
而在Kotlin中,为我们提供了by关键字来解决这种需要额外书写大量同名方法的困扰。 当我们使用by关键字时,也可以对我们需要特殊定制的方法提供不同的实现,使用示例如下
class MyList(val innerList : MutableCollection = ArrayList()) : MutableCollection by innerList
{
var objAdded : Int = 0;
override fun add(element: String): Boolean {
objAdded++
return innerList.add(element)
}
override fun addAll(elements: Collection): Boolean {
objAdded += elements.size
return innerList.addAll(elements)
}
}
九、object关键字
在Java中我们在某些场景需要只有一个对象,这时候我们会使用单例模式,创建单例模式的时候有很多种写法,也有各种写法的评价,我们此处对于Java创建单例模式的方式优缺点不进行解释。重点是在Kotlin中,创建单例模式我们只需要使用一个关键字即可!
在声明一个类的时候,我们只需要使用object进行声明,这个类就是单例模式的了(其实在Kotlin中认为object关键字是声明了一个对象,只是一个对象当然就是单例的了),使用object声明的对象,可以有方法,属性,初始化语句块等,唯一与类不同就是不允许建立构造方法,因为在声明的时候就已经创建了对象,外部也没有也不需要调用构造方法,因此构造方法是没有意义的。
下面看一个示例,我们在进行集合排序的时候,会传入一个Comparator对象,这个非常适合做成一个单例的对象。
object MyComparator : Comparator
{
override fun compare(o1: String?, o2: String?): Int {
if (null != o1) {
return if(null != o2) {
o1.compareTo(o2)
} else {
1
}
}
else if(null != o2)
{
return -1
}
else
{
return 0
}
}
}
另外,object关键字,还可以在类内部声明对象使用,此时,它在容器类中不同的实例中,仍然只有一个实例对象。
object关键字在类的内部使用时,还可以在前面添加companion关键字,此时的object对象就成为了一个伴生对象,伴生对象中的方法,可以直接使用容器类名 + . + (伴生对象名 + .) + 方法名的方式调用,并且伴生对象可以调用容器类中的私有构造方法,这样我们就可以在Kotlin中实现工厂模式了,示例如下
class Fruit private constructor(val name : String){
companion object Creater{
fun creatApple() = Fruit("Apple")
fun creatBanana() = Fruit("Banana")
}
}
调用方式如下
val apple = Fruit.creatApple()
val banana = Fruit.Creater.creatBanana()
tips:当我们不为伴生对象命名的时候,会有默认的名称Companion
伴生对象同样也可以实现接口和为伴生对象创建扩展函数,此处不再过多介绍,大家可以自己试一下。
在Android开发中,有太多太多的匿名内部类,那么在Kotlin中,我们就可以用刚认识的object关键字来创建匿名内部类,让我们看下面示例
val txt = findViewById(R.id.txt) as TextView
txt.setOnClickListener(object : View.OnClickListener{
override fun onClick(v: View?) {
println("txt onClick")
}
})
可以看出,我们使用了object声明了一个匿名对象,实现了onClickListener接口,然后实现了onClick方法,这样就创建了一个匿名内部类,使用起来很简单。
//下一章 新手上路,Kotlin学习笔记(四)---Lambda表达式在Kotlin中的使用