companion object เป็นกลไกของภาษา Kotlin ที่แทนที่คีย์เวิร์ด static ของ Java สำหรับการประกาศสมาชิกแบบสแตติกของคลาส Kotlin ไม่มี static ในตัว — แต่จะใช้การประกาศ object พร้อมกับตัวปรับแต่ง companion ภายในคลาส ตามข้อมูลจาก JetBrains, 2025 companion object อนุญาตให้เรียกเมธอดและคุณสมบัติผ่านชื่อคลาส ทำให้เทียบเท่ากับสมาชิกแบบสแตติกในระบบนิเวศ Java อย่างสมบูรณ์
ประเด็นสำคัญ
companion object เป็นประเภทพิเศษของ การประกาศ object ใน Kotlin ที่ถูกทำเครื่องหมายด้วยคีย์เวิร์ด companion ซึ่งอนุญาตให้ประกาศสมาชิกที่เป็นของคลาสแทนที่จะเป็นอินสแตนซ์ — นั่นคือสมาชิกแบบสแตติกในศัพท์ของ Java ต่างจาก Java ที่ static เป็นตัวปรับแต่งสำหรับฟิลด์และเมธอดแต่ละรายการ ใน Kotlin สมาชิกแบบสแตติกจะถูกจัดกลุ่มภายในออบเจกต์คู่หูเดียว
คลาสใดๆ ใน Kotlin สามารถมี companion object ได้หนึ่งตัวพอดี สมาชิกของออบเจกต์นี้สามารถเข้าถึงได้ผ่านชื่อคลาสโดยไม่ต้องสร้างอินสแตนซ์: MyClass.method() ในแง่ของไบต์โค้ด companion object จะถูกคอมไพล์เป็นคลาสภายในแยกต่างหาก และเมธอดของมันจะกลายเป็นเมธอดสแตติกของคลาสภายนอกเมื่อใช้คำอธิบายประกอบ @JvmStatic
ตามข้อมูลจาก Google I/O 2024 companion object ได้กลายเป็นรูปแบบหลักสำหรับเมธอดโรงงาน ค่าคงที่ และฟังก์ชันอำนวยความสะดวกในแอปพลิเคชัน Android สมัยใหม่ที่เขียนด้วย Kotlin นักพัฒนาเลือกใช้แทนคลาสอำนวยความสะดวกที่มีฟังก์ชันระดับแพ็คเกจ เนื่องจาก companion object รักษาความเชื่อมโยงเชิงตรรกะกับคลาสเจ้าของ
ใช้ companion object สำหรับการจัดกลุ่มบริบทสแตติก — ค่าคงที่ โรงงาน เมธอดช่วยเหลือที่เกี่ยวข้องเชิงตรรกะกับคลาสแต่ไม่ต้องการอินสแตนซ์
ไวยากรณ์พื้นฐานของ companion object นั้นง่าย: คีย์เวิร์ด companion ถูกวางไว้ก่อนการประกาศ object ภายในคลาส หากไม่ได้ระบุชื่อ ออบเจกต์จะได้รับชื่อ Companion ซึ่งสามารถเข้าถึงได้อย่างชัดแจ้งหรือโดยนัย
class User {
companion object {
const val TABLE_NAME = "users"
fun create(name: String): User {
return User(name)
}
}
val name: String
constructor(name: String) { this.name = name }
}
// Access via class name
val table = User.TABLE_NAME
val user = User.create("Alice")
ในตัวอย่างข้างต้น TABLE_NAME และ create() สามารถเข้าถึงได้ผ่าน User.TABLE_NAME และ User.create() สังเกตตัวปรับแต่ง const สำหรับค่าคงที่ชนิดดั้งเดิม — มันรับประกันการแทรกค่าในเวลาคอมไพล์ คล้ายกับ Java static final สำหรับชนิดดั้งเดิมและ String
หาก companion object ไม่มีชื่อ สามารถเข้าถึงได้ผ่านชื่ออัตโนมัติ Companion: User.Companion.TABLE_NAME ในทางปฏิบัติแทบไม่จำเป็น แต่มีประโยชน์เมื่อเรียกจากโค้ด Java หรือเมื่อใช้รีเฟลกชัน
// Both calls are equivalent
User.create("Bob")
User.Companion.create("Bob")
// Getting reference to companion object
val companion: User.Companion = User
companion object สามารถมีชื่อได้ ซึ่งช่วยเพิ่มความสามารถในการอ่านโค้ดและอนุญาตให้เข้าถึงด้วยชื่อที่มีความหมาย Factory เป็นชื่อที่พบบ่อยที่สุดสำหรับ companion object ที่ใช้เป็นโรงงาน
class HttpResponse {
companion object Factory {
fun ok(body: String): HttpResponse = HttpResponse(200, body)
fun notFound(): HttpResponse = HttpResponse(404, "Not Found")
fun serverError(): HttpResponse = HttpResponse(500, "Internal Error")
}
}
// Access via Factory name
val response = HttpResponse.Factory.ok("data")
companion object ที่มีชื่อช่วยบันทึกวัตถุประสงค์ของสมาชิกแบบสแตติก ในการปรับโครงสร้างโค้ด ชื่อทำให้โค้ดอธิบายตนเองได้ — นักพัฒนาเห็นทันทีว่า Factory สร้างอินสแตนซ์ของ HttpResponse
คลาสสามารถมี companion object ได้เพียงหนึ่งตัว นี่คือความแตกต่างพื้นฐานจาก Java ซึ่งคุณสามารถประกาศฟิลด์และเมธอดสแตติกจำนวนเท่าใดก็ได้โดยไม่ต้องจัดกลุ่ม หากจำเป็นต้องมีกลุ่มตรรกะหลายกลุ่มของสมาชิกสแตติก ให้ใช้การประกาศ object ที่ซ้อนกันโดยไม่มี companion
ตามเอกสารประกอบ Kotlin อย่างเป็นทางการ (Kotlin Docs, 2025) ข้อจำกัดหนึ่ง companion object มีแรงจูงใจจากการออกแบบภาษา: สมาชิกสแตติกควรผูกพันกับคลาสอย่างแน่นหนา และออบเจกต์คู่หูหนึ่งตัวให้ขอบเขตที่ชัดเจนสำหรับการเชื่อมต่อนี้ หากมีสมาชิกสแตติกมากเกินไป นั่นเป็นสัญญาณให้ปรับโครงสร้างคลาส
ใน Kotlin อินเทอร์เฟซก็สามารถมี companion object ได้เช่นกัน ซึ่งอนุญาตให้กำหนดเมธอดสแตติกและค่าคงที่โดยตรงภายในอินเทอร์เฟซ ซึ่งเป็นสิ่งที่ไม่สามารถทำได้ใน Java แนวทางนี้มักใช้สำหรับการประกาศค่าคงที่ที่เกี่ยวข้องกับอินเทอร์เฟซหรือเมธอดโรงงาน
interface ApiService {
companion object {
const val BASE_URL = "https://api.example.com"
const val TIMEOUT_MS = 5000
fun create(client: OkHttpClient): ApiService =
Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.build()
.create(ApiService::class.java)
}
}
ในตัวอย่างนี้ BASE_URL, TIMEOUT_MS และ create() เป็นของอินเทอร์เฟซ ApiService ไม่ใช่การ implement ของมัน ซึ่งสะดวก: ค่าคงที่ทั้งหมดและตรรกะของโรงงานสำหรับสร้างอินสแตนซ์บริการ API อยู่ในที่เดียว — ภายในอินเทอร์เฟซเอง
การ implement อินเทอร์เฟซไม่สืบทอดสมาชิกของ companion object — พวกมันถูกเรียกผ่านชื่ออินเทอร์เฟซเท่านั้น: ApiService.BASE_URL
เมื่อเรียกเมธอดของ companion object จากโค้ด Java โดยค่าเริ่มต้นพวกมันสามารถเข้าถึงได้เป็นเมธอดของคลาส Companion ที่ซ้อนกัน ไม่ใช่เป็นเมธอดสแตติกของคลาสภายนอก เพื่อส่งออกพวกมันเป็นเมธอดและฟิลด์สแตติกจริงสำหรับ Java ให้ใช้คำอธิบายประกอบ @JvmStatic สำหรับเมธอดและ @JvmField สำหรับฟิลด์
| คำอธิบายประกอบ | การใช้งาน | ผลลัพธ์ใน Java |
|---|---|---|
| @JvmStatic | เมธอดของ companion object | เมธอดสแตติก: ClassName.method() |
| @JvmField | ฟิลด์ของ companion object | ฟิลด์สแตติก: ClassName.field |
| const | ชนิดดั้งเดิมและ String | ค่าคงที่แบบอินไลน์: คอมไพเลอร์แทนที่ค่า |
| ไม่มีคำอธิบายประกอบ | เมธอด/ฟิลด์โดยค่าเริ่มต้น | Companion.method() / Companion.field |
class MathUtils {
companion object {
const val PI = 3.14159
@JvmStatic
fun square(x: Int): Int = x * x
@JvmField
val TAG: String = "MathUtils"
}
}
// Called in Java as MathUtils.square(5), MathUtils.TAG
แนะนำให้ใช้ @JvmStatic สำหรับเมธอดสาธารณะทั้งหมดของ companion object ที่ควรเข้าถึงได้จากโค้ด Java const ใช้ได้กับชนิดดั้งเดิมและ String เท่านั้น — สำหรับชนิดอื่นให้ใช้ @JvmField
ในโปรเจกต์จริง companion object ถูกใช้สำหรับหลายสถานการณ์มาตรฐาน เมธอดโรงงานเป็นรูปแบบที่พบบ่อยที่สุด: แทนที่จะใช้คอนสตรักเตอร์หลายตัวที่มีลายเซ็นต่างกัน จะใช้เมธอดโรงงานที่มีชื่อใน companion object
sealed class NetworkResult<out T> {
data class Success<out T>(val data: T) : NetworkResult<T>()
data class Error(val message: String) : NetworkResult<Nothing>()
companion object {
fun loading<T>(): NetworkResult<T> =
Loading()
}
}
private class Loading<T> : NetworkResult<T>()
companion object ใน NetworkResult ให้เมธอดโรงงาน loading() ที่สร้างอินสแตนซ์ Loading ด้วยชนิดเจเนอริกที่ถูกต้อง หากไม่มีเมธอดนี้ คุณจะต้องสร้างอินสแตนซ์โดยตรงผ่าน Loading() ซึ่งเปิดเผยการ implement ภายใน
companion object มักใช้สำหรับเก็บค่าคงที่เฉพาะคลาส ค่าคงที่ที่ประกาศด้วย const val จะถูกแทรกในเวลาคอมไพล์ ทำให้ไม่มีค่าใช้จ่ายเพิ่มเติมในรันไทม์
class UserRepository {
companion object {
private const val TAG = "UserRepository"
private const val CACHE_SIZE = 100
private const val DEFAULT_PAGE_SIZE = 20
}
}
คำถามที่พบบ่อย
object ปกติเป็นซิงเกิลตันเต็มรูปแบบที่ดำรงอยู่อย่างอิสระจากคลาส companion object เป็น object ภายในคลาสซึ่งสมาชิกถูกเรียกผ่านชื่อคลาสภายนอก ในไบต์โค้ด companion object กลายเป็นคลาสซ้อนแบบสแตติก ในขณะที่ object ปกติกลายเป็นคลาสซิงเกิลตันอิสระ
ไม่ companion object ไม่ถูกสืบทอดโดยคลาสย่อย หากคลาส B สืบทอดคลาส A ดังนั้น B.method() จะไม่เรียกเมธอดจาก companion object ของคลาส A — คุณต้องเรียกผ่าน A.method() ซึ่งสอดคล้องกับพฤติกรรมของเมธอดสแตติกใน Java
โดยค่าเริ่มต้น — ผ่าน ClassName.Companion.method() เพื่อเรียกเป็นเมธอดสแตติก ให้เพิ่ม @JvmStatic ให้กับเมธอดของ companion object สำหรับฟิลด์ ให้ใช้ @JvmField หรือประกาศด้วย const val สำหรับชนิดดั้งเดิม
นักพัฒนา Kotlin ตัดสินใจละทิ้ง static เพื่อหันมาใช้ companion object สำหรับการทำงานที่เป็นหนึ่งเดียวกับออบเจกต์ static ใน Java ละเมิดหลักการ OOP เพราะเมธอดสแตติกไม่ผูกติดกับอินสแตนซ์ companion object เป็นออบเจกต์ระดับหนึ่งที่สามารถส่งต่อ ขยายด้วยฟังก์ชันส่วนขยาย และ implement อินเทอร์เฟซได้
เมื่อใช้ const val สำหรับชนิดดั้งเดิมและ String — ไม่มีค่าใช้จ่ายเพิ่มเติม ค่าจะถูกแทรกในไบต์โค้ด สำหรับเมธอด ไม่มีค่าใช้จ่ายเพิ่มเติมเว้นแต่เมธอดจะเป็น inline ในกรณีส่วนใหญ่ companion object ไม่สร้างผลกระทบที่วัดได้ต่อประสิทธิภาพ ใช้ @JvmStatic เฉพาะสำหรับ API สาธารณะที่เรียกจาก Java
สรุป
เราจะพัฒนาแอปพลิเคชันบนมือถือแบบครบวงจร
IT Sectr สร้างแอปพลิเคชัน iOS และ Android สำหรับสตาร์ทอัพและธุรกิจตั้งแต่ปี 2017 เราจะให้คำแนะนำและเสนอวิธีแก้ปัญหาที่ดีที่สุดแก่คุณ
อ่านเพิ่มเติม