Scala中内部对象与伴生对象的区别:两类定义写法差异咨询
Great question — let's break this down step by step, starting with the core differences between inner objects and companion objects, then dive into exactly how those two code snippets behave differently.
First: Core Definitions & Differences
Companion Objects
Companion objects are Scala's replacement for Java-style static members (since Scala doesn't have static). Here's what makes them unique:
- They share the exact same name as a class/trait, and live in the same top-level scope (same source file, not nested inside another class).
- The class and its companion object have mutual access to each other's private members — this is a key superpower you don't get with regular objects.
- They're typically used for factory methods (like
apply()to create instances withoutnew), global constants, utility methods, or to hold state shared across all instances of the class.
Example of a companion pair:
class User(private val id: Int) { // Can access private members of the companion object def printSharedId(): Unit = println(User.sharedDefaultId) } // Companion object (same name, same top-level scope) object User { private val sharedDefaultId = 0 // Can access private members of the class def getUserId(user: User): Int = user.id // Factory method def apply(name: String): User = new User(generateId()) private def generateId(): Int = scala.util.Random.nextInt(1000) }
Inner Objects
Inner objects are singleton objects nested inside another class, trait, or even another object. Think of them as a named, singleton namespace tied to the outer class:
- They're scoped to the outer class, so you access them via
OuterClass.InnerObject(no need to create an instance of the outer class first). - They can access private members of the outer class, but the reverse is not true — the outer class can't access the inner object's private members unless explicitly allowed.
- Use cases include encapsulating logic tightly coupled to the outer class but not per-instance, or avoiding global namespace clutter.
Example of an inner object:
class ShoppingCart { private val taxRate = 0.08 // Inner object object Calculator { // Can access outer class's private taxRate def calculateTotal(subtotal: Double): Double = subtotal * (1 + taxRate) private val discountThreshold = 100.0 } // Can't access Calculator.discountThreshold here — it's private to the inner object } // Usage: Call directly via the outer class println(ShoppingCart.Calculator.calculateTotal(50)) // Outputs 54.0
Now: Your Two Code Patterns
Pattern 1: class A { object A { } }
This defines a class A with a nested inner object also named A. Here's what you need to know:
- The inner
Ais scoped to the outer classA— you access it asA.A(no instance of the outerArequired). - No companion relationship exists here. The outer class
Acannot access private members of the inner objectA, even though the inner object can access the outer class's private members. - This pattern is useful if you want to encapsulate singleton logic only relevant to class
Awithout polluting the global namespace.
Example in action:
class A { private val outerSecret = "outer123" object A { def getOuterSecret(): String = outerSecret // Works — inner can access outer private private val innerSecret = "inner456" } // println(A.innerSecret) // Compile error — outer can't access inner private } // Usage println(A.A.getOuterSecret()) // Outputs "outer123" // println(A.A.innerSecret) // Compile error — private to inner object
Pattern 2: class A { } object A { }
This is the standard Scala companion object pattern. Here's how it differs from Pattern 1:
- The class
Aand objectAlive in the same top-level scope (not nested), so they're official companions. - Bidirectional private access: The class can access private members of the object, and the object can access private members of the class.
- This is the go-to pattern for factory methods, shared state, and any logic that would be
staticin Java.
Example in action:
class A { private val instanceSecret = "instance789" def printCompanionSecret(): Unit = println(A.companionSecret) // Works — class accesses companion private } object A { private val companionSecret = "companion012" def getInstanceSecret(a: A): String = a.instanceSecret // Works — companion accesses class private def apply(): A = new A() // Factory method } // Usage val a = A() // Uses companion's apply() to create instance println(A.getInstanceSecret(a)) // Outputs "instance789" a.printCompanionSecret() // Outputs "companion012"
Quick Cheat Sheet
| Feature | Inner Object (Pattern 1) | Companion Object (Pattern 2) |
|---|---|---|
| Scope | Nested inside outer class | Same top-level scope as class |
| Private Access | Only inner → outer | Bidirectional (inner ↔ outer) |
| Relationship | No companion link | Official companion pair |
| Primary Use Case | Encapsulate class-specific singleton logic | Factory methods, shared state, "static" replacements |
内容的提问来源于stack exchange,提问作者sashank malladi

