Kotlin学习之路:继承
Posted rockyou666
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kotlin学习之路:继承相关的知识,希望对你有一定的参考价值。
文章目录
前言
Kotlin的继承和Java的继承一样都是单继承,区别在于Kotlin用:
来代替了extends
一. 类的继承
Kotlin用:
表示继承,Java用exteds
表示继承。
// 父类Person
open class Person()
// 子类Man
class Man() : Person()
上面是最简单的Kotlin继承的例子。这里还需要注意的是在Kotlin中类默认都是final的(代表该类不让继承),需要在class
关键词前面加上open
,表示该类可以被其他类继承。和Java一样Kotlin也是单继承的。
和Java的继承一样,子类可以使用父类的属性和方法:
open class Person
var name: String = ""
var age: Int = -1
fun eat()
println("eat food!")
class Man() : Person()
fun main()
val man = Man()
man.age = 22
man.name = "Tom"
println("age is $man.age")
println("name is $man.name")
man.eat()
输出结果:
age is 22
name is Tom
eat food!
从上面的代码可以看出,子类Man
可以使用父类Person
的属性和方法。
二. 继承中的构造函数
Kotlin在继承情况的下,构造函数的语法是怎么样的?首先来看看Java是如何处理的。
2.1 继承中Java的构造函数
Java是通过super
来实现对父类构造函数的访问和传值,类似如下代码:
// Person类(父类)
public class Person
private String name;
private int age;
public Person(String name, int age)
this.name = name;
this.age = age;
// Man类(子类)
public class Man extends Person
public Man(String name, int age)
super(name, age);
通过关键字super
可以将参数传递给父类,androidStudio中可以通过点击Create constructor matching super
自动生成构造函数。接下来看看Kotlin是如何处理这个问题的。
2.2 继承中Kotlin的构造函数
在Kotlin中实现这种功能,只需要极少的代码量就可以实现:
// Person
open class Person(var name:String, var age:Int)
// Empty Code
// Man类
class Man(name: String, age: Int): Person(name, age)
// Empty Code
fun main()
val man = Man("Tom", 22)
println("name is $man.name age is $man.age")
打印结果:
name is Tom age is 22
通过Man类的构造函数传递参数name
和age
,再通过: Person(name, age)
传递到父类。
有人可能要问了,如果不加上后面的: Person(name, age)
是否可行?
显然是不行的,Man
类的构造函数没有var
,那么它们就只是Man
类构造函数的参数,不是类的成员变量。
那么如果Man
类加上var
是否可以?
答案也是不行的,这样父类和子类就会有两套成员变量,肯定会报错的。但是,我们可以重写父类的成员变量,后面会详细讲到。
2.3 Kotlin多个构造函数的情况
Kotlin是有主次构造函数之分的,如果一个父类有多个构造函数,那么子类是如何继承的呢?
第一种写法:
// Person类,这里有三个构造函数
open class Person(var name: String, var age: Int)
constructor(name: String): this(name, -1)
constructor(age: Int): this("", age)
// Man类
class Man(name: String, age: Int) : Person(name, age)
// Empty Code
父类有多个构造函数的情况,可以和上面一样这么写。但是,这样就不能以次构造函数的方式去创建一个对象。
fun main()
val man = Man("Tom") // 报错!
println("name is $man.name age is $man.age")
造成这种情况的原因是,我们只重写了父类的主构造函数,没有重写父类的次构造函数。针对这种情况,我们可以这样写。同样是上面的例子,代码如下:
// Person类,这里有三个构造函数
open class Person(var name: String, var age: Int)
constructor(name: String): this(name, -1) // 次构造函数
constructor(age: Int): this("", age) // 次构造函数
// Man类
class Man: Person
constructor(name: String, age: Int) : super(name, age) // 对应Person类的主构造函数
constructor(name: String) : super(name) // 对应Person类的constructor(name: String)
constructor(age: Int) : super(age) // 对应Person类的constructor(age: Int)
直接将父类的三个次构造函数再复写一遍,用super
指向父类的构造函数就行了。
为了方便快速的构建,可以利用AndroidStudio的快捷创建方式:
首先在类的内部点击鼠标右键,弹出如下菜单:
在弹出的菜单中点击Generate按钮,再弹出以下菜单:
最后点击Secondary Constructor按钮,选择你需要重写的构造函数:
通过这种快捷方式可以快速的重写父类的所有主次构造函数。
三. 重写和重载
这一章节重点讲解重写,至于重载,可以看我之前的文章Kotlin学习之路(三):函数
中的1.3小结
3.1 方法的重写
代码还是上面的例子,只不过父类里面多一个测试方法:
// Person
open class Person(var name:String, var age:Int)
open fun testMethod() // 注意这里加上了open,表示可以被重写。默认情况下是fina
println("父类test!")
// Man类
class Man(name: String, age: Int): Person(name, age)
override fun testMethod() // 类似Java的override重写标志
println("子类test!")
fun main()
val man = Man("Tom", 22)
man.testMethod()
输出结果:
子类test!
这里需要着重介绍的一点的是,Kotlin的方法不加修饰符之前都是public final
的,特别是final
,所以在需要被重写的方法中加上open
修饰符。
重写方法需要遵循 两同、两小、一大的原则。
- 两同:方法名相同,参数类型相同
// Person
open class Person(var name:String, var age:Int)
open fun testMethod(str: String)
println("父类test! $str")
// Man类
class Man(name: String, age: Int): Person(name, age)
override fun testMethod(str: String)
println("子类test! $str")
fun main()
val man = Man("Tom", 22)
man.testMethod("aaa")
- 两小:子类返回值类型比父类返回值类型(返回值类型范围)小或相等、子类抛出异常类型比父类小或相等
// Person
open class Person(var name:String, var age:Int)
open fun testMethod(): Any?
println("父类test!")
return null
// Man类
class Man(name: String, age: Int): Person(name, age)
override fun testMethod(): String
println("子类test!")
return "test"
上面的例子父类的方法的返回值类型是Any?
,子类的方法的返回值类型是String
,从继承关系看,String
继承于Any?
,范围自然是Any?
大于String
。不过这种情况一般用的不多。
- 一大:子类的修饰符权限比父类的修饰符权限大或相等
// Person
open class Person(var name:String, var age:Int)
protected open fun testMethod(): Any?
println("父类test!")
return null
// Man类
class Man(name: String, age: Int): Person(name, age)
override fun testMethod(): String // 如果这里改为private就会报错
println("子类test!")
return "test"
3.2 属性的重写
属性的重写和方法的重写类似:
// Person
open class Person(var name:String, var age:Int)
open var test = -1 // 和方法一样,加上了open
// Man类
class Man(name: String, age: Int): Person(name, age)
override var test = 0 // 这里加上了override标志
fun main()
val man = Man("Tom", 22)
println("test value is $man.test")
输出结果:
test value is 0
从结果可以看出父类的成员变量test
被子类重写了。一般情况下这种功能较少使用,重写属性可能会破坏里氏替换原则(子类可以扩展父类的功能,但不能改变父类原有的功能)
四. super关键字
Kotlin和Java一样也是有super关键字的。和Java的用法也是类似的,都是指代父类,用法也和Java相似。
4.1 简单用法
// Person
open class Person(var name:String, var age:Int)
// Empty Code
// Man类
class Man(name: String, age: Int): Person(name, age)
fun test()
println("name is $super.name")
fun main()
val man = Man("Tom", 22)
man.test()
输出结果:
name is Tom
上面的例子多少有点脱裤子放屁的意思,实际上可以去掉super
,这里只是为了演示。它的主要作用还是用于方法重写上面。
4.2 复杂情况下的用法
这里介绍相对复杂的情况下的super
的用法。
4.2.1 子类重写方法中使用super
在重写方法中使用super
的方式基本和Java一样:
// Person
open class Person(var name:String, var age:Int)
open var test = -1
open fun testMethod()
println("父类test!")
// Man类
class Man(name: String, age: Int): Person(name, age)
override var test = 0
override fun testMethod()
super.testMethod() // 和Java的用法一样,调用父类Person的方法testMethod
println("test value is $super.test") // 调用的是父类Person的test
fun main()
val man = Man("Tom", 22)
man.testMethod()
输出结果:
父类test!
test value is -1
4.2.2 子类选择性调用父接口/父类的方法
当Kotlin的类继承父类和多个接口的时候,这就需要区分super
调用的是哪一个父类的方法。Kotlin中通过super<父类/父接口>.方法
方式调用父类/接口的方法
// Australopithecus接口,写法和Java类似
interface Australopithecus
fun testMethod()
println("接口test!")
// Person
open class Person
open fun testMethod()
println("父类test!")
// Man类,既继承了父类Man,也实现了接口Australopithecus
class Man: Person(), Australopithecus
override fun testMethod()
super<Australopithecus>.testMethod()
super<Person>.testMethod()
fun main()
val man = Man()
man.testMethod()
输出结果:
接口test!
父类test!
4.2.3 子类内部类调用父类方法
这种情况在于子类的内部类调用父类的方法,写法类似这样:super@子类.方法
// Person
open class Person
open fun testMethod()
println("父类test!")
// Man类
class Man: Person()
// 被inner修饰的内部类是非静态内部类,静态内部类显然不能访问非静态内部类的成员方法
inner class Heart
fun testMethod()
super@Man.testMethod()
fun main()
Man().Heart().testMethod()
输出结果:
父类test!
其实this
在内部类中也有类似的用法:
// Man类
class Man
fun eat()
println("eat!")
inner class Heart
fun testMethod()
this@Man.eat() // 调用外部类的方法,这种操作更加常见
fun main()
Man().Heart().testMethod()
输出结果:
eat!
记住无论是super
还是this
的@
后面一定是子类!
Android开发学习之路--Kotlin之常用表达式及函数
中缀表达式
只有一个参数,用infix修饰的函数
infix fun shouldBe(name:String)
this shouldBe "666"
if 表达式
看一个max函数,这里的if是有返回值的,也就是a>=b返回a,否则返回b
fun max(a:Int, b: Int) = if(a >= b) a else b
when 表达式
判断类型直接返回对应的结果
fun type(obj: Any) = when (obj)
is Long -> "I am Long"
is String -> "I am String"
else -> "I am unknow"
apply
调用某对象的apply函数,在函数块内可以通过this指代该对象。返回值为该对象自己。
- 函数定义:
fun T.apply(block: T.() -> Unit): T block(); return this
- 示例:
class Data(var name: String = "", var phone: String = "")
var applyTest = Data().apply
this.name = "eastmoon"
phone = "10086"
在apply块中,可以直接赋值,当然也可以省略this,最后返回对象自己.适用于run函数的任何场景,一般用于初始化一个对象实例的时候,操作对象属性,并最终返回这个对象
let
调用某对象的let函数,则该对象为函数的参数。在函数块内可以通过it指代该对象。返回值为函数块的最后一行或指定return表达式。
- 函数定义:
fun T.let(block: (T) -> R): R = block(this)
- 示例:
val letTest: Data? = Data()
letTest?.let
it.name = "huaixi"
it.phone = "12580"
true
?: let
toast("letTest is null")
false
在let块中,可以直接赋值,不可以省略it,最后返回true.适用于处理不为null的操作场景
with
with函数和前面的几个函数使用方式略有不同,因为它不是以扩展的形式存在的。它是将某对象作为函数的参数,在函数块内可以通过this指代该对象。返回值为函数块的最后一行或指定return表达式。
- 函数定义:
fun with(receiver: T, block: T.() -> R): R = receiver.block()
- 示例:
val withTest = Data()
with(withTest)
name = "huaixi"
this.phone = "12580"
true
在with块中,可以直接赋值,当然也可以省略this,最后返回true.适用于调用同一个类的多个方法时,可以省去类名重复.
run
调用run函数块。返回值为函数块最后一行,或者指定return表达式。
- 函数定义:
fun T.run(block: T.() -> R): R = block()
- 示例:
val runTest = Data()
runTest.run
this.name = "huaixi"
phone = "12580"
true
在run块中,可以直接赋值,当然也可以省略this,最后返回true.适用于let,with函数任何场景。
also
调用某对象的also函数,则该对象为函数的参数。在函数块内可以通过 it 指代该对象。返回值为该对象自己。
- 函数定义:
fun T.also(block: (T) -> Unit): T block(this); return this
- 示例:
val alsoTest = Data().also
it.name = "huaixi"
it.phone = "12580"
在also块中,可以直接赋值,不可以省略it,最后返回对象自己. 适用于let函数的任何场景,一般可用于多个扩展函数链式调用
以上是关于Kotlin学习之路:继承的主要内容,如果未能解决你的问题,请参考以下文章
Kotlin学习之旅解决错误:kotlin.NotImplementedError: An operation is not implemented: Not yet implemented
Kotlin学习之旅解决错误:kotlin.NotImplementedError: An operation is not implemented: Not yet implemented(代码片段
Kotlin学习之路--可添加header/footer的刷新列表