首发于公众号: DSGtalk1989
5.Kotlin 类和对象
-
构造器
kotlin中一个类只能有一个主构造器和一个或多个次构造器。主构造器可以直接跟在
class
定义的类名后面但是没有方法体,如下:class Person constructor(s : String) { } //也可以写成这样,记得,没有空格 class Person(s : String){ } //一旦构造函数存在修饰符或者是注解的情况下,我们就不能省去constructor的方法名 class Child public constructor(s : String){ }
以上的都是主构造函数,次构造函数定义在类中,并且kotlin强制规定,次构造函数必须要调用主构造函数,如下:
class Person constructor(s : String) { constructor(i : Int) : this("123"){ } } //也可以没有主构造器,如下的方式就是直接起的次构造器 //只有这种情况下,次构造器不需要再调用主构造器,所以一般如下这种方式跟我们的java习惯比较像 class Person{ constructor(){ } }
-
private,public,protected,internal
前面三个大家比较熟悉了,在java中都有,但是
internal
是kotlin中才引入的,叫做模块内可见,即同一个module
中可见。我们分别来看下,用这四个修饰符来描述属性所带来的编译区别。
//kt private var a = "a" public var b = "b" protected var c = "c" internal var d = "d" //decompiled private String a; @NotNull private String b; @NotNull private String c; @NotNull private String d; @NotNull public final String getB() { return this.b; } public final void setB(@NotNull String var1) { Intrinsics.checkParameterIsNotNull(var1, "<set-?>"); this.b = var1; } @NotNull protected final String getC() { return this.c; } protected final void setC(@NotNull String var1) { Intrinsics.checkParameterIsNotNull(var1, "<set-?>"); this.c = var1; } @NotNull public final String getD$app_debug() { return this.d; } public final void setD$app_debug(@NotNull String var1) { Intrinsics.checkParameterIsNotNull(var1, "<set-?>"); this.d = var1; }
总结一下,就是不管是哪一个修饰符,最终经过编译之后生成的在java中的参数描述都是
private
的,修饰符真正造成的区别是在编译了自后的get
和set
的方法不同。private
的话,就不会生成get
和set
方法,因为对于这个参数来说,是外部不可访问的。public
和protected
就是相应的set
和get
。而internal
则是public
的setD$app_debug
和getD$app_debug
方法。我们可以认为这两个方法,在model
中都是可以被访问的。 -
init关键字
上面说到主构造器直接写在类名之后是没有方法体的,因此一旦我们想要在构造函数中做一些初始化的操作,就需要挪到init中实现了。
class Person constructor(firstName: String) { init { println("FirstName is $firstName") } }
-
getter和setter
跟java差的有点多,首先属性定义前面说过了,kotlin中getter和setter直接定义在属性下方,由于kotlin的本身属性的直接访问性,只要你创建的是public的属性,都可以直接获取到属性值即get方法和修改属性值即set方法。
所以免去了为了
fastjson
而专门去写的setter
和getter
,但是一旦你需要在getter
和setter
时做一些其他的操作,我们就需要去显示的写出get
和set
了var sex = "boy" get() { return "girl" } set(value) { when { value.contains("girl") -> field = "boy" } } var age = 16 get() = field + 10 private set(value) = action(value) fun action(int: Int) { }
get
和set
可以直接包含方法体,也可以直接通过等号的方式链到单行表达式或者方法。
即我们认为,一旦触发取值和赋值的时候会做相应的操作。其中field
就是指的他自己。>>注:field的重要性<<
在kotlin中,我们定义了一个参数
name
然后,只要去调用他,比如parent.name
或者说去对他进行赋值name = "Tony"
最终都会被编译成使用了get
和set
方法,如下//Observer.kt fun main(args : Array<String>){ var observer = Observer() observer.name = "Tony" var newName = observer.name } //Observer.decompiled.java public static final void main(@NotNull String[] args) { Intrinsics.checkParameterIsNotNull(args, "args"); Observer observer = new Observer(); //调用了set observer.setName("Tony"); //调用了get String newName = observer.getName(); }
所以我们在类中定义属性的
setter
和getter
的时候,如果直接操作属性本身,就会出现死循环。这就是field
的用途//kt var no: Int = 100 get() = no set(value) { if (value < 10) { // 如果传入的值小于 10 返回该值 no = value } else { no = -1 // 如果传入的值大于等于 10 返回 -1 } } //decompiled int no = 100; public int getNo() { return getNo();// Kotlin中的get() = no语句中出来了变量no,直接被编译器理解成“调用getter方法” } public void setNo(int value) { if (value < 10) { setNo(value);// Kotlin中出现“no =”这样的字样,直接被编译器理解成“这里要调用setter方法” } else { setNo(-1);// 在setter方法中调用setter方法,这是不正确的 } }
很显然,造成了死循环。
-
lateinit关键字
我们都知道kotlin中,在方法中定义属性时,我们必须进行初始化的操作。而类中本身我们一开始并不知道他到底是什么,所以会有希望晚一点再初始化的需求,这里就可以使用
lateinit
关键字来描述,那我们就可以不用给出具体的初始化值,但是kotlin会要求你必须给出属性的类型。lateinit var game : String
那么这个
game
我们可以之后再对其赋值。从kotlin 1.2开始已经支持全局和局部变量都是用lateinit, 并且我们可以通过isInitialized来判断是否已经初始化过
-
抽象类
我们默认定义的
class
都是final
的,无法被继承的。所以一旦需要这个class
能够被继承,我们需要加上open
关键字。如果这是个抽象类,那我们需要添加abstract
关键字。一旦被abstract
描述,就无需再加上open
了。open class Person(){ } abstract class Parent{ }
紧接着,另外几种场景
-
方法是否可以被重写
默认方法都是
final
的,如果需要让方法可以被重写,需要在方法前再加上open
所有我们平时在java中写的一个单纯的类实际上转换成kotlin是如下这个样子的:
open class Person constructor(s: String) { open fun getName(){} }
同样的
abstract
也是这个意思,加载class
前面只是形容类,跟方法和属性什么的一点关系都没有 -
抽象属性
这是一个比较新的东西,因为java不支持抽象属性。就是说,你要是继承我,你就必须要初始化我所要求初始化的属性。
abstract class Parent(ame : String){ abstract var ame : String } //两种集成方式,一种是直接在构造函数中对抽象属性进行复写 //由于父类构造需要传一个字符串,所以在继承时也需要直接传入,此处传入的是Child1自己的构造参数s class Child1 constructor(s: String, override var ame: String) : Parent(s) { } //一种是在类中对属性进行复写 class Child2 constructor(s: String) : Parent(s) { override lateinit var ame: String } //如果子类没有主构造函数,也可以通过次构造函数调用`super`方法实现 class Child: Parent { constructor() : super("s") override lateinit var ame: String }
-
-
嵌套类
直接在
class
内部定义class
,基本和java差不多class Outer { // 外部类 private val bar: Int = 1 class Nested { // 嵌套类 fun foo() = 2 } } fun main(args: Array<String>) { val demo = Outer.Nested().foo() // 调用格式:外部类.嵌套类.嵌套类方法/属性 println(demo) // == 2 }
-
内部类
在刚才嵌套类的基础上加上
inner
的关键字申明。class Outer { private val bar: Int = 1 var v = "成员属性" /**嵌套内部类**/ inner class Inner { fun foo() = bar // 访问外部类成员 fun innerTest() { var o = this@Outer //获取外部类的成员变量 println("内部类可以引用外部类的成员,例如:" + o.v) } } }
唯一的区别在于内部类持有了外部类的引用,可以通过
@外部类名
的方式,来访问外部类的成员变量。 -
内部类和嵌套类的区别
我们来看如下两个嵌套类和内部类的以及让门各自编译成class文件的例子
//Out.kt class Out{ class Inner{ } } //Out.decompiled.java public final class Out { public static final class Inner { } } //Out.kt class Out{ inner class Inner{ } } //Out.decompiled.java public final class Out { public final class Inner { } }
已经很明显了,
inner
之所以持有外部的引用,是因为他不是static
的。也就是说kotlin的class
默认就是static final
的。调用嵌套类的方式与调用内部类的方式差别也只是一个括号而已
fun main(args : Array<String>){ //内部类调用 Out().Inner().method() //嵌套类的调用 Out1.Inner().method() }
其实比较容易理解,嵌套类是
static
的直接可以通过类名来进行访问嵌套类。 -
匿名内部类
一般用在接口层面的很多,我们通常知道的是传参是个接口,方法中调用了接口方法的形式,如下:
class Observer{ fun getIt(listener: Listener){ listener.onClick() } } interface Listener{ fun onClick() } fun main(args : Array<String>){ var observer = Observer() //注意,此处的object是kotlin独有的关键字 //不是随便写写的,匿名内部类必须通过这个关键字来申明 observer.getIt(object : Listener{ override fun onClick() { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. } }) }
通常我们也可以直接使用
接口名
+lambda表达式
的方式来生成匿名内部类,但条件是这个接口必须是函数式java接口,即只有一个抽象方法的java文件中定义的接口。比如我们基本碰到的所有的什么
OnclickListener
等等tv_case_id.setOnClickListener { View.OnClickListener{ } }
不过kotlin中定义的接口,我们就必须通过
object
的方式去实现了同时匿名内部类我们可以单独的拿出来进行定义,实际上我们可以把
object :
理解成一个匿名的内部类实现了一个接口,也就是说我们还可以实现多个接口,比如:open class A(x: Int) { public open val y: Int = x } interface B { …… } val ab: A = object : A(1), B { override val y = 15 }
通常我们在java中是无法做到匿名内部类实现多个接口的,因为我们只能
new
一个接口出来。
更甚者说,我们很多时候甚至不需要这个object
去实现或者是继承什么,我们可以直接搞一个object
出来
fun foo() {
val adHoc = object {
var x: Int = 0
var y: Int = 0
}
print(adHoc.x + adHoc.y)
}
-
匿名对象最为函数的返回类型
我们上面是将匿名对象赋值给了对象,我们还可以吧匿名对象直接赋值给方法,比如下面这个样子。
fun publicFoo() = object { val x: String = "x" }
这里涉及到公有还是私有的问题。
匿名对象我们一般只能用在私有域和本地。白话的说就是一旦变成了公有,那就说谁都可以去调用,由于匿名对象只在生命的本地和私有域起作用,导致公有调用拿到的对象只能是匿名对象的超类(即父类,比如上面的
object : Listener
就是Listener
,如果没有显式的定义超类就是Any
)那么这样一来,就会导致匿名内部类中定义的属性是拿不到的,比如上面的x
,因为上面的object
并没有显式的定义超类,所以他返回的是Any
,而Any
是没有x
属性的. -
匿名对象访问变量
在java中匿名内部类想要访问相应的属性变量必须要
final
才行,但是在kotlin中,我们直接可以访问包含匿名对象作用域中的所有变量。fun countClicks(window: JComponent) { var clickCount = 0 var enterCount = 0 window.addMouseListener(object : MouseAdapter() { override fun mouseClicked(e: MouseEvent) { clickCount++ } override fun mouseEntered(e: MouseEvent) { enterCount++ } }) }
Kotlin学习笔记之 13 基础操作符run、with、let、also、apply