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