<>Kotlin之面向对象
<>类
* 定义:使用class关键字进行定义。
* class Dog { }
* 在kotlin类中,除非显式地声明延迟初始化,否则就得指定属性的默认值。
* 在kotlin类中,成员默认是全局可见,而Java中,需要使用public才能达到此效果。
* 构造函数
* Kotlin将构造函数分为两种:主构造函数和次构造函数。
* ①主构造函数:
* 每个类默认都会有一个不带参数的主构造函数,也可以显式地给它指明参数。
* 可以给主构造函数的参数指定默认值。
* class Dog(val name: String = "", val age: Int = 0, val color: Color = Color.
BLUE) 创建: val dog = Dog("wangwang") //创建对象时会根据传入的参数进行匹配,如果发现不一致的就会报错
//解决办法就是指定这个参数值要赋值给哪一个参数,例如dog2的color = Color.BLUE //val dog1 = Dog(1) val dog2
= Dog(color = Color.BLUE) val dog3 = Dog("Tom", 1)
* 如果主构造函数中的变量被var或val修饰,表示这个变量相当于类的成员变量。此时如果再在类中定义同名变量会报错。
* class Dog(val name: String = "", age: Int = 0) { var age: Int = 0
//由于主构造函数中已经定义了该属性,这里再定义就重名了 //var name: String = "" init { //this.name = name
//左边是类的成员变量,右边是构造函数中的变量 this.age = age } }
* 特点:没有函数体,直接定义在类名的后面即可。如果想在主构造函数中写一些逻辑可以放在init结构体中。
* //People后面的括号用来说明Java的一个特性:
//子类构造方法中必须调用父类的构造方法,这里调用了People的无参构造函数,就算无参也需要写括号。 class Student(sno: String,
grade: Int) : People() { init { //在此处写主构造函数的逻辑 } }
//调用了People的有参构造函数,由于Student类没有name和age字段,因此需要我们自己添加
//注意:不能再将name和age属性声明成var或val,防止与父类属性重名。 class Student(sno: String, grade: Int,
name: String, age: Int) : People(name, age) { }
* 利用刚说的构造函数中参数的特性,我们有3种方式初始化:
* ①在构造函数中将参数指定为var或val,使其变为类的成员变量;
* ②不把构造函数中的参数指定为var或val,在类中定义同名的成员变量,并在init块中使用构造参数来进行初始化;
* ③不把构造函数中的参数指定为var或val,在类中定义同名的成员变量,并把构造参数赋值给成员变量。
* //① class Dog(val name: String = "", val age: Int = 0) { fun test() {
println("name = $name, age = $age") } } //② class Dog(name: String = "", age:
Int= 0) { val name: String val age: Int //注:init初始化块可以定义多个,当创建对象时会依次从上往下执行每个块
init { this.name = name println("初始化块1:name = $name") } init { this.age = age
println("初始化块2:age = $age") } fun test() { println("name = $name, age = $age") }
} 输出: 初始化块1:name = wangwang 初始化块2:age = 1 name = wangwang, age = 1 //③ class Dog
(name: String = "", age: Int = 0) { val name: String = name val age: Int = age
fun test() { println("name = $name, age = $age") } }
* 延迟初始化:不想在创建对象时就初始化该属性。
* 可以使用lateinit和by lazy这两种语法来实现。
* by lazy:
* ①主要用于val变量;
* ②在首次被调用时才会赋值,一旦赋值就不能再修改它。
* ③线程安全问题:
*
a.LazyThreadSafetyMode.SYNCHRONIZED:系统会默认给lazy属性加上同步锁,它在同一时刻只允许一个线程对它进行初始化,所以它是线程安全的;
* b.LazyThreadSafetyMode.PUBLICATION:你能确认该属性可以并行执行,没有线程安全问题;
* c.LazyThreadSafetyMode.NONE:不会有任何线程方面的开销,也不保证线程安全。
* val name: String by lazy(LazyThreadSafetyMode.SYNCHRONIZED) { "lala" }
* lateinit:
* ①主要用于var变量;
*
②不能用于像Int、Long这些基本类型,必须用它们的包装类如Integer等。如果必须要用Int,可以采用Kotlin中委托的语法实现,即Delegates.notNull。
* lateinit var name: String var age by Delegates.notNull<Int>() lateinit var
age: Integer
* ②次构造函数:
* 任何类只能有一个主构造函数,但可以有多个次构造函数。
* 次构造函数也可以用于实例化一个类,只不过它是有函数体的。
* Kotlin规定:当一个类既有主构造函数又有次构造函数时,所有的次构造函数都必须调用主构造函数(也包括间接调用)。
* 次构造函数可能在自定义view时使用。
* 定义:次构造函数使用constructor关键字来定义。
* class Student(sno: String, grade: Int, name: String, age: Int) : People(name
, age) { //直接调用了主构造函数,并使用默认值赋给次构造函数的变量 constructor(name: String, age: Int) :
this("", 0, name, age) { } //通过上面的次构造函数间接调用了主构造函数 constructor() : this("", 0) {
} } 使用: val student1 = Student() val student2 = Student("Tom", 18) val student3
= Student("100", 7, "Jack", 17)
* 特殊情况:类中只有次构造函数,没有主构造函数。
*
Student没有显示定义主构造函数,同时又定义了次构造函数,所以Student是没有主构造函数的。因此,也不用在遵循Java的规定,不用再调用父类的构造方法了,去掉了People的构造方法。
* class Student : People { constructor(name: String, age: Int) : super(name,
age) { } }
* 次构造函数基本用不到,Kotlin提供了一个给函数设定默认值的功能,基本上可以代替次构造函数的作用。
<>对象
* 实例就是对象,对象就是实例。
* 创建对象:不再需要使用Java那样的new关键字。
* val a = A()。
<>接口
* Kotlin的接口基本和Java的接口一样。
* Kotlin和Java一样,最多只能继承一个类,实现一个或多个接口。
* Java实现接口使用implements关键字,kotlin不管是继承还是实现,都是用冒号:,之间使用逗号,分隔。
* 使用override关键字重写父类或实现接口中的函数。
* class Student : People { override fun test() { } }
* 支持定义抽象属性。
* 虽然kotlin的接口支持定义属性,但不能同时给它赋值:val a: Int = 10。
* 需要使用另一种方式来赋初值:
* val a: Int get() = 10
* 支持定义有默认实现的方法。
* Java从1.8开始也支持这个功能了。
* 背后实现其实是通过在内部定义一个静态内部类来提供方法的默认实现。
* interface A { //抽象属性 val a: Int //抽象方法 fun run() //带有默认实现的方法 fun show() {
println("A show()") } }
<>继承
* 在kotlin中,一个非抽象类默认是不可被继承的,相当于Java中给类声明了final关键字。需要给类加上open关键字。
* open class People { }
* 继承需要使用冒号:,在Java中是使用extends关键字。
* class Student : People() { }
* 多继承:
* 特点:
* ①在实现一个接口时,需要实现接口中没有默认实现的方法及未初始化的属性。
*
②若要同时实现多个接口以及方法,同时又遇到同名的未实现的方法时,这个方法来自于谁已经不重要了,可以说它既来自类又来自接口,如果是同名的已实现的方法,要么重写方法,要么用“super”指定要调用哪个接口或类的方法。
* ③在实现接口或类的属性或方法时,必须要带上override关键字。
* interface FlyAnimal { val name: String fun fly() fun eat() { println(
"FlyAnimal eat") } } abstract class Animal { abstract fun fly() open fun eat() {
println("FlyAnimal eat") } } class Bird(override val name: String) : Animal(),
FlyAnimal{ override fun fly() { println("fly") } override fun eat() = super<
Animal>.eat() } fun main() { val bird = Bird("lala") bird.eat() } 输出: FlyAnimal
eat
* 实现多继承的方式:
* ①使用多个接口模拟;
* ②使用内部类实现;
* 内部类:内部类使用inner class进行定义,如果不写,默认是静态内部类。
* 这种实现方式的优点:
* 1.可以在内部定义多个内部类,每个内部类的实例都有自己的独立状态,它们与外部对象的信息相互独立;
* 2.内部多个类可以实现不同的接口从而拥有多个状态和行为;
* 3.可以使用private修饰内部类,使得其他类不能访问内部类。
* open class Horse { fun runFast() {} } open class Donkey { fun doLongTime() {
} } class Mule { fun runFast() { CHorse().runFast() } fun doLongTime() { CDonkey
().doLongTime() } private inner class CHorse : Horse() private inner class
CDonkey: Donkey() }
* ③使用委托代替多继承:
* 使用by关键字实现委托。
* 优点:
* 1.接口是无状态的,即使可以在默认方法写逻辑,但也仅限于简单的逻辑,如果给它创建一个实现类,可以在其中编写更复杂的逻辑;
* 2.委托更加直观。
* interface Fly { fun fly() } interface Eat { fun eat() } open class Flyer :
Fly{ override fun fly() { println("fly") } } open class Animal : Eat { override
fun eat() { println("eat") } } class Bird(flyer: Flyer, animal: Animal) : Fly by
flyer, Eat by animal {} fun main() { val bird = Bird(Flyer(), Animal()) bird.
eat() bird.fly() } 输出: eat fly
<>可见性修饰符
* kotlin的可见性修饰符分4种:public、internal、protected、default。
* Java默认的可见性修饰符是default,不同于Java,Kotlin则是public。
* internal:我们开发了一个模块给别人使用,但是有一些函数只允许我们在模块内部调用,而不想暴露给外部,就可以用internal。
* 可见性修饰符功能一览表:
修饰符可见性
public所有类可见
internal同一模块中的类可见
protected当前类、子类可见
private当前类可见
<>伴生对象
* 意为某个类的对象,它属于这个类所有。
* 伴生对象和Java的static效果一样,全局只有一个单例。它需要声明在类的内部,在类被装载时进行初始化。
* 使用companion object进行定义,里面用来装类的静态变量和静态方法。
* 使用时直接用类名.。
* 在安卓中,我们可以给Fragment、Activity、Dialog定义一个伴生对象用来传递数据和启动等。
* class HomeFragment { companion object { private const val TAG =
"HomeFragment" fun newInstance(): HomeFragment { return HomeFragment() } } }
<>数据类
* 它相当于Java的bean类。
* data类会自动重写equals()、hashCode()、toString()方法。
* equals():判断两个类是否相等。
* hashCode():作为equals()方法的配套方法,若不重写会导致HashMap、HashSet等无法正常工作。
* toString():提供更清晰的输出日志。
* 使用data class进行声明,它会根据构造函数中的参数自动生成以上几个方法。
* data class People(val name: String, val age: Int)
* 解构:
* kotlin对于数组的解构默认最多允许5各变量。
* 解构还用于Pair和Triple。
* val (int,string,double) = "1,abc,20.1".split(",") println("int = $int,
string =$string, double = $double") 输出: int = 1, string = abc, double = 20.1
* Pair:
* 二元组,可以有两个元素。
* //定义 val a = "s" to 1 val b = Pair("s", 1) //使用 val a1 = a.first val a2 = a.
second//解构 val (string, int) = a
* Triple:
* 二元组,可以有两个元素。
* //定义 val c = Triple(1, "abc", 20.1) //使用 val a1 = c.first val a2 = c.second
val a3 = c.third //解构 val (int,string,double) = c
<>单例类
* 希望某个类在全局最多只能拥有一个实例。
* 在Java中创建单例类的步骤:
* (1)使类的构造方法私有化;
* (2)提供一个静态的getInstance()方法,用于给外部提供类的实例。
* 在kotlin中使用object关键字进行声明。
* object SystemManager {}
<>object表达式
* 相比于匿名内部类只能继承一个类或实现一个接口,object表达式却没有这个限制。
* 在安卓中,我们可以使用object表达式来代替匿名内部类,如常用的Listener接口等。
* viewLifecycleOwner.lifecycle.addObserver(object : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) fun onDestroyView() { block.invoke
() } })
* 对于object表达式可以实现的一些场景,Lambda表达式也可以实现。具体建议参考如下:
* 当匿名内部类使用的类接口只需要实现一个方法时,使用Lambda更合适;当有多个方法需要实现时,则建议使用object表达式。