全网最全面的由浅到深的Kotlin基础教程(五)
前言
本篇文章接着上一篇文章全网最全面的由浅到深的Kotlin基础教程(四)继续进阶学习kotlin,建议看完上一篇文章,再来看本篇文章。前面4篇文章都是kotlin的基础知识,本篇文章开始进入深水区,主要讲解kotlin语言中类的继承,数据类,枚举类,接口类,抽象类,密封类,泛型,运算符重载,out协变,in逆变等等。
1. 继承与重载的open关键字
kotlin需要使用open关键字使得类可以被继承,使得类中的方法可以被重写。示例代码如下:
open class Person(val name:String){
//默认都是final方法,无法继承,需要用open关键字去除final
open fun show() = println("人类name=$name")
}
class Student(name: String):Person(name){
override fun show() {
println("学生name=$name")
}
}
fun main() {
var person:Person = Student("MEKEATER")
person.show()
}
运行结果如下:
2. 类型转换 is + as 配合类型转换
is用户判断是否为指定类型,as用于类型转换,示例代码如下:
fun main() {
//Student类继承自Person
var p: Person = Student("mekeater")
if (p is Student) {
(p as Student).show()
}
}
4. Any超类学习
kotlin任何类默认都继承了Any类
class Test04:Any() //任何类默认都继承了Any类
fun main() {
println(Test04().toString())
}
5. 对象声明:实现单例实例
kotlin通过object关键字生命的类,就是实现一个单例对象,可以反编译查看java代码的实现。通过这种方式实现java的单例模式,示例代码如下:
//object 就是实现一个单例对象。可以反编译查看java代码的实现。
object Test5{
//因为是单例,主构造函数用不到了,所以,init实际写在静态代码块了,可以反编译查看
init {
println("Test5 init")
}
fun show() = println("show函数")
}
fun main() {
//都是同一个实例
println(Test5)
println(Test5)
}
运行结果如下:
6. object :对象表达式
kotlin通过object : 对象表达式匿名实现接口或者继承类,示例代码如下:
open class Test06{
open fun show(info:String) = println("[$info]")
}
fun main() {
//匿名对象 表达式,即匿名实现类对象
var test06 = object : Test06() {
override fun show(info: String) {
println("匿名对象实现【$info】")
}
}
test06.show("mekeater")
//对接口,用对象表达式(匿名对象)实现
var run = object : Runnable {
override fun run() {
println("通过对象表达式,匿名实现接口")
}
}
run.run()
}
7. 伴生对象companion object
kotlin通过companion object关键字实现静态变量和静态方法,示例代码如下:
class Test07{
companion object{
val info = "mekeater"
fun show() = println("info=$info")
}
}
fun main() {
Test07.show()
}
8. 嵌套类与内部类
在类中再定义一个类,如果不加inner关键字,则为嵌套类,否则为内部类,内部类可以访问外层类的变量,嵌套类不可以。示例代码如下:
class Test08{
val info = "mekeater"
//不加inner默认是嵌套类,必须加inner使得嵌套的类成为内部类,而内部类才能访问外部类的成员变量
inner class Test1{
fun show() = println("info=$info")
}
}
fun main() {
Test08().Test1().show()
}
9. 数据类
kotlin通过data关键字声明的类称为数据类,数据类会自动生成toString hashCode equals 解构操作,这点可以通过反编译为java代码证明。数据类适用于类似javaBean的场景,示例代码如下:
//可以通过反编译为java代码比较普通类和数据类的区别。
//普通类只有get set和构造函数,而数据类相对于普通类,会再自动生成toString hashCode equals 解构操作
class Test09(var name:String)
data class Test091(var name: String)
fun main() {
println(Test09("mekeater"))
println(Test091("mekeater")) //因为数据类自动重写了toString,所以,输出结果与普通类不一样
}
运行结果如下:
上面代码反编译为java的部分代码如下,可以明显看出加了data关键字的Test091类,自动生成了component1解构函数,copy函数,toString函数,hashCode函数,equals函数,但是,可以看出这些操作只是处理主构造的参数,所以,次构造的内容会丢失,这点需要注意!
public final class Test09 {
@NotNull
private String name;
@NotNull
public final String getName() {
return this.name;
}
public final void setName(@NotNull String var1) {
Intrinsics.checkNotNullParameter(var1, "<set-?>");
this.name = var1;
}
public Test09(@NotNull String name) {
Intrinsics.checkNotNullParameter(name, "name");
super();
this.name = name;
}
}
public final class Test091 {
@NotNull
private String name;
@NotNull
public final String getName() {
return this.name;
}
public final void setName(@NotNull String var1) {
Intrinsics.checkNotNullParameter(var1, "<set-?>");
this.name = var1;
}
public Test091(@NotNull String name) {
Intrinsics.checkNotNullParameter(name, "name");
super();
this.name = name;
}
@NotNull
public final String component1() {
return this.name;
}
@NotNull
public final Test091 copy(@NotNull String name) {
Intrinsics.checkNotNullParameter(name, "name");
return new Test091(name);
}
// $FF: synthetic method
public static Test091 copy$default(Test091 var0, String var1, int var2, Object var3) {
if ((var2 & 1) != 0) {
var1 = var0.name;
}
return var0.copy(var1);
}
@NotNull
public String toString() {
return "Test091(name=" + this.name + ")";
}
public int hashCode() {
String var10000 = this.name;
return var10000 != null ? var10000.hashCode() : 0;
}
public boolean equals(@Nullable Object var1) {
if (this != var1) {
if (var1 instanceof Test091) {
Test091 var2 = (Test091)var1;
if (Intrinsics.areEqual(this.name, var2.name)) {
return true;
}
}
return false;
} else {
return true;
}
}
}
10. 类实现解构声明
kotlin通过component加数字实现为类添加解构功能,至于解构是什么东西,直接看如下代码示例,相信一看就懂了。
//普通类,声明解构函数,使其具备解构能力,但一般没必要,需要解构,就定义数据类就行了
class Test11(var name:String, var age:Int){
//函数名必须为component1 component2 ...
operator fun component1() = name
operator fun component2() = age
}
//数据类,上面也提到过,默认已经实现了解构
data class Test11_1(var name:String, var age:Int)
fun main() {
var test11 = Test11("mekeater", 18)
val (name,age) = test11
println("name=$name,age=$age")
// 下划线代表不接收某个解构数据,可以看反编译java源码,理解实现方式
val (name1, _) = Test11_1("sun", 18)
println("name=$name1")
}
代码运行结果如下:
11. 运算符重载
kotlin通过operator关键字及指定的函数名称实现重载运算符,如实现+运算符重载示例代码如下:
data class Add(var num1:Int, var num2:Int){
//实现 + 运算符重载
operator fun plus(p1:Add): Int {
return (num1+p1.num1) + (num2+p1.num2)
}
}
fun main() {
println(Add(1,2) + Add(3,4))
}
运行结果如下:
其它一些运算符名称如下:
12. 枚举类
12.1 枚举类定义
通过enum关键字声明的类为枚举类,示例代码如下:
enum class Week{
星期一,
星期二
}
fun main() {
println(Week.星期一)
println(Week.星期二 is Week)
}
运行结果如下:
12.2 定义主构造函数带参数的枚举类
枚举类和普通类一样,也有主构造函数,定义主构造函数带参数的枚举类,示例代码如下:
//一般枚举类的参数都要私有化,因为参数值是在内部枚举值中传入的,不需要外部传入
enum class Info(private var status:String){
START("开始"), //因为枚举类主构造函数带参数,因此枚举值需要传入值
STOP("停止"); //枚举后面如果有函数,需要有结束符
fun show() = println("status = $status")
fun update(s:String){
this.status = s
}
}
fun main() {
Info.START.show()
Info.STOP.show()
Info.START.update("启动")
Info.START.show()
}
运行结果如下:
13. 代数数据类型
代数数据类型,其实就是把枚举类和when结合使用的场景,示例代码如下:
enum class Score{
A,
B,
C,
D
}
class Teacher(private val score:Score){
fun show() = when(score){
Score.A -> "成绩为A"
Score.B -> "成绩为B"
Score.C -> "成绩为C"
Score.D -> "成绩为D"
}
}
fun main() {
println(Teacher(Score.A).show())
}
运行结果如下:
14. 密封类
密封类就是把一堆类收集在一个由sealed声明的类中,示例代码如下:
sealed class Person1 {
object Bird:Person1()
class Student(var name:String):Person1()
}
class Test16(private var person1:Person1){
fun show() = when(person1){
is Person1.Student -> "这是一个学生,学生姓名:${(person1 as Person1.Student).name}"
is Person1.Bird -> "这是一只小鸟"
}
}
fun main() {
println(Test16(Person1.Student("mekeater")).show())
println(Test16(Person1.Bird).show())
}
运行结果如下:
15. 接口类
kotlin定义接口和java类似,通过interface关键字定义接口,其中接口类中声明的成员变量需要实现类通过构造函数,或者重写成员变量实现,示例代码如下:
interface IUSB{
//成员变量需要实现类通过构造函数,或者重写成员变量实现
var usbVersion:String
var usbInfo:String
fun show():String
}
//方式一、通过构造函数实现接口中的成员变量
class Mouse(override var usbVersion: String, override var usbInfo: String) :IUSB{
override fun show(): String {
return "Mouse usbVersion=$usbVersion,usbInfo=$usbInfo"
}
}
//方式二、通过重新接口类的成员变量实现
class Keyword():IUSB{
override var usbVersion: String = ""
override var usbInfo: String = ""
override fun show(): String {
return "Keyword usbVersion=$usbVersion,usbInfo=$usbInfo"
}
}
fun main() {
var usb:IUSB = Mouse("3.0", "usb3.0版本")
println(usb.show())
usb = Keyword()
usb.usbVersion="3.1"
usb.usbInfo="usb3.1"
println(usb.show())
}
运行结果如下:
16. 抽象类
kotlin通过abstract关键字定义抽象类,这个和java类似,没啥好说的,直接看示例代码:
abstract class BaseActivity{
fun onCreate(){
setContentView(getLayoutID())
initView()
}
private fun setContentView(layoutID:Int) = println("layoutID=$layoutID")
abstract fun getLayoutID():Int
abstract fun initView()
}
class MainActivity: BaseActivity() {
override fun getLayoutID(): Int = 666
override fun initView() = println("initView finish")
}
fun main() {
MainActivity().onCreate()
}
运行结果如下:
17. 泛型学习
17.1 定义泛型类
kotlin定义泛型类与java类似,直接看示例代码:
class Kt19<T>(private val obj:T){
fun show() = println("obj=$obj")
}
fun main() {
Kt19<Int>(18).show()
Kt19("hello").show() //kotlin具有类型推断能力,因此可以省略指定类型
}
运行结果如下:
17.2 泛型函数
kotlin定义泛型函数与java类似,直接看示例代码:
fun<T> show(info:T){
info?.also {
println("info=$it")
} ?: println("info=null")
}
fun main() {
show(10)
show(10.66)
show("good")
show(null)
}
运行结果如下:
17.3 泛型变换
输入一种泛型类型,然后通过lambda表达式实现输出为另一种泛型类型,这种泛型转换写法在kotlin代码中很常见,示例代码如下:
inline fun <I, O> map(input: I, action: (I) -> O) =
action(input)
fun main() {
var map = map(123) {
"[$it]"
}
println(map)
}
运行结果如下:
17.4 泛型类型约束
kotlin泛型类型约束相当于java中T extends ,示例代码如下:
open class Animal()
open class Dog():Animal()
//T:Dog,T必须是Dog或者继承自Dog,相当于java中T extends Dog
class Test22<T:Dog>(private val input:T, private val isT:Boolean = true){
fun getInput() = input.takeIf { isT }
}
18. vararg关键字(动态参数)
通过vararg关键字实现动态参数,示例代码如下:
class Kt23<T>(vararg var ages: T){
fun show(index:Int) = println("age=${ages[index]}")
//运算符重载
operator fun get(index:Int) = ages[index]
}
fun main() {
val p1 = Kt23(16,17.7,"sun")
p1.show(2)
println(p1[0])
}
运行结果如下:
19. out-协变和in-逆变
- 协变 out T具有以下特点
- 泛型只能读取,不能修改
- 子类泛型可以直接赋值给父类泛型,类似java ? extend T
- 逆变 in T具有以下特点
- 泛型只能修改,不能读取
- 父类泛型可以直接赋值给子类泛型,类似java ? super T。父类去转子类,这应该是逆向过程,所以逆变的名称因此而来。
示例代码如下:
//协变
interface Product<out T>{
//修改操作,无法通过编译
//fun f1(item:T)
fun product():T
}
//逆变
interface Consumer<in T>{
fun consumer(item:T)
//读取操作,无法通过编译
//fun f2():T
}
open class Animal1
open class Dog1: Animal1()
class Product0:Product<Animal1>{
override fun product(): Animal1 {
println("生产 Animal1")
return Animal1()
}
}
class Product1:Product<Dog1>{
override fun product(): Dog1 {
println("生产 Dog1")
return Dog1()
}
}
class Consumer0:Consumer<Animal1>{
override fun consumer(item: Animal1) {
println("消费 Animal1")
}
}
class Consumer1:Consumer<Dog1>{
override fun consumer(item: Dog1) {
println("消费 Dog1")
}
}
fun main() {
val p1:Animal1 = Product0().product()
// 子类Dog1可以赋值给父类Animal1,正常子类是不能赋值给父类的,
// 此处之所以可以,是因为Product中的泛型是采用协变,类似java的 ? extend Animal
val p2:Animal1 = Product1().product()
//父类泛型Animal1直接赋值给了子类泛型Dog1,原因是in起的作用,类似java中 ? super Dog1
val p1:Consumer<Dog1> = Consumer0()
val p2:Consumer<Dog1> = Consumer1()
}
20. reified关键字学习
reified修饰的泛型,才具有泛型类型判断的能力,即 obj is T,示例代码如下:
data class ObjClass1(val name:String)
data class ObjClass2(val name:String)
class Kt27{
//只有用reified修饰泛型,才有泛型类型判断的能力 it is T。同时只有inline修饰的函数,才能使用reified修饰
inline fun<reified T> getObj(defaultLambdaAction:()->T):T{
var listOf = listOf(ObjClass1("obj1"), ObjClass2("obj2"))
var obj = listOf.shuffled().first()
println("随机对象:$obj")
//T? 代表可为null,不然会有null as T转换异常
return obj.takeIf { it is T} as T? ?: defaultLambdaAction()
}
}
fun main() {
Kt27().getObj<ObjClass1> {
var result = ObjClass1("备用对象")
println("启用备用对象:$result")
result
}
}
示例运行结果: