本章内容包括:
- 类和接口
- 非默认属性和构造方法
- 数据类
- 类委托
- 使用 object 关键字
1、定义类继承结构
/*--------------- 4.1.1 kotlin 中的接口-------------*/
// 代码清单 1 声明一个简单的接口
// interface Clickable {
// fun click()
// }
// 代码清单 2 实现一个简单的接口
class Button : Clickable2 {
override fun click() {
println(" I was clicked")
LogUtil.e(" I was clicked")
}
}
Button().click()
Button().showOff()
/**
* 说明:
* Kotlin 在后面使用【冒号:】 来代替java中的 extends 和 implements 关键字。
* 可实现多个接口继承一个类。
*/
// 代码清单 3 在接口定义一个带方法体的方法
// 代码清单 4 定义另一个实现了同样方法的接口
/**
* 可以在接口中定义一个带方法体的方法。
* 如果你的类实现了两个接口,这两个接口都定义了相同的方法,则编译器会强制你实现那个方法。
*/
// 代码清单 5 调用继承自接口方法的实现
class Button2 : Clickable2, Focusable {
// override fun click() {
// LogUtil.e("I was clicked2")
// }
override fun click() = LogUtil.e("I was clicked2")
// 必须提供显示实现
override fun showOff() {
/** 使用尖括号加上父类型名字的 "super" 表明了你想要调用哪一个父类的方法*/
super<Clickable2>.showOff()
super<Focusable>.showOff()
}
}
// 验证所有继承的方法都能被调用到
LogUtil.e("--------分割线--------")
val button = Button()
button.click()
button.showOff()
val button2 = Button2()
button2.click()
button2.showOff()
button2.setFocus(true)
/*--------------- 4.1.2 open、final和abstract修饰符: 默认为final -------------*/
/**
* 《Effective Java》:"要么为继承做好设计并记录文档,要么禁止这么做。"
* Java 的类和方法默认是open的,而 Kotlin 中默认都是final的。
* 如果你想允许创建一个类的子类,需要使用 open 修饰符来标识这个类。
* 此外需要给每一个可以被重写的属性或方法添加 open 修饰符。
*/
// 代码清单 6 声明一个带一个 open 方法的open类
open class RichButton : Clickable {
// 这个函数重写了一个open函数并且它本身同样是open的
override fun click() {}
// 这个函数是final的:不能在子类中重写它
fun disable() {}
// 这个函数使open的:可以在子类中重写它
open fun animate() {}
}
/**
* 注意:如果你重写了一个基类或者接口的成员,重写了的成员同样默认是open的。
* 如果你想改变这一行为,阻止子类的操作,可以显示将重写的成员变量设置为final。
*/
// 代码清单 7 禁止重写
open class RichButton2 : Clickable {
// 没有final的override 意味着是open的
final override fun click() {}
}
/**
* open 类和智能转换
* 属性默认是final的,可以在大多数属性上不加思考的使用智能转换,这提高了你的代码表现力。
* if (e is Sum) {
* // 变量 e 被智能转换了类型
* return eval(e.left) + eval(e.right)
* }
*/
// 代码清单 8 声明一个抽象类
// 这个类是抽象的,不能直接创建它的实例
abstract class Animated {
// 这个函数是抽象的:它没有实现必须被子类重写
abstract fun animate()
// 抽象类的抽象函数并不是默认open的,但是可以被标注为open的
open fun stopAnimating() {}
fun animateTwice() {}
}
// 测试
class Button3 : Animated() {
// 一定实现
override fun animate() {
}
// 可以选择实现
override fun stopAnimating() {
super.stopAnimating()
}
// 不能实现
// animateTeice()
}
/*--------------- 4.1.3 可见性修饰符:默认为 public -------------*/
/**
* 修饰符 相关成员 评注
* final 不能被重写 类中成员默认使用
* open 可以被重写 需要明确地表明
* abstract 必须被重写 只能在抽象类中使用:抽象成员不能实现
* override 重写父类或接口中的成员 如果没有使用final表明,重写的成员默认是开放的
*/
/**
* open : 控制继承修饰符。
* 要被继承的类必须是open修饰的,因为类默认是final的。
* 同理,要被重写的属性和方法也必须被open修饰。
*/
/*--------------- 4.1.4 内部类和嵌套类:默认是嵌套类 -------------*/
// Kotlin 的嵌套类不能访问外部类的实例,除非你特别地做出了要求。
// 代码清单 9 声明一个包含可序列化状态的视图
// interface State : Serializable
// interface View {
// fun getCurrentState(): ObjectActivity.State
// fun restoreState(state: ObjectActivity.State) {}
// }
// 代码清单 4.10 用带内部类的 Java 代码来实现 View
// public class Button implements ObjectActivity.View {
//
// @NotNull
// @Override
// public ObjectActivity.State getCurrentState() {
// return new ButtonState();
// }
//
// @Override
// public void restoreState(@NotNull ObjectActivity.State state) {
//
// }
//
// public class ButtonState implements ObjectActivity.State {
// }
// }
/**
* 问题:
* 这个代码有什么问题?为什么你会得到一个 java io NotSerializable Exception : Button 异常,如果你试图序列化声明的按钮的状态?
* 这最开始可能会看起来奇怪:你序列化的变量是 ButtonState 类型的 state ,并不是 Button类型。
* 当你想起来这是在 Java 中时所有的事情都清楚了,当你在另个类中声明一个类时,它会默认变成内部类。这个例子中的 ButtonState 类隐式地存储了它的外
* 部Button 类的引用。这就解释了为什么 ButtonState 不能被序列化: Button不是可序列化的,并且它的引用破坏了 ButtonState 的序列化
* 要修复这个问题,你需要声明 ButtonState 类是 static 的。将一个嵌套类声明为 static 会从这个类中删除包围它的类的隐式引用。
*/
// Kotlin 中,内部类的默认行为与我们刚刚描述的是相反的
// 代码清单4.11 在Kotlin中使用嵌套类实现View
// class Button4 : View {
// override fun getCurrentState(): State = ButtonState2()
// override fun restoreState(state: State) {}
//
// // 这个类与Java中的静态嵌套类类似
// class ButtonState2 : State {}
//
// }
/**
* 嵌套类和内部类在Java与Kotlin中的对应关系
* 类A在另一个类B中声明 在Java中 在Kotlin中
* 嵌套类(不存储外部类的引用) static class A class A
* 内部类(存储外部类的引用) class A inner class A
*/
/*--------------- 4.1.5 密封类:定义受限的类继承结构 -------------*/
// 代码清单4.1.2 作为接口实现的表达试
// interface Expr
// class Num(val value: Int) : ObjectActivity.Expr
// class Sum(val left: ObjectActivity.Expr, val right: ObjectActivity.Expr) : ObjectActivity.Expr
//
// fun eval(e: ObjectActivity.Expr): Int =
// when (e) {
// is Num -> e.value
// is Sum -> eval(e.right) + eval(e.left)
// // 必须检查“else”分支
// else -> throw IllegalArgumentException("Unknown expression")
// }
// 为解决else的情况,增加sealed类修饰符,对可能创建的子类做出了严格的限制。所有的直接子类必须嵌套在父类中。
// 代码清单4.1.3 作为密封类的表达式
// sealed class ExprS {
// // 将基类标记为密封的..
// class Num(val value: Int) : ObjectActivity.ExprS()
//
// // 将所有可能的类作为嵌套类列出
// class sum(val left: ObjectActivity.ExprS, val right: ObjectActivity.ExprS) : ObjectActivity.ExprS()
// }
//
// fun evalS(e: ObjectActivity.ExprS): Int =
// when (e) {
// is ObjectActivity.ExprS.Num -> e.value
// is ObjectActivity.ExprS.sum -> evalS(e.left) + evalS(e.right)
// }
2、声明一个带非默认构造方法或属性的类
/**------------------------- 4.2.1初始化类:主构造方法和初始化语句块 -------------------------*/
// 被括号围起来的叫主构造方法。目的:1表明构造方法的参数 2定义使用这些参数初始化属性
class User(val nickName: String)
// 1.明确的写法:
/**
* 关键字:
* 1、constructor:用来开始一个构造方法或从构造方法的声明
* 2、init:用来引入一个初始化语句块。主构造方法有语法限制,不能包含初始化代码。
*/
class User1 constructor(_nickName: String) {
val nickName: String
init {
nickName = _nickName
}
}
// 2.用参数来初始化属性
class User2 constructor(_nickName: String) {
val nickName = _nickName
}
// 3.val 意味着相应的属性会用构造方法的参数来初始化
open class User3(val nickName: String)
// 为构造方法声明一个默认值:
class User4(val nickName: String, val isSuc: Boolean = true)
// 创作构造方法时不需要new
val user4 = User4("jingbin")
val user42 = User4("jingbin", false)
val user43 = User4("jingbin", isSuc = false)
println(user4.isSuc)// true
println(user42.isSuc)// false
println(user43.isSuc)// false
// 如果所有的参数都有默认值,会生成一个额外不带参数的构造方法来使用所有的默认值
class User5(val nickName: String = "jingbin", val isSuc: Boolean = true)
// 使用不带参数的,但是默认值是声明的默认值
val user5 = User5()
// 如果你的类具有一个父类,主构造方法同样需要初始化父类
class TwitterUser(nickName: String) : User3(nickName) {
}
// 将会生成一个不带任何参数的构造方法
open class Button
// 必须显示的调用父类的构造方法,即使没有任何参数,空括号表示没有参数的构造方法
class RadioButton : Button()
// 包装自己的类不被其他的类实例化,有一个private构造方法
class Secretive private constructor() {}
/**------------------------- 4.2.2 构造方法:用不同的方式来初始化父类 -------------------------*/
// 大部分的情况不需要声明多个构造方法,因为可以在构造方法中写默认值来规避
class User6(val nickName: String, val isSuc: Boolean = true)
// 比较常见到的是一个View
open class View {
// 从构造方法
constructor(ctx: Context) {
}
constructor(ctx: Context, attr: Attributes?) {
}
}
// 如果想扩展这个类的,可以声明同样的构造方法
class MyButton : View {
// 调用父类的构造方法
constructor(context: Context) : super(context) {
}
constructor(context: Context, attributes: Attributes) : super(context, attributes) {
}
}
// 也可以使用this关键字调用自己的另一个构造方法
class MyButton1 : View {
// 委托给这个类的另一个构造方法
constructor(context: Context) : this(context, null) {
}
constructor(context: Context, attributes: Attributes?) : super(context, attributes) {
}
}
/**------------------------- 4.2.3 实现在接口中声明的属性 -------------------------*/
// Kotlin中接口可以包含抽象属性的声明,Java貌似不行,实际上就是一个getName()的方法
// interface User8 {
// val name: String
// }
// 代码清单 4.14 实现一个接口属性
// 主构造方法属性
class PrivateUser(override val name: String) : User8
// 每次访问时都会调用substringBefore
class SubScribingUser(val email: String) : User8 {
override val name: String
// 在@之前的字符串
get() = email.substringBefore("@")
}
// 只会调用一次id.toString()
class FaceBookUser(val id: Int) : User8 {
// 初始化属性
override val name = id.toString()
}
// 接口中还可以包含getter和setter的属性
// interface User9 {
// val email: String
// val name: String get() = email.substringBefore("@")
// }
/**------------------------- 4.2.4 通过getter或setter访问支持字段 -------------------------*/
class User10(val name: String) {
var address: String = "unspecified"
set(value: String) {
// 在set中访问支持字段
println("$field --> $value")
// field 是原来的值,value是设置的值
field = value
}
}
val user10 = User10("jingbin")
// 将 jingbin 重新设置为 jinbeen "jingbin --> jinbeen"
user10.address = "jinbeen"
/**------------------------- 4.2.5 修改访问器的可见性 -------------------------*/
// 代码清单 4.16 声明一个具有private setter的属性
class LengthCounter {
var counter: Int = 0
// 不能在类外部修改这个属性
private set
fun addWord(word: String) {
counter += word.length
}
}
LengthCounter().addWord("jingbin")
3、编译器生成的方法: 数据类和类委托
/**------------------------- 4.3.1 通用对象方法 -------------------------*/
// 处理 toString equals hashCode等
// 代码清单 4.17 Client类的最初声明
class Client(val name: String, val postalCode: Int)
/**toString()*/
// 代码清单 4.18 为Client实现 toString()
class Client2(val name: String, val postalCode: Int) {
override fun toString(): String = "Client(name=$name,postalCode=$postalCode)"
}
println(Client2("jingbin", 333))
/**equals()*/
// 在Kotlin中,==检查对象是否相等,而不是比较引用。===与java中的 ==比较对象引用的效果一模一样
// 代码清单 4.19 为Client类实现 equals()
class Client3(val name: String, val postalCode: Int) {
// "Any"是java.lang.Object的模拟:Kotlin中所有类的父类。可空类型"Any?"意味着"other"是可以为空的
override fun equals(other: Any?): Boolean {
if (other == null || other !is Client) {
return false
}
return name == other.name && postalCode == other.postalCode
}
override fun toString(): String = "Client(name=$name,postalCode=$postalCode)"
}
/**hashCode()*/
val hashSetOf = hashSetOf(Client3("jingbin", 123456))
println(hashSetOf.contains(Client3("jingbin", 123456)))// false
// 因为Client缺少了hashCode方法。因此它违反了通用的hashCode契约:如果两个对象相等,它们必须有着相同的hash值。
// 代码清单 4.20 为Client实现hashCode()
class Client4(val name: String, val postalCode: Int) {
override fun hashCode(): Int = name.hashCode() * 31 + postalCode
}
/**------------------------- 4.3.2 数据类:自动生成通用方法的实现 -------------------------*/
// 给你的类添加 data修饰符上述的方法就会自动添加好,而且还会有其他的方法
// 代码清单4.21 数据类 Client
// equals 和 hashCode 会将所有主构造方法里的值一起处理,不在主构造方法里的不会处理!
data class Client5(val name: String, val postalCode: Int)
// 数据类和不可变性:Copy()方法
class Client6(val name: String, val postalCode: Int) {
fun copy(name: String = this.name, postalCode: Int = this.postalCode) = Client6(name, postalCode)
}
val client6 = Client6("jingbin", 123)
println(client6.copy(postalCode = 345))
// 为避免上述的模板代码,使用Kotlin -> by 类委托
/**------------------------- 4.3.3 类委托:使用“by”关键字 -------------------------*/
// 无论什么时候实现一个接口,你都可以使用by关键字将接口的实现 委托 到另一个对象。
class DetegatingCollention<T>(innerList: Collection<T> = ArrayList<T>()) : Collection<T> by innerList {}
// 代码清单4.22 使用类委托
class CountingSet<T>(val innerSet: MutableCollection<T> = HashSet<T>()) : MutableCollection<T> by innerSet {
// 将MutableCollection的实现委托给innerSet
var objectAdded = 0
override fun add(element: T): Boolean {
objectAdded++
return innerSet.add(element)
}
override fun addAll(c: Collection<T>): Boolean {
objectAdded += c.size
return innerSet.addAll(c)
}
}
val countingSet = CountingSet<Int>()
countingSet.addAll(listOf(1, 2, 3))
// 3 objects were added,2 remain
println("${countingSet.objectAdded} objects were added,${countingSet.size} remain ")
4、“object”关键字:将声明一个类与创建一个实例结合起来
// 使用的核心理念:定义一个类并同时创建一个对象
/**------------------------- 4.4.1 对象声明:创建单例易如反掌 -------------------------*/
// 对象声明将 类声明与该类的 单一实例 声明结合到了一起
// 表示一个组织的工资单
// object Payroll {
// val allEmployees = arrayListOf<Person>()
// fun calculateSalary() {
// for (person in allEmployees) {
person.name
// }
// }
// }
// 对象声明不允许有构造方法,且定义的时候就创建了,不需要调用构造方法!
// Payroll.allEmployees.add()
// 代码清单4.23 使用对象来实现 Comparator
// object CaseInsensitiveFileComparator : Comparator<File> {
// override fun compare(o1: File, o2: File): Int {
// return o1.parent.compareTo(o2.path, ignoreCase = true)
// }
// }
CaseInsensitiveFileComparator.compare(File("2"), File("1"))
// 可以在任何可以使用普通对象的地方使用单例对象
val listOf = listOf(File(""), File("2"))
// sortedWith 返回一个可以根据特定的比较器排序过的列表
println(listOf.sortedWith(CaseInsensitiveFileComparator))
// 代码清单4.24 使用嵌套类实现Comparator
// data class Person(val name: String) {
// object NameComparator : Comparator<Person> {
// override fun compare(o1: Person, o2: Person): Int {
// return o1.name.compareTo(o2.name)
// }
//
// }
// }
/**------------------------- 4.4.2 伴生对象:工厂方法和静态成员的地盘 -------------------------*/
// companion 获得了直接通过容器类名称来访问这个对象的方法和属性的能力
// class A{
// companion object {
// fun bar{
// println("companion object called")
// }
// }
// }
// 代码清单 4.25 定义一个拥有多个从构造方法的类
// class User {
// val nickName: String
//
// constructor(email: String) {
// nickName = email.substringBefore("@")
// }
//
// constructor(id: Int) {
// nickName = id.toString()
// }
// }
// 代码清单4.26 使用工厂方法
// class User2 private constructor(val nickName: String) {
// companion object {
// fun newSubUser(email: String) = User2(email.substringBefore("@"))
// fun newFBUser(id: Int) = User2(id.toString())
// }
// }
User2.newFBUser(123)
User2.newSubUser("jingbin127@gmail.com")
/**------------------------- 4.4.3 作为普通对象使用的伴生对象 -------------------------*/
// 代码清单4.27 声明一个命名伴生对象
// class Person2(val name: String) {
// companion object Loader {
// fun fromJSON(jsonText: String): Object4Activity.Person2 = Person2(jsonText)
// }
// }
Person2.Loader.fromJSON("{name:jingbin}")
Person2.fromJSON("{name:jingbin}")
// 代码清单4.28 在伴生对象中实现接口
// interface JSONFactory<T> {
// fun fromJSON(jsonText: String): T
// }
//
// class Person3 {
// companion object : Object4Activity.JSONFactory<Person3> {
// // 实现接口的伴生对象
// override fun fromJSON(jsonText: String): Person3 = fromJSON(jsonText + "哈哈")
// }
// }
// 4.29 为伴生对象定一个扩展函数
// class Person4(val firstName: String, val lastName: String) {
// // 声明一个空的伴生对象
// companion object {}
// }
//
// // 声明一个扩展函数 默认Companion
// fun Object4Activity.Person4.Companion.fromJSON(jsonText: String): Object4Activity.Person4 = Person4(jsonText, "bin")
/**------------------------- 4.4.4 对象表达式:改变写法的匿名内部类 -------------------------*/
// 代码清单 4.30 使用匿名对象来实现事件监听器
// window.addMouseListener(
// // 声明一个继承MouseAdapter的匿名对象
// object : MouseAdapter() {
// override fun mouseClicked(e: MouseEvent) {}
// override fun mouseEntered(e: MouseEvent) {}
// })
// 与对象声明不同,匿名对象不是单例的。每次执行都会创建一个新的对象实例。
//代码清单4.31 从匿名对象访问局部变量
// fun countClicks(window: Window) {
// // 局部变量
// var clickCount = 0
// window.addMouseListener(object : MouseAdapter() {
// override fun mouseClicked(e: MouseEvent) {
// // 更新变量的值
// clickCount++
// }
// })
// }
总结
- Kotlin 的接口与 Java 的相似,但是可以包含默认实现 (Java 从第8版才开始支持)和属性。
- 所有的声明默认都是 final和public的
- 要想使声明不是 final 的,将其标记为 open
- internal 声明在同一模块中可见。
- 嵌套类默认不是内部类。使用 inner 关键字来存储外部类的引用。
- sealed 类的子类只能嵌套在自身的声明中(Kotlin 1.1 允许将子类放置在同一文件的任意地方)。
- 初始化语句块和从构造方法为初始化类实例提供了灵活性
- 使用 field 标识符在访问器方法体中引用属性的支持字段
- 数据类提供了编译器生成的 equals hashCode toString copy 和其他方法。
- 类委托帮助避免在代码中出现许多相似的委托方法。
- 对象声明是Kotlin 中定义单例类的方法。
- 伴生对象(与包级别函数和属性 起)替代了 Java 静态方法和字段定义
- 伴生对象与其他对象一样,可以实现接口,也可以拥有有扩展函数和属性
- 对象表达式是 Kotlin 中针对 Java 匿名内部类的替代品,并增加了诸如实现个接口的能力和修改在创建对象的作用域中定义的变 的能力等功能