一、密封类 sealed
1.1 受限的类继承结构
所谓受限的类继承结构,即当类中的一个值只能是有限的几种类型,而不能是其他的任何类型。
这种受限的类继承结构从某种意义上讲,它相当于是枚举类的扩展。但是,我们知道Kotlin的枚举类中的枚举常量是受限的,因为每一个枚举常量只能存在一个实例。
但是其和枚举类不同的地方在于,密封类的一个子类可以有可包含状态的多个实例。
也可以说成,密封类是包含了一组受限的类集合,因为里面的类都是继承自这个密封类的。但是其和其他继承类(open)的区别在,密封类可以不被此文件外被继承,有效保护代码。但是,其密封类的子类的扩展是是可以在程序中任何位置的,即可以不再统一文件下。
1.2 声明
关键字:sealed
声明格式:
sealed class SealedExpr()
注意:密封类是不能被实例化的
1.3 作用及用法
1.3.1 作用
用来表示受限的类继承结构
sealed class SealedExpr{
data class Person(val num1 : Int, val num2 : Int) : SealedExpr()
object Add : SealedExpr() // 单例模式
object Minus : SealedExpr() // 单例模式
}
// 其子类可以定在密封类外部,但是必须在同一文件中 v1.1之前只能定义在密封类内部
object NotANumber : SealedExpr()
分析:即所定义的子类都必须继承于密封类,表示一组受限的类
1.3.2 和普通继承类的区别
普通继承来使用open关键字定义,在项目中的类都可以继承自该类;
而密封类的子类必须在密封类内部或者密封类的同一文件内,即有效的代码保护。
1.3.3 和枚举类的区别
枚举类中的每一个枚举常量都只能存在一个实例;
而密封类的子类可以存在多个实例。
1.3.4 其子类的类扩展实例
在kotlin支持扩展功能,其和C#、Go语言类似。这一点是java所不具备的。
sealed class SealedExpr{
data class Person(val name : String, val age : Int) : SealedExpr()
object Add : SealedExpr()
companion object Minus : SealedExpr()
}
object NotANumber : SealedExpr()
其存在TestSealedDemo.kt文件中
fun <T>SealedExpr.Add.add(num1 : T, num2 : T) : Int{
return 100
}
fun main(args: Array<String>) {
println(SealedExpr.Add.add(1,2))
}
输出结果为:
100
1.3.5 使用密封类的好处
- 有效的代码保护
- 在使用when表达式的时候,如果能够验证语句覆盖了所有的情况,就可以不再为该语句添加else子句了。
package hello
sealed class SealedExpr{
data class Person(val name : String, val age : Int) : SealedExpr()
object Add : SealedExpr()
companion object Minus : SealedExpr()
}
object NotANumber : SealedExpr()
fun eval(expr: SealedExpr) = when(expr){
is SealedExpr.Add -> println("is Add")
is SealedExpr.Minus -> println("is Minus")
is SealedExpr.Person -> println(SealedExpr.Person("Koltin",22))
NotANumber -> TODO()
}
fun main(args: Array<String>) {
val mPerson1 = SealedExpr.Person("name1",22)
println(mPerson1)
val mPerson2 = SealedExpr.Person("name2",23)
println(mPerson2)
println(mPerson1.hashCode())
println(mPerson2.hashCode())
eval(SealedExpr)
}
输出结果:
Person(name=name1, age=22)
Person(name=name2, age=23)
-1052833328
-1052833296
is Minus
二、抽象类 abstract
在实际可发过程中,一般都会写一个基类,封装常用的方法、以及处理一些共有的逻辑 ,但是程序逻辑是根据每个类不同的功能去实现不同的代码。而这个所谓的基类,一般都是一个抽象类。不管是java还是kotlin,实现抽象类的作用就在于此。
抽象类可以理解为类定义一个模板,所有的子类都根据这个模板去填充自己的代码。
2.1 声明
关键字:abstract
抽象可以分为抽象类、抽象函数和抽象属性。
抽象类和普通类的区别在于:抽象类除了可以有其自己的属性、构造函数、方法等组成部分外,还包含了抽象函数以及抽象属性
abstract class Lanauage{
val TAG = this.javaClass.simpleName // 自身的属性
// 自身的函数
fun test() : Unit{
// exp
}
abstract var name : String // 抽象属性
abstract fun init() // 抽象方法
}
/**
* 抽象类Lanauage的实现类TestAbstarctA
*/
class TestAbstarctA : Lanauage(){
override var name: String
get() = "Kotlin"
set(value) {}
override fun init() {
println("我是$name")
}
}
/**
* 抽象类Lanauage的实现类TestAbstarctB
*/
class TestAbstarctB : Lanauage(){
override var name: String
get() = "Java"
set(value) {}
override fun init() {
println("我是$name")
}
}
fun main(args: Array<String>) {
// val lanauage = Lanauage() 是错误的,因为抽象类不能直接被实例化
val mTestAbstarctA = TestAbstarctA()
val mTestAbstarctB = TestAbstarctB()
println(mTestAbstarctA.name)
mTestAbstarctA.init()
println(mTestAbstarctB.name)
mTestAbstarctB.init()
}
输出结果:
Kotlin
我是Kotlin
Java
我是Java
抽象类本身具有普通类特性及其组成部分,不过,抽象类不能直接被实例化;
抽象类的子类必须全部重写带abstract修饰的属性和方法;
抽象成员只有定义,没有实现,都用abstract修饰符修饰;
抽象类是为其子类定义了一个模板,不同的类实现不同的功能。
2.2 规则
- 在kotlin中的抽象类在顶层定义的时候只能使用public可见性修饰符修饰;
- 抽象类中可以定义内部抽象类;
- 只能继承一个抽象类;
- 若要实现抽象类的实例化,需要依靠子类采用向上转型的方式处理;
- 抽象类可以继承自一个继承类,即抽象类可以作为子类。不过,抽象类建议不用open修饰,因为可以覆写抽象类的父类的函数。
2.3 实际应用
在Java的设计模式中,有一种设计模式叫模板设计模式,其定义为:
- 定义一个操作中算法的骨架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变算法的结构即可重定义该算法的某些特定步骤。
- 通俗点的理解就是 :完成一件事情,有固定的数个步骤,但是每个步骤根据对象的不同,而实现细节不同;就可以在父类中定义一个完成该事情的总方法,按照完成事件需要的步骤去调用其每个步骤的实现方法。每个步骤的具体实现,由子类完成。
Kotlin和Java是互通的,说明Kotlin也是支持这种设计模式的。
如果你对Java中的模板设计模式不是很了解的,请参见这篇文章
三、内部类(嵌套类)
在实际开发中,用到内部类的地方是很多的。比如说:
对于Android开发来说,列表适配器(adapter)中的ViewHolder类,就是一个内部类。
根据后台开发人员提供的json字符串生成的对象中,也包含另外一个对象,这也是一个内部类。
3.1 嵌套类
即:一个类可以嵌套在其他类中。
class Other{ // 外部类
val numOuther = 1
class Nested { // 嵌套类
fun init(){
println("执行了init方法")
}
}
}
fun main(args: Array<String>) {
Other.Nested().init() // 调用格式为:外部类.嵌套类().嵌套类方法/属性
}
输出结果为:
执行了init方法
注意:
- 调用嵌套类的属性或方法的格式为:
外部类.嵌套类().嵌套类方法/属性。在调用的时候嵌套类是需要实例化的
。 - 嵌套类不能使用外部类的属性和成员
3.2 内部类
在上面的例子中讲解了嵌套类的使用,而内部类和嵌套类还是有一定的区别的,而且内部类是有特定的关键字去声明的。
1.声明
关键字:inner
声明格式:
inner class 类名(参数){}
class Other{ // 外部类
val numOther = 1
inner class InnerClass{ // 嵌套内部类
val name = "InnerClass"
fun init(){
println("我是内部类")
}
}
}
fun main(args: Array<String>) {
Other().InnerClass().init() // 调用格式为:外部类().内部类().内部类方法/属性
}
注意:
- 调用内部类的属性或方法的格式为:
外部类().内部类().内部类方法/属性。在调用的时候嵌套类是需要实例化的
。 - 内部类不能使用外部类的属性和成员
2.匿名内部类
作为一名Android开发者,对匿名内部类都不陌生,因为在开发中,匿名内部类随处可见。比如说Button的OnClickListener,ListView的单击、长按事件等都用到了匿名内部类。
一般的使用方式为定义一个接口,在接口中定义一个方法。
class Other{
lateinit private var listener : OnClickListener
fun setOnClickListener(listener: OnClickListener){
this.listener = listener
}
fun testListener(){
listener.onItemClick("我是匿名内部类的测试方法")
}
}
interface OnClickListener{
fun onItemClick(str : String)
}
fun main(args: Array<String>){
// 测试匿名内部类
val other = Other()
other.setOnClickListener(object : OnClickListener{
override fun onItemClick(str: String) {
// todo
println(str)
}
})
other.testListener()
}
输出结果为:
我是匿名内部类的测试方法
3.3 局部类
所谓局部类,这一点和Java是一致的。即定义在方法(函数)中的类。
class Other{ // 外部类
val numOther = 1
fun partMethod(){
var name : String = "partMethod"
class Part{
var numPart : Int = 2
fun test(){
name = "test"
numPart = 5
println("我是局部类中的方法")
}
}
val part = Part()
println("name = $name \t numPart = " + part.numPart + "\t numOther = numOther")
part.test()
println("name = $name \t numPart = " + part.numPart + "\t numOther = numOther")
}
}
fun main(args: Array<String>) {
// 测试局部类
Other().partMethod()
}
输出结果为:
name = partMethod numPart = 2 numOther = 1
我是局部类中的方法
name = test numPart = 5 numOther = 1
注意:
- 局部类只能在定义该局部类的方法中使用。
- 定义在实例方法中的局部类可以访问外部类的所有变量和方法。但不能修改
- 局部类中的可以定义属性、方法。并且可以修改局部方法中的变量。
3.4 静态类
熟悉Java的朋友都知道Java的静态类,或者说用static修饰符修饰的类。但是在Kotlin中,是不存在static关键字的。
1.静态类
Kotlin语言中使用"object"修饰静态类,被修饰的类,可以使用类名.方法名的形式调用
object Util {
fun getCurrentVersion(): String {
return BuildConfig.VERSION_NAME
}
}
调用:
var version_name1 = Util.getCurrentVersion()
2.静态方法
Kotlin语言中使用"companion object"修饰静态方法,可以使用类名.方法名的形式调用
class Util2 {
companion object {
fun getCurrentVersion(): String {
return BuildConfig.VERSION_NAME
}
}
}
调用:
var version_name2 = Util2.getCurrentVersion()