kotlin之object详解

在写kotlin代码的时候,特别是在写Rxjava的时候经常会碰到(object:类名称)这样的表达式,完全不知道怎么回事,故搜集了些资料彻底搞清楚kotlin中object的含义。

object是kotlin中的一个重要关键字,也是java中没有的。object主要有以下三种使用场景:

  1. 对象声明(Object Decalaration)
  2. 伴生对象(Companion Object)
  3. 对象表达式(Object Expression)– 上面提到的就是这种

对象声明

语法:通过object实现kotlin中的单例
例子:

1
2
3
4
5
object RepositoryManager{
fun method(){
println("I'm in object declaration")
}
}

即将object代替class关键字,声明一个类,这个类就是单例了

使用:

1
2
3
4
fun main(args: Array<String>) {
RepositoryManager.method()//kotlin的调用
RepositoryManager.INSTANCE.method();//java的调用
}

像在Java中调用静态方法(kotlin中没有静态方法)一样去调用其中定义的方法。其实,object声明的类最终被编译成:一个类拥有一个静态成员变量来持有对自己的引用,并且这个静态成员的名称是INSTANCE。它等价于java代码:

1
2
3
4
5
class RepositoryManager{
private RepositoryManager(){}
public static final RepositoryManager INSTANCE = new RepositoryManager();

}

尽管和普通类的声明一样,可以包含属性,方法,初始化代码块以及可以继承其他类或者实现某个接口,但是它不能包含构造器,java中构造器是私有的。

它也可以定义在一个类的内部:

1
2
3
4
5
6
7
8
9
10
class ObjectOuter {
object Inner{
fun method(){
println("I'm in inner class")
}
}
}
fun main(args: Array<String>) {
ObjectOuter.Inner.method()
}

伴生对象(Companion object)

在kotlin中是没有static关键字的,也就意味着没有了静态方法和静态成员。那么在kotlin中如果想表示这种概率,取而代之的是==包级别函数==这里的伴生对象。他们的区别在下面会介绍。

包级别函数是指在kotlin中fun可以不依赖于class,直接新建.kt文件在文件中直接可以写fun方法,调用的时候IDE会自动导入文件急fun的方法名可以在其他类中直接使用。(其实编译后它还是在类中的,只不过类名称是.kt的文件名)

伴生对象的语法形式:

1
2
3
4
5
class A{
companion object 伴生对象名(可以省略){
//define method and field here
}
}

示例:

1
2
3
4
5
6
7
8
9
10
11
class ObjectTest {

companion object MyObjec{

val a = 20

fun method() {
println("I'm in companion object")
}
}
}

使用:

1
2
3
4
5
6
7
8
9
fun main(args: Array<String>) {
//方式一
ObjectTest.MyObject.method()
println(ObjectTest.MyObject.a)

//方式二(推荐方式)
ObjectTest.method()
println(ObjectTest.a)
}

在定义时如果省略了伴生对象名称,那么编辑器会为其提供默认的名字Companion。在方法二中,我们是直接通过类名称.方法名()的形式调用的,我们在没有生成ObjectTest类对象时,直接调用了其伴生对象中定义的属性和方法,和java中的静态方法很相似。

通过javap命令,让我们看下其生成的字节码:

注意红框中,这个MyObject成员变量的类型,是用$符号连接的,那么说明我们在定义伴生对象的时候,实际上是把它当作了静态内部类来看待的,并且目标类会持有该内部类的一个应用,最终调用的是定义在这个静态内部类中的实例方法。

那么伴生对象和包级别函数的区别是什么呢?我们反编译下kt文件。

可以看出,一个名叫ObjectTest2.kt文件实际上会生成一个名叫ObjectTest2Kt的类,而这个顶级函数是作为这个类的静态方法的形式存在的。
所以实际上类中的静态方法和内部类中的实例方法的区别,因为成员内部类中的方法是可以访问外部内定义的方法和成员变量的,哪怕是private的,而静态方法是做不到这一点的。

对象表达式(Object Expression)

先来看下java中的匿名内部类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface Contents {
void absMethod();
}
public class Hello {

public Contents contents() {
return new Contents() {

@Override
public void absMethod() {
System.out.println("method invoked...");
}
};
}

public static void main(String[] args) {

Hello hello = new Hello();
hello.contents().absMethod(); //打印method invoked...
}
}

这里指出两点java中内部类的局限性

  1. 如果在匿名内部类中添加了一些方法和属性,那么在外部是无法调用的
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
return new Contents() {
private int i = 1;

public int value() {
return i;
}

@Override
public void absMethod() {
System.out.println("method invoked...");
}
};

public static void main(String[] args) {

Hello hello = new Hello();
hello.contents().absMethod();
hello.value(); //Cannot resolve method 'value()'
}

当你想使用这个value方式的时候,编译会报错,因为java的多态导致父类型的引用是无法知晓子类添加的方法的。

  1. 一个匿名内部类肯定只能实现一个接口或者继承一个类。

在看看kotlin的对象表达式:
语法:

1
object [ : 接口1,接口2,类型1, 类型2]{}    //中括号中的可省略

示例:
实现一个接口或者类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface AA {
fun a()
}

fun main(args: Array<String>) {

val aa = object : AA {
override fun a() {
println("a invoked")
}
}

aa.a()
}

不实现任何接口和类,并且在匿名内部类中添加方法

1
2
3
4
5
6
7
8
9
10
fun main(args: Array<String>) {

val obj = object {
fun a() {
println("a invoked")
}
}

obj.a() //打印:a invoked
}

从这个例子看出kotlin中,新添加的方式是可以调用的
那么,实现多个接口和类(NB)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
fun main(args: Array<String>) {
val cc = object : AA, BB() {
override fun a() {

}

override fun b() {

}

}

cc.a()
cc.b()

}//注意写法

kotlin官方文档上的一句话:匿名对象只定义局部变量和private成员变量时,才能体现它的真实类型,如果你是将匿名对象作为public函数的返回值或者是public的属性时,你只能将它看作是它的父类,当然你不指定类型时就当Any看待,这时你在匿名对象中添加的方法和属性时不能被访问的。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class MyTest {

private val foo = object {
fun method() {
println("private")
}
}

val foo2 = object {
fun method() {
println("public")
}
}

fun m() = object {
fun method(){
println("method")
}
}

fun invoke(){

val local = object {
fun method(){
println("local")
}
}

local.method() //编译通过
foo.method() //编译通过
foo2.method() //编译通不过
m().method() //编译通不过
}
}

最后:
object声明:当第一次访问它时才初始化,是一种懒加载
伴生对象:当他对应的类被加载后,它才初始化,类似java中的额静态代码块
对象表达式:一旦被执行,就立刻初始化。