Kotlin : object、companion object、 @JvmField、 @JvmStatic、顶层函数、单例
object关键字:单例对象
在Kotlin中,object关键字用于定义一个单例对象或一个匿名类。
- 用途:直接定义一个单例(全局唯一的实例),无需手动实例化。
- 特点:
- 不能有构造函数(因为实例在定义时直接创建)。
- 可以直接通过类名访问属性和方法。
- 示例:
object DatabaseManager {
fun connect() { println("连接数据库") }
}
// 使用:
DatabaseManager.connect() // 直接调用,无需 newcompanion object:伴生对象
在Kotlin中,class关键字用于定义一个类。类可以包含属性(变量)和方法(函数)。伴生对象(companion object)是Kotlin中的一种特殊结构,它允许我们在类级别定义静态成员(类似于Java中的静态成员)。伴生对象中的成员可以通过类名直接访问,而无需创建类的实例。
- 用途:在类内部定义“静态成员”(但本质是一个单例对象)。
- 特点:
- 可以通过类名直接访问其内部的属性和方法。
- 可以继承接口或实现其他逻辑。
- 示例:
class MyClass {
companion object {
const val TAG = "MyClass"
fun create(): MyClass = MyClass()
}
}
// 使用:
println(MyClass.TAG) // 输出:MyClass
val instance = MyClass.create()const val
Kotlin中的const val
const val用于声明编译时常量。这意味着常量的值在编译时就已经确定,并且在运行时无法改变。const修饰符只能用于顶层(文件级别)、伴生对象或嵌套类中的属性,并且这些属性必须是val类型,不能是var。
Java中的public static final
在Java中,public static final用于声明公共的静态最终变量。这些变量在类加载时初始化,并且在运行时无法修改其值。它们通常用于定义常量,如错误码、配置参数等。
4. 顶层函数(Top-level Functions)
- 用途:直接在 Kotlin 文件中定义函数,无需包裹在类中。
- 特点:
- 编译后会被包装到以文件名 +
Kt结尾的类中(如MyFile.kt→MyFileKt)。 - 在 Java 中可以通过
文件名Kt.函数名()调用。
- 编译后会被包装到以文件名 +
- 示例:
// 文件:Utils.kt
fun printHello() {
println("Hello World!")
}
// 使用(Kotlin):
printHello() // 直接调用
// 在 Java 中:
UtilsKt.printHello();综合用法:
// 文件:App.kt
// 顶层函数
fun showToast(message: String) {
println("Toast: $message")
}
// 数据类
data class Product(val id: Int, val name: String)
// 单例对象(全局管理器)
object NetworkManager {
fun fetchData() { println("下载数据...") }
}
class User {
companion object Factory {
fun create(): User = User()
}
}
// 使用:
fun main() {
showToast("Hello!") // 顶层函数
val product = Product(1, "手机") // data class
NetworkManager.fetchData() // 单例对象
val user = User.Factory.create() // 伴生对象
}问题
在 Kotlin 中,object 和 Companion object 都能表示单例对象,为啥还要 Companion object。
object 和 companion object 虽然都涉及单例模式,但它们的 设计意图 和 使用场景 完全不同。以下是它们的核心区别和设计哲学:
1. object:独立的单例对象
- 作用:定义一个 全局单例对象,直接通过类名访问。
- 特点:
- 独立于任何类存在(或作为嵌套对象)。
- 可以继承其他类或实现接口。
- 适合工具类、全局配置、服务定位器等。
- 示例:
object DatabaseManager {
fun connect() { /* ... */ }
}
// 使用
DatabaseManager.connect()2. companion object:类的伴生对象
- 作用:定义一个 与类绑定的单例对象,用于存放与类相关的“静态”成员。
- 特点:
- 绑定到类:必须声明在某个类内部。
- 访问权限:可以访问外部类的私有成员(包括私有构造函数)。
- 替代 Java 的静态成员:但更灵活(可以实现接口、扩展函数等)。
- 示例:
class MyClass {
companion object {
const val TAG = "MyClass"
fun create(): MyClass = MyClass()
}
}
// 使用
MyClass.TAG
MyClass.create()虽然object和companion object都表示单例,但它们的设计目标不同:
| 特性 | object | companion object |
|---|---|---|
| 作用域 | 全局或嵌套在类中 | 必须属于某个类 |
| 访问外部类私有成员 | 不能 | 可以 |
| 初始化时机 | 首次访问时 | 外部类加载时(与类一起初始化) |
| 典型用途 | 工具类、服务、全局配置 | 工厂方法、静态常量、类相关逻辑 |
object:用于独立的单例对象,作用域不绑定任何类。companion object:用于与类绑定的单例对象,提供类级别的“静态”成员,同时支持更灵活的访问和继承。
Kotlin 通过区分两者,既保持了代码的语义清晰性,又提供了比 Java 静态成员更强大的功能。这是 Kotlin 设计哲学的一部分:通过明确的语法表达意图,同时避免 Java 静态成员的局限性。
关键区别:
- 访问权限:
companion object可以访问外部类的私有成员(如私有构造函数),这是object做不到的。- 示例:实现工厂模式时,通过
companion object调用私有构造函数:
class User private constructor(val name: String) {
companion object {
fun create(name: String): User {
return User(name) // 访问私有构造函数
}
}
}- 作用域绑定:
companion object是类的一部分,逻辑上更紧密。例如,MyClass.Companion的存在是为了明确这些成员属于MyClass。- 而
object是独立的,不依赖任何类。
- 与 Java 的互操作性:
companion object的成员在 Java 中可以通过MyClass.Companion.getX()调用,但 Kotlin 提供了语法糖,允许直接写MyClass.x(类似静态成员)。- 普通的
object在 Java 中需要通过MyObject.INSTANCE访问。
Kotlin 的伴生对象(Companion Object)与 Java 的static变量之间的差异
1.Java 的静态变量(static)
在 Java 中:
- 静态变量属于类本身(而非对象实例),可以直接通过类名访问(如
ClassName.staticVar)。 - 静态方法可以直接访问静态变量,但**非静态方法(实例方法)**也能访问静态变量,因为实例方法属于对象,而对象可以访问类的静态成员。
public class MyClass {
static int myStaticVar = 42;
public void instanceMethod() {
// 实例方法可以访问静态变量
System.out.println(myStaticVar);
}
public static void staticMethod() {
// 静态方法可以访问静态变量
System.out.println(myStaticVar);
}
}2.Kotlin 的伴生对象(Companion Object)
Kotlin 没有static关键字,而是通过伴生对象实现类似功能。但伴生对象本质上是类的单例实例:
- 伴生对象中的成员(变量或函数)不是静态的,而是属于一个单例对象。
- 在 Java 中调用时,伴生对象的成员会被编译成静态成员(通过
Companion类),但 Kotlin 中可以直接通过类名访问。
class MyClass {
companion object {
val myVar = 42
}
fun instanceMethod() {
// 实例方法可以访问伴生对象的成员
println(myVar) // 等同于 MyClass.Companion.myVar
}
}3.为什么onCreate可以访问伴生对象的context?
onCreate是实例方法,但它属于SunnyWeatherApplication类的实例。- 伴生对象的成员(如
context)可以通过类名直接访问(如SunnyWeatherApplication.context),但本质上它们是伴生对象实例的成员。 - 在实例方法中,可以直接访问伴生对象的成员(无需类名前缀),因为 Kotlin 隐式处理了这一点。
4.Java 视角的等价代码
- 虽然
onCreate是实例方法,但它可以访问Companion单例的成员,因为Companion是静态的。 - Kotlin 在语法层面隐藏了这些细节,使得伴生对象的成员看起来像是静态的。
public class SunnyWeatherApplication extends Application {
private static volatile SunnyWeatherApplication.Companion Companion;
// 伴生对象内部的单例实例
public static final class Companion {
private Context context;
public final Context getContext() { return context; }
public final void setContext(Context value) { context = value; }
}
@Override
public void onCreate() {
super.onCreate();
// 通过 Companion 单例设置 context
Companion.setContext(getApplicationContext());
}
}更新: 2025-03-24 11:57:36
原文: https://www.yuque.com/dongpozhouzi-mshe3/zhm85g/em0a9gtznexdg0sf