Kotlin学习系列——对象表达式和对象声明

喜夏-厌秋 提交于 2020-01-23 04:27:35

Kotlin 用对象表达式和对象声明来实现创建一个对某个类做了轻微改动的类的对象,且不需要去声明一个新的子类。

对象表达式

通过对象表达式实现一个匿名内部类的对象用于方法的参数中:

window.addMouseListener(object : MouseAdapter() {
    override fun mouseClicked(e: MouseEvent) {
        // ...
    }
    override fun mouseEntered(e: MouseEvent) {
        // ...
    }
})

对象可以继承于某个基类,或者实现其他接口:

open class A(x: Int) {
    public open val y: Int = x
}

interface B {……}

val ab: A = object : A(1), B {
    override val y = 15
}

如果超类型有一个构造函数,则必须传递参数给它。多个超类型和接口可以用逗号分隔。

通过对象表达式可以越过类的定义直接得到一个对象:

fun main(args: Array<String>) {
    val site = object {
        var name: String = "菜鸟教程"
        var url: String = "www.runoob.com"
    }
    println(site.name)
    println(site.url)
}

请注意,匿名对象可以用作只在本地和私有作用域中声明的类型。如果你使用匿名对象作为公有函数的 返回类型或者用作公有属性的类型,那么该函数或属性的实际类型 会是匿名对象声明的超类型,如果你没有声明任何超类型,就会是 Any。在匿名对象 中添加的成员将无法访问。

class C {
    // 私有函数,所以其返回类型是匿名对象类型
    private fun foo() = object {
        val x: String = "x"
    }

    // 公有函数,所以其返回类型是 Any
    fun publicFoo() = object {
        val x: String = "x"
    }

    fun bar() {
        val x1 = foo().x        // 没问题
        val x2 = publicFoo().x  // 错误:未能解析的引用“x”
    }
}

在对象表达中可以方便的访问到作用域中的其他变量:

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++
        }
    })
    // ……
}
访问局部变量

我们知道在Java中,被匿名内部类访问的变量必须被final修饰符修饰。代码示例如下:

private void Example(Window window){
    int i = 0;
    window.addMouseListener(new MouseAdapter() {
        @Override
        public void mouseClicked(MouseEvent e) {
            super.mouseClicked(e);
            //错误,i必须声明为final的
            i++;
        }
    });
}

而在Kotlin中没有这种限制,对象表达式可以直接访问创建它的函数内部的变量,并且修改其值。同样是上面的例子我们看看Kotlin中的代码例子:

private fun Example(window: Window) {
    var i = 0
    window.addMouseListener(object : MouseAdapter() {
        override fun mouseClicked(e: MouseEvent?) {
            super.mouseClicked(e)
            //i的值可以被正常修改
            i++
        }
    })
}

看出上面Kotlin中i的值可以被正常修改不会报错。
对象表达式的知识点其实是object关键字的用法之一,它的出现可以很好地帮我们解决一些Kotlin编程中的特殊场景的问题。


对象声明

我们都知道在Java中,必须先有类,然后才能new出对象,也就是声明类和创建对象是两个分开的步骤,并有先后次序。在Kotlin中,我们可以使用object关键字在声明定义一个类的同时创建出一个对象,也就是我们标题所说的对象声明,下面就来一起看看对象声明的相关知识点。

基本用法

对象声明的基本格式如下:

object 类名 {
    属性声明
    方法声明
}
  • 1
  • 2
  • 3
  • 4

对象声明的使用格式如下:

类名.属性
类名.方法
  • 1
  • 2

这里我们创建一个年级类的对象声明,代码如下所示:

package cn.codekong.objectdeclare

data class Student(val name: String, val age: Int){

}

//对象声明
object Grade{
    val allStudents = arrayListOf<Student>()

    //计算所有学生年龄和的方法
    fun countAge(): Int {
        var sum = 0
        for (student in allStudents){
            sum += student.age
        }
        return sum

    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

我们使用上面的对象声明

fun main(args: Array<String>) {
    Grade.allStudents.add(Student("小白", 12))
    Grade.allStudents.add(Student("小厅", 13))
    Grade.allStudents.add(Student("小红", 14))
    Grade.allStudents.add(Student("小明", 15))

    print(Grade.countAge())
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

看到这里大家可能觉得这个也没什么嘛,感觉跟Java中一个类中声明了static方法和static属性,然后通过类名直接调用一样嘛,当然这里可以这么类比Java进行理解,但是要记住在Kotlin中没有static这个关键字。

使用场景

看了上面的小例子,感觉还是很简单的,那这种对象声明用在什么地方呢?学了这个知识点终归是要用的啊。

1 . 用于创建单例对象
作为23种设计模式之一的单例模式,相信很多学习Java的小伙伴都是知道的,在Kotlin中,直接利用对象声明这个高级特性将类的声明和实例的创建结合在一起,就完美达到了单例模式的要求,如下代码所示:

object Singleton{
    val info =  "string"
    fun out(suffix: String){
        println("$info---$suffix")
    }
}

fun main(args: Array<String>) {
    Singleton.out("xxx")
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

由于我们不能以其他方式创建Singleton的对象,所以它是满足单例模式的。

2 . 实现接口但是不包含状态
对象声明也可以像普通类那样实现接口,但是由于对象声明创建的实例只有一个,但可能被多次使用,所以一般不包含数据和状态,在这样的情况下,也可以使用对象声明,下面通过一个比较器的例子来说明,我们实现一个忽略大小写进行文件路径比较的例子,代码如下:

object CaseInsensitiveFileComparator: Comparator<File>{
    override fun compare(file1: File, file2: File): Int {
        return file1.path.compareTo(file2.path, ignoreCase = true)
    }
}

fun main(args: Array<String>) {
    val fileList = listOf<File>(File("/abc"), File("/aBc"), File("/aaa"))
    println(fileList.sortedWith(CaseInsensitiveFileComparator))
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

上面的路径比较是没有状态的,同时将我们自定义的声明对象传递给比较函数进行比较。

与Java互操作

一直在说Java和Kotlin有良好的互操作性,那么Kotlin中的对象声明在Java中该如何使用呢?
Kotlin中的对象声明被编译为Java中通过静态字段来持有的单一实例,这个实例的名字固定为INSTANCE,就好像我们在Java中调用Java定义的单例模式的实例一样。代码示例如下:

//对象声明
object Grade{
    var allStudents = arrayListOf<Student>()

    //计算所有学生年龄和的方法
    fun countAge(): Int {
        var sum = 0
        for (student in allStudents){
            sum += student.age
        }
        return sum
    }
}


//Java中的调用
public class JObjectDeclare{
    public static void main(String[] args) {
        ArrayList<Student> students = new ArrayList<>();
        students.add(new Student("小雨", 10));

        Grade.INSTANCE.setAllStudents(students);
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

注意事项

接着,我们说一下对象声明的使用注意事项:

1 . 对象声明可以包含属性、方法、初始化语句块

2 . 对象声明不可以使用构造方法(包括主构造方法和从构造方法)

3 . 对象声明可以被声明在一个类的内部

写在最后

此处只是介绍了object关键字在对象声明中的用途,后续还会介绍更多object关键字的用处,敬请期待。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!