有时候,我们需要创建一个对某个类做了轻微改动的类的对象,而不用为之显式声明新的子类。 Kotlin 用对象表达式和对象声明处理这种情况。
1.对象表达式
主要用来创建匿名内部类的对象
我们回顾一下匿名内部类的特点:
- )匿名内部类是没有名字的类
- )匿名内部类 我们一般只想在局部使用一次,为此我们并不愿意重新定义一个新的类。
- )匿名内部类一定是继承了某个父类,或是实现了某个接口,来完成局部代码需要的功能
- )运行时将该匿名内部类当做它所实现的接口或是所继承的父类看待。
- )匿名内部类简化了代码结构和代码量,这是因为它越过类的定义直接得到一个对象
1.1 )声明语法:
我们使用object关键字 声明一个对象表达式(也就是匿名内部类),kotlin继承使用冒号声明,所以后面跟冒号: 和 继承的父类或接口,继承的父类和接口如果有多个,之间使用逗号分割
声明格式:
object : 继承的父类或实现的接口,使用逗号隔开 {
需要实现的方法
}
示例:
class AnonymousClass {
private lateinit var listener : OnClickListener
fun setOnClickListener(listener : OnClickListener) {
this.listener = listener
}
fun click() {
listener.onClick()
}
}
interface OnClickListener {
fun onClick()
}
使用:
AnonymousClass().setOnClickListener(object :OnClickListener{
override fun onClick() {
}
})
上述代码等价于我们单独声明一个新类,继承了OnClickListener接口,然后再创建一个对象传入setOnClickListener,但如果我们使用对象表达式极大的简化了代码结构及代码量,不使用对象表达式的示例如下:
class MyClick :OnClickListener{
override fun onClick() {
}
}
AnonymousClass().setOnClickListener(MyClick())
1.2 )如果父类有一个构造函数,则必须传递适当的构造函数参数给对象表达式。 多个父类可以由跟在冒号后面的逗号分隔的列表指定
open class A(x: Int) {
public open val y: Int = x
})
interface B {
/*……*/ }
val ab: A = object : A(1), B {
override val y = 15
}
1.3. )任何时候,如果我们只需要“一个对象而已”,并不需要特殊超类型,我们可以省略掉冒号:
示例:
fun foo() {
val adHoc = object {
var x: Int = 0
var y: Int = 0
}
print(adHoc.x + adHoc.y)
}
1.4)请注意,匿名对象可以用作只在本地和私有作用域中声明的类型。如果你使用匿名对象作为公有函数的返回类型或者用作公有属性的类型,那么该函数或属性的实际类型会是匿名对象声明的超类型,如果你没有声明任何超类型,就会是 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”
}
}
1.5)对象表达式中的代码可以访问来自包含它的作用域的变量。
示例:
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中经常会使用到单例模式
如下:
public class RetrofitManager {
private static RetrofitManager mInstance;
public static RetrofitManager getInstance() {
if (mInstance == null) {
synchronized (RetrofitManager.class) {
if (mInstance == null) {
mInstance = new RetrofitManager();
}
}
}
return mInstance;
}
}
在Kotlin中,可以使用对象声明来快速实现单例模式。
2.1)通过object关键字定义对象声明。一个对象声明可以高效地以一句话来定义一个类和该类的变量。对象声明不能有任何构造函数,可以添加属性、方法、初始化语句快。
如下:
object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
// ……
}
val allDataProviders: Collection<DataProvider>
get() = // ……
}
2.2)对象声明不是一个表达式,不能用在赋值语句的右边。
2.3)对象声明的初始化过程是线程安全的并且在首次访问时进行。
2.4)如需引用该对象,我们直接使用其名称即可:
DataProviderManager.registerDataProvider(……)
2.5)对象声明可以有超类型
object DefaultListener : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
…… }
override fun mouseEntered(e: MouseEvent) {
…… }
}
注意:对象声明不能在局部作用域(即直接嵌套在函数内部),但是它们可以嵌套到其他对象声明或非内部类中。
2.6)对象声明到底如何实现单例模式的呢?
如下我们定义了一个对象声明,我们想实现一个用户管理的单例,这个单例有一个用户对象loginUser,初始化时赋值,并有两个操作该对象的方法。
object UserManager {
private var loginUser : User? = null
init {
loginUser = User("张三" , 12)
}
fun setLoginUser(loginUser : User) {
this.loginUser = loginUser
}
fun getLoginUserName() : String? =
loginUser?.name
}
使用Android studio将上述kotlin代码编译为java 代码,操作步骤为:
依次点击工具类Tools - Kotlin -Show Kotlin Bytecode ,会打开Kotlin Bytecode面板,点击该面板的Decompile按钮,会将当前文件的kotlin代码转换为Java代码。
我们查看一下上述UserManager代码转换为Java代码以后是什么样的。
public final class UserManager {
private static User loginUser;
@NotNull
public static final UserManager INSTANCE;
public final void setLoginUser(@NotNull User loginUser) {
Intrinsics.checkNotNullParameter(loginUser, "loginUser");
UserManager.loginUser = loginUser;
}
@Nullable
public final String getLoginUserName() {
User var10000 = loginUser;
return var10000 != null ? var10000.getName() : null;
}
private UserManager() {
}
static {
UserManager var0 = new UserManager();
INSTANCE = var0;
loginUser = new User("张三", 12);
}
}
可以看到,编译后的代码使用 静态代码块和静态变量 来实现了单例模式。
1.构造函数是private私有的,说明外部无法创建该对象,只能内部创建
private UserManager() {
}
2. 创建了一个该类自身的static静态对象(INSTANCE)。
类的变量(本例中的loginUser)也都是static静态的。
private static User loginUser;
public static final UserManager INSTANCE;
使用static代码块完成初始化
static {
UserManager var0 = new UserManager();
INSTANCE = var0;
loginUser = new User(“张三”, 12);
}
我们知道Java静态代码块中的代码会在类加载JVM时运行,且只被执行一次,也就是说这些代码不需要实例化类就能够被调用。一般情况下,如果有些代码必须在项目启动的时候就执行的时候,就需要使用静态代码块,所以静态块常用来执行类属性的初始化
这里的静态代码块用来初始化了单例。
三、伴生对象
3.1)类内部的对象声明可以用 companion 关键字标记: 这样的对象声明就叫做伴生对象
示例:
class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}
3.2 )伴生对象的成员可通过只使用类名作为限定符来调用:
val instance = MyClass.create()
3.3)可以省略伴生对象的名称,在这种情况下将使用名称 Companion
class MyClass {
companion object {
}
}
val x = MyClass.Companion
3.4 )即使伴生对象的成员看起来像其他语言的静态成员,在运行时他们仍然是真实对象的实例成员,而且,例如还可以实现接口
interface Factory<T> {
fun create(): T
}
class MyClass {
companion object : Factory<MyClass> {
override fun create(): MyClass = MyClass()
}
}
val f: Factory<MyClass> = MyClass
3.5) 一个类里面只能声明一个内部关联对象,即关键字 companion 只能使用一次
3.6) 伴生对象是怎么实现的呢?
我们定义一个伴生对象:
class UserInfo {
companion object {
val id = "userId"
fun getUserInfo() =
id.uppercase()
}
}
同样的,我们将上述代码转换为Java代码
public final class UserInfo {
@NotNull
private static final String id = "userId";
@NotNull
public static final UserInfo.Companion Companion = new UserInfo.Companion((DefaultConstructorMarker)null);
@Metadata(
mv = {
1, 7, 1},
k = 1,
d1 = {
"\u0000\u0014\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u000e\n\u0002\b\u0004\b\u0086\u0003\u0018\u00002\u00020\u0001B\u0007\b\u0002¢\u0006\u0002\u0010\u0002J\u0006\u0010\u0007\u001a\u00020\u0004R\u0014\u0010\u0003\u001a\u00020\u0004X\u0086D¢\u0006\b\n\u0000\u001a\u0004\b\u0005\u0010\u0006¨\u0006\b"},
d2 = {
"Lcom/jf/simple/other/UserInfo$Companion;", "", "()V", "id", "", "getId", "()Ljava/lang/String;", "getUserInfo", "JFUtils_master.app.main"}
)
public static final class Companion {
@NotNull
public final String getId() {
return UserInfo.id;
}
@NotNull
public final String getUserInfo() {
String var1 = ((UserInfo.Companion)this).getId();
if (var1 == null) {
throw new NullPointerException("null cannot be cast to non-null type java.lang.String");
} else {
String var10000 = var1.toUpperCase(Locale.ROOT);
Intrinsics.checkNotNullExpressionValue(var10000, "(this as java.lang.Strin….toUpperCase(Locale.ROOT)");
return var10000;
}
}
private Companion() {
}
// $FF: synthetic method
public Companion(DefaultConstructorMarker $constructor_marker) {
this();
}
}
}
1.伴生对象声明的变量,变成了所在类的静态变量 。
private static final String id = “userId”;
2.生成了一个静态类Companion,伴生对象声明的方法变成了Companion的同名方法,并且所在类生成了一个Companion的静态对象
public static final UserInfo.Companion Companion = new UserInfo.Companion((DefaultConstructorMarker)null);
上面两步,其实真正做的 就是把伴生对象 声明的变量和方法,变成了所在类的静态对象和静态方法,所以我们也可以这么理解:一个类的伴生对象用于给这个类声明静态变量和静态方法。
综上,对象声明和伴生对象是不同的东西
对象声明: 用来实现一个类的单例模式。
伴生对象:用于给所在类声明静态变量和静态方法,并不用于实现类的单例