Kotlin中object关键字在多种情况下出现,但是他们都遵循同样的核心理念:这个关键字定义一个类并同时创建一个实例对象。使用它的不同场景:
- 对象声明:定义单例对象
- 伴生对象:可以持有工厂方法和其他与这个类相关,但在调用时并不依赖类实例的方法。他们的成员可以通过类名来访问。
- 对象表达式:替代Java的匿名内部类。
有时候,我们需要创建一个对某个类做了轻微改动的类的对象,而不用为之显式声明新的子类。 Kotlin 用对象表达式和对象声明处理这种情况。
Demo传送门,求个Star
对象声明
对象声明可以用来轻易实现单例模式,它隐式创建一个类并返回类的对象,我们称之为Single Object(单例对象),object声明的对象可以拥有自己的方法和属性:
//onject声明的对象可以拥有方法和自己的属性,简称为对象声明
object DataClass{
fun getData(){
}
val data = 1
}
同时可以实现接口或者继承其他类:
object DataClass2 : A {
}
具体使用:
import `interface`.People
class TestObject {
/**
* 单例对象
*/
object SingleObject{
//object声明的对象可以拥有方法和自己的属性
var num = 0
fun totalNum():Int{
num += 1
return num
}
}
/**
* 单例对象可以有超类(实现接口或者继承其他类)
*/
object people:People{
override fun setKind(): String {
return "American"
}
override val name: String
get() = "tony"
}
}
fun main(){
//调用单例对象
System.out.println(TestObject.SingleObject.num)
System.out.println(TestObject.SingleObject.totalNum() == 1)
System.out.println(TestObject.SingleObject.totalNum() == 1)
System.out.println(TestObject.SingleObject.totalNum())
System.out.println(TestObject.people.setKind())
System.out.println(TestObject.people.name)
}
输出:
0
true
false
3
American
tony
所以称对象声明为单例对象更加合适点,其就是生成单例模式的class对象,它拥有方法和属性。
伴生对象
类内部的对象声明可以用 ==companion== 关键字标记:
class CompainClass{
companion object Factory{
}
}
该伴生对象的成员可通过只使用类名作为限定符来调用:
val instance = CompainClass.creatFartory()
伴生对象也可以省略对象名称:
class CompainClass2{
companion object
}
伴生对象可以实现其他超类型:
class CompainClass3{
companion object : A{
override val name: String
get() = "伴生对象实现接口A"
}
}
使用的时候就可以省略对象名称,直接用类名代替啦:
fun main(){
val instance = CompainClass.creatFartory()
val instance2 = CompainClass2.Companion
val instance3 = CompainClass2
println(CompainClass3.name)
}
具体使用实例:
import `interface`.People
class CompanionObject {
//外部类私有属性
private var name:String = "xlu"
fun getMyName():String{
//外部类可以访问伴生对象的私有属性
return myName
}
companion object test{
//伴生对象的私有属性
private var myName:String = "companion xlu"
//JvmStatic注解生成static成员
@JvmStatic var test = "static"
fun getCompaionName():String{
val objects = create()
//伴生对象可以访问外部类的私有属性
return CompanionObject().name
}
fun create():CompanionObject{
return CompanionObject()
}
}
/**
* 一个类最多一个伴生对象,以下会报错
*/
companion object second:People{
//伴生对象可以有超类型
override fun setKind(): String {
return super.setKind()
}
}
}
fun main(){
System.out.println(CompanionObject.test)
System.out.println(CompanionObject.getCompaionName())
}
伴生对象可以访问外部类的私有属性,外部类方法可以访问伴生对象私有属性。他们似乎没有什么约束,为什么这么设计呢?
我们在上面讲述过了对象声明就是一种单例模式的class对象,那么companion object可以理解是class为名称的单例对象,它任然是class的成员对象。
- 伴生对象很像java中的static静态成员,kotlin是没有static这个概念的
- kotlin中可以用顶层函数和伴生对象替代static
- 可以用@JvmStatic生成真正的静态成员
- 伴生对象和外部类的私有属性可以互相访问
对象表达式:
一个对象:继承父类、抽象类、接口的匿名类的对象
定义方式:
object:父类型{
}
如果我们只需要“一个对象”,并不需要特殊超类型,那么我们可以简单地写:
fun foo() {
val adHoc = object {
var x: Int = 0
var y: Int = 0
fun add(){
x++
y++
}
}
print(adHoc.x + adHoc.y)
}
实际应用:
class ObjectExpression {
/**
* 匿名类的对象,简称“对象表达式”
*/
var people = object :Action(){
override fun eat(): String {
return "油条"
}
override fun run() {
TODO("Not yet implemented")
}
}
fun main(){
people.eat()
people.run()
}
}
abstract class Action{
abstract fun eat():String
abstract fun run()
fun sleep(){
//do something
}
}
如果你不了解匿名类,建议先去了解下java中的匿名类。
如果将对象表达式作为函数的返回对象,其类型不一定是匿名类的真实类型
- 私有函数(private fun)修饰:返回的是匿名类真实类型
- 公有函数(public fun)修饰:返回的是父类型或者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”
}
}
实例二:
class ObjectExpression2 {
//私有方法
private fun A() = object : Parent(){
val x:String = "x"
}
//公有方法
public fun B() = object : Parent(){
val x:String = "x"
}
fun test(){
val objectPrivate = A()
val objectPublic = B()
//私有方法返回的是匿名类的真实类型,可以访问x和y对象
System.out.println(objectPrivate.x + objectPrivate.y)
//公有方法返回的是父类型对象,Parent()或者Any(),只能访问父类型的对象
System.out.println(objectPublic.y)
//访问x报错
System.out.println(objectPublic.x)
}
}
open class Parent{
val y:String = "y"
}
定义在函数中的对象表达式可以访问函数内的属性
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 静态初始化器的语义相匹配。
如果有错误和不同的地方,请大佬们指出