Kotlin学习与实践 (六)数据类、类委托、“object”
1、数据类
为了引出数据类,先回顾一下Java中的数据类,通常都是有很多字段来存储数据并向外提供getter、setter、toString、Hashcode等机械式的方法。
* Kotlin 也像Java一样提供了toString equals hashCode 方法,也可以重写他们
class Client1(val name: String, val postalCode: Int) { override fun toString(): String { return "Client(name = $name , postalCode = $postalCode)" } override fun equals(other: Any?): Boolean { if (other == null || other !is Client1) { return false } return name == other.name && postalCode == other.postalCode } override fun hashCode(): Int { return name.hashCode() * 31 + postalCode } fun copy(name: String = this.name, postalCode: Int = this.postalCode) = Client1(name, postalCode) //自己添加的用于展示copy实现的方法 }
Kotlin提供了一种data修饰符,使用data修饰符声明数据类就会自动创建以上的机械性的方法。
* data 修饰符创建一个方便的数据容器,它会自动生成 equals 、 hashCode 、toString 方法(就是Java的标准实现)
* 注意:只有主构造函数中声明的属性才会纳入 equals 和 hashCode 考虑
* 提倡使用val 声明data类的属性,这样类的实例就成为不可变的了,将作为HashMap 或者其他容器的key的时候就会安全很多(不会因为作为key的对象被修改而成为脏数据)
* 同时Kotlin 给data 类提供了 copy 的方法,可以轻松的使用不可变对象的数据类
* 创建副本通常是修改实例的好选择,副本有着独立的生命周期而且不会影响代码中引用原始实例的位置
data class Client2(var name: String, val postalCode: Int) /** * Kotlin中 == 是比较两个对象的默认方式,本质上它们是通过对比equals 来比较值的,这点和Java的不同 * 可以使用 === 运算符来比较对象的引用,这点与Java的 == 一样 */ fun main(args: Array<String>) { println(Client("ShangHai", 200045)) println(Client1("ShangHai", 200045)) val c1 = Client1("ShangHai", 200045) val c2 = Client1("ShangHai", 200045) println(c1 == c2) println(c1 === c2) val v3 = Client2("name", 46555) val copy = v3.copy(v3.name, 121212) }
2、类委托“by”关键字
* 为了解决由集成导致了代码脆弱性(子类太依赖基类,基类改变或者添加方法的时候子类的假设就会出问题),Kotlin 将类默认设置成了final的
* 除非显示的使用open 关键字, 有时候类已经存在了或者设置的时候没有被设计成可扩展的时候,通常会以 “装饰器模式”闻名。与原来类的拥有同样
* 行为的方法不用修改,只需要添加新的方法。
class DelegatingCollection<T> : Collection<T> { private val innerList = arrayListOf<T>() override val size: Int get() = innerList.size override fun contains(element: T): Boolean = innerList.contains(element) override fun containsAll(elements: Collection<T>): Boolean = innerList.containsAll(elements) override fun isEmpty(): Boolean = innerList.isEmpty() override fun iterator(): Iterator<T> = innerList.iterator() fun addTest() { } }
在Kotlin中想声明一个委托类就简单很多了:
* Kotlin 将委托作为一个语言级别的功能做了头等支持,无论什么时候实现一个接口,都可以是使用by 关键字将接口的实现委托到另外一个对象。
class DelegatingCollection0<T>(val innerList: Collection<T> = ArrayList<T>()) : Collection<T> by innerList { /** * 也可以指定重写某些方法 */ override fun contains(element: T): Boolean { return innerList.contains(element) } fun addTest() { } }
再看如何使用委托类:
class CountingSet<T>(val countSet: MutableCollection<T> = HashSet<T>()) : MutableCollection<T> by countSet { //通过 by 关键字把Mutable的实现委托给了countSet var objectsAdded = 0 override fun add(element: T): Boolean {//这里没有使用委托类中的默认实现,自己提供了一种新的实现 objectsAdded++ return countSet.add(element) } override fun addAll(elements: Collection<T>): Boolean {//提供自己的实现 objectsAdded += elements.size return countSet.addAll(elements) } } fun main(args: Array<String>) { val cset = CountingSet<String>() cset.add("dsa") cset.add("dsa") cset.addAll(listOf("dsa", "xxx")) println(cset.objectsAdded) }
3、object 关键字
* object 的使用场景:
* 1、对象声明是定义单例的一种方式
* 2、伴生对象可以持有工厂方法和其他与这个类相关,但再调用时不依赖类实例的方法。他们的成员可以用过类名来访问。
* 3、对象表达式用来替代Java的匿名内部类。
对象声明--Kotlin中的单例
* Kotlin 通过使用“对象声明”功能为单例提供了最高级的语言支持。对象声明将类声明与该类的唯一实例声明结合到了一起。
* 对象声明通过“object”关键字引入。一个对象声明可以非常高效的已一句话来定义一个类和一个该类的变量。
* 与类一样,一个对象声明也可以有属性、方法、初始化语句块等声明。唯一不允许的就是构造方法!(包括主构造方法和从构造方法)。
* 与普通类的实例不同的是对象声明在定义的时候就创建了,不需要在其他地方调用构造函数,所有不需要定义一个没有意义的构造函数。
object Payroll { val allEmployees = arrayListOf<Person>() fun calculateSalary() { for (person in allEmployees) { println(person.name) } } } /** * 与普通的变量一样,对象声明允许你使用对象名加.字符的方式调用方法和属性。 */ fun main(args: Array<String>) { Payroll.allEmployees.add(Person("张三")) Payroll.calculateSalary() testCaseT() }
* 对象声明同样可以继承自类和接口。这通常在使用框架需要去实现一个接口,但是实现并不包含任何状态的时候很有用。 如:
object CaseInsensitiveFileComparator : Comparator<File> { override fun compare(o1: File?, o2: File?): Int { if (o2 != null) { if (o1 != null) { return o1.path.compareTo(o2.path, true) } } return -1 } }
使用:
fun testCaseT() { println(CaseInsensitiveFileComparator.compare(File("/User"), File("/user"))) /** * 可以在任何使用普通对象的地方使用对象声明创建的单例对象。 */ val files = listOf(File("/a"), File("/c"), File("/p")) println(files.sortedWith(CaseInsensitiveFileComparator)) }
* 同样可以在类中声明“声明对象”。这样的对象同样只有一个单一实例;他们在每个容器类的实例中并不具有不同的实例。 如:
data class Dog(val name: String) { object NameComparator : Comparator<Dog> { override fun compare(o1: Dog?, o2: Dog?): Int { if (o2 != null) { if (o1 != null) { return o1.name.compareTo(o2.name, true) } } return -1 } } } fun testInnerObject() { val dogs = listOf(Dog("K"), Dog("dsa")) println(dogs.sortedWith(Dog.NameComparator)) }
Java中调用:
* Kotlin 中的对象声明被编译成了通过静态字段来持有它的单一实例的类,这个字段名字始终都是INSTANCE。如果要再Java中使用,可以通过访问静态的INSTANCE 字段:
* CaseInsensitiveFileComparator.INSTANCE.compare(file1,file2)
2、companion object 伴生对象
* Kotlin中的类不能有静态成员;Java中的static关键字并不是Kotlin语言的一部分。
* 作为替代,Kotlin依赖包级别函数和对象声明,在大多数情况下还是推荐使用顶层函数,但是顶层函数不能访问类的private成员,
* 因此如果你需要一个可以在没有类实例情况下调用但是需要访问类内部的函数,可以将其写成那个类中的对象声明的成员。这种函数的例子就是工厂方法。
* 使用companion关键字标记内部类就获得了直接通过容器类名称来访问征对象的方法和属性的能力,不需要再显式地指明对象的名称。最终的语法看起来和Java的静态方法调用一样。
class A { companion object { fun bar() { println("Companion object called!") } } } fun usAge(){ A.bar() }
* 伴生对象可以访问类中的所有private成员,包括private构造方法 这样伴生对象就可以用作工厂类的声明
class TopUser { val nickName: String constructor(email: String) { nickName = email.substringBefore("@") } constructor(facebookAccountId: Int) { nickName = getFaceBookName(facebookAccountId) } private fun getFaceBookName(facebookAccountId: Int): String { return "name_$facebookAccountId" } } /** * 伴生对象可以访问类中的所有private成员,包括private构造方法,它是理想的实现工厂模式的选择 */ class TopUser2 private constructor(val nickName: String) { companion object { fun newSubTopUser(email: String) = TopUser2(email.substringBefore("@")) fun newFaceBookUser(accountId: Int) = TopUser2(getFaceBookName(accountId)) private fun getFaceBookName(facebookAccountId: Int): String { return "name_$facebookAccountId" } } }
3、对象表达式用来替代Java的匿名内部类。
为了展示如何使用object关键字来代替匿名内部类先抛出几个基础类:
interface MouseAdapter { fun mouseClicked() } interface MouseTouch { fun mouseClicked() } class Window { fun addMouseListener(mouseAdapter: MouseAdapter) { println("set mouseAdapter ${mouseAdapter.toString()}") } }
* object 关键字不仅仅可以用作单例类的声明,还可以用来声明匿名内部类。
* 出了去掉了对象的名字外,语法与对象声明的语法一样。
* 对象表达式声明了一个类并创建了该类的实例,但是没有给这个实例或者类分配一个名字。通常它都不需要名字,但是也可以将这个对象分配一个名字,将其存贮到一个变量中
fun testAnonymousInnerClass() { Window().addMouseListener(object : MouseAdapter { override fun mouseClicked() { } }) val listener = object : MouseAdapter { override fun mouseClicked() { } } }
* 与Java匿名内部类不同的是,Kotlin的匿名内部类可以实现多个接口或者不实现接口。 匿名对象不是单例的,每次对象表达式被执行都会创建一个新的对象实例。
* 与Java不同的是,在匿名内部类中访问创建它的函数中的变量时并没有强制限制变量必须是 final的,还可以修改变量的值
fun countClicks(window: Window) { var clickCount = 0 window.addMouseListener(object : MouseAdapter, MouseTouch { override fun mouseClicked() { clickCount++ } }) }