Kotlin进阶系列-Object关键字

1. 前言

kotlin中有一个重要的关键字object,其主要使用场景有以下三种:

  • 对象表达式(Object Expression)

  • 伴生对象(Companion Object)

  • 对象声明(Object Expression)

接下来本文将分别介绍这三种场景的示例,使用方法和技术要点。

2. 对象表达式(Object Expression)

对象表达式用于生成匿名类的对象,该匿名类可以直接从零开始创建,也可以继承自某一父类,或者实现某一接口。

2.1 示例

class OuterObjExpression {
    private val objExpression = object {
        fun objExpressionPrint() {
            println("objExpressionPrint")
        }
    }
}
复制代码

2.2 使用方法

对象表达式可以用于本地变量,成员变量,或者函数的返回值。

对象表达式需要注意的是其成员的可见性。直接使用被对象表达式赋值的成员,就可以访问对象表达式中可见的成员。例如:

class OuterObjExpression {
    private val objExpression = object {
        fun objExpressionPrint() {
            println("objExpressionPrint")
        }
    }

    fun printX() {
        println(objExpression.objExpressionPrint())
    }
}
复制代码

2.3 技术要点

假如一个匿名类对象用作一个本地变量,或者私有变量或者私有非inline函数的返回值,则其所有成员都可以被访问。例如上面的例子。

假如一个匿名类对象用作一个公有变量,公有函数的返回值,以及私有inline函数的返回值时,它的实际类型如下:

  • 假如该匿名类未声明父类或者接口,其实际类型是Any

  • 假如该匿名类声明一个父类型(只有一个父类,或者只实现一个接口),其实际类型是其父类型。

  • 假如该匿名类超过一个父类型,其实际父类型是其显式声明的类型。

代码示例如下所示:

class C {
     // 实际类型是Any. x不可访问
   fun getObject()object {
        val x: String = "x"
    }

     // 实际类型是A;x不可访问;funFromA可以访问
   fun getObjectA()object: A {
        override fun funFromA() {}
        val x: String = "x"
    }

     // 实际类型是B; funFromA() 和 x 不可以访问
   fun getObjectB(): B = object: A, B {  // explicit return type is required
   override fun funFromA() {}
        val x: String = "x"
    }
}
复制代码

其成员可见性和实际类型是相对应的,比如getObjectB返回的实际类型是B,那自然不能访问接口A的函数funFromA,以及新增的属性x

3. 伴生对象(Companion Object)

众所周知,Kotlin是没有static关键字的,为了使用这种概念,其提供了包级别函数及伴生对象。这两者的区别是伴生对象可以直接访问其外部类中私有成员,而包级别函数不行。

3.1 示例

伴生对象的示例如下:

class CompanionOuter {
    companion object CompanionInner {
        fun companionPrint() {
            println("companionPrint")
        }
    }
}
复制代码

其中,伴生对象名(CompanionInner)可以被省略,此时该伴生对象会被赋予一个默认的名字Companion

3.2 使用方法

可以使用外部类名直接调用伴生对象的函数,其中内部伴生对象名可以被忽略。另外,外部函数名可以直接用于表示其伴生对象的引用,此时后面也可以加上内部伴生对象名,如果未定义伴生对象名则使用默认伴生对象名Companion。代码示例如下:

fun TestFun() {
    CompanionOuter.companionPrint()   //推荐
    CompanionOuter.CompanionInner.companionPrint()

    val a1 = CompanionOuter
    val a2 = CompanionOuter.CompanionInner
}
复制代码

3.3 技术要点

上述示例和函数对应的java代码如下:

public final class CompanionOuter {
   @NotNull
   public static final CompanionOuter.CompanionInner CompanionInner = new CompanionOuter.CompanionInner((DefaultConstructorMarker)null);

   public static final class CompanionInner {
      public final void companionPrint() {
         String var1 = "companionPrint";
         System.out.println(var1);
      }

      private CompanionInner() {
      }

       // $FF: synthetic method**
   public CompanionInner(DefaultConstructorMarker $constructor_marker) {
         this();
      }
   }
}

public final class CompanyObjTestKt {
   public static final void TestFun() {
      CompanionOuter.CompanionInner.companionPrint();
      CompanionOuter.CompanionInner.companionPrint();
      CompanionOuter.CompanionInner a1 = CompanionOuter.CompanionInner;
      CompanionOuter.CompanionInner a2 = CompanionOuter.CompanionInner;
   }
}
复制代码

可见伴生对象在java代码中会对应生成一个静态内部类,在外部类中会生成该静态内部类的静态对象,对该伴生对象的调用实际是通过这个静态对象实现的。另外,虽然伴生对象看着像其他语言中的静态对象,但是其仍然可以继承接口。

4. 对象声明(Object Declaration)

对象声明是为了更好的声明单例模式。对象声明并非一个表达式,因此其不能放在赋值语句的右侧。

4.1 示例

object ObjDeclaration {
    fun objDeclarationPrint() {
        println("objDeclarationPrint")
    }
}
复制代码

4.2 使用方法

可以直接使用对象声明中的对象名调用其内部定义的方法,就像在Java中调用静态方法一样。例如:

fun TestFun() {
    ObjDeclaration.objDeclarationPrint()
}
复制代码

4.3 技术要点

4.1中示例对应的java代码如下:

public final class ObjDeclaration {
   @NotNull
   public static final ObjDeclaration INSTANCE;

   public final void objDeclarationPrint() {
      String var1 = "objDeclarationPrint";
      System.out.println(var1);
   }

   private ObjDeclaration() {
   }

   static {
      ObjDeclaration var0 = new ObjDeclaration();
      INSTANCE = var0;
   }
}
复制代码

可以看到其对应的java代码就是单例模式。此处值得注意的是其初始化是懒初始化,即在第一次访问的时候才会初始化。原因从其对应的java代码可以看出。INSTANCE的赋值是在静态代码块中,众所周知,静态代码库的执行时机是在java类运行的初始化时(深入Java虚拟机),所以kotlin对象声明的初始化是懒初始化。

5. 小结

本文介绍了object关键字在对象表达式,伴生对象,对象声明中的使用,并介绍了这几种使用场景的使用方法和技术要点。其中,对象表达式用于生成匿名类对象,伴生对象用于替代其他语言中的静态对象或者方法,对象声明用于更简单的实现单例模式。

6. 参考文档

Object expressions and declarations

Kotlin学习系列之:object关键字的使用场景

猜你喜欢

转载自juejin.im/post/7110186621156196382