Scala Tutorial
Basics
Control Statements
OOP Concepts
Parameterized - Type
Exceptions
Scala Annotation
Methods
String
Scala Packages
Scala Trait
Collections
Scala Options
Miscellaneous Topics
In Scala, just like in Java, a class can have another class defined inside it. Such a class is called an inner class. However, there's an interesting difference in the way Scala handles inner classes compared to Java. In Scala, each instance of the outer class will have its own unique type for its inner classes.
Here's a guide to inner classes in Scala:
You can define an inner class by simply placing one class inside another:
class Outer { class Inner { def show(): Unit = println("Inside Inner class") } }
To instantiate an inner class, you must have an instance of the outer class:
val outer = new Outer val inner = new outer.Inner inner.show() // Prints: Inside Inner class
Unlike Java, in Scala, inner classes are bound to the instance of the outer class. This means that for different instances of the outer class, the type of the inner class is also different:
val outer1 = new Outer val outer2 = new Outer val inner1: outer1.Inner = new outer1.Inner val inner2: outer2.Inner = new outer2.Inner // The following would result in a compilation error // val wrongInner: outer1.Inner = new outer2.Inner
The inner class has access to the members of the outer class:
class Outer { private val message = "Hello from Outer class" class Inner { def showMessage(): Unit = println(message) } } val outer = new Outer val inner = new outer.Inner inner.showMessage() // Prints: Hello from Outer class
Given Scala's unique treatment of inner classes, the type of the inner class is dependent on the instance of the outer class. This leads to what's called "path-dependent types." In the earlier example, outer1.Inner
and outer2.Inner
are distinct types, each dependent on the specific instance of Outer
.
Inner classes in Scala can be useful for organizing code and encapsulating logic. However, keep in mind Scala's unique treatment of inner classes with path-dependent types. It offers powerful modeling capabilities but can also introduce complexity if not used judiciously.
Defining and using inner classes in Scala:
class Outer { class Inner { def greeting: String = "Hello from Inner!" } } val outerInstance = new Outer val innerInstance = new outerInstance.Inner println(innerInstance.greeting) // Output: Hello from Inner!
Accessing outer class members from inner class in Scala:
class Outer { private val outerField = "Outer Field" class Inner { def accessOuterField(): String = outerField } } val outerInstance = new Outer val innerInstance = new outerInstance.Inner println(innerInstance.accessOuterField()) // Output: Outer Field
Anonymous inner classes in Scala:
trait Greet { def greeting(): String } val anonymousInstance = new Greet { def greeting(): String = "Hello from anonymous class!" } println(anonymousInstance.greeting()) // Output: Hello from anonymous class!
Visibility and scoping rules for inner classes in Scala:
class Outer { private val outerPrivate = "Outer Private" class Inner { private val innerPrivate = "Inner Private" def accessOuterPrivate(): String = outerPrivate } def accessInnerPrivate(): String = { val innerInstance = new Inner innerInstance.innerPrivate } }
Anonymous inner classes and traits in Scala:
trait Logger { def log(message: String): Unit } val anonymousLogger = new Logger { def log(message: String): Unit = println(s"Log: $message") } anonymousLogger.log("This is an anonymous log entry")
Using inner classes for encapsulation in Scala:
class Outer { private val outerField = "Outer Field" class Inner { def accessOuterField(): String = outerField } } val outerInstance = new Outer val innerInstance = new outerInstance.Inner println(innerInstance.accessOuterField()) // Accessing private field
Inner classes and type projection in Scala:
class Container { type T class Inner(value: T) { def display(): String = s"Value: $value" } } val containerInstance = new Container val innerInstance: containerInstance.Inner = new containerInstance.Inner("Scala") println(innerInstance.display()) // Output: Value: Scala
Pattern matching with inner classes in Scala:
class Shape { def area(): Double = 0.0 } class Circle(radius: Double) extends Shape { override def area(): Double = math.Pi * radius * radius } val shape: Shape = new Circle(5.0) shape match { case c: Circle => println(s"Circle area: ${c.area()}") case _ => println("Unknown shape") }
Inner classes and inheritance in Scala:
class Animal { def sound(): String = "Some generic sound" } class Outer { class Inner extends Animal { override def sound(): String = "Custom sound from Inner" } } val outerInstance = new Outer val innerInstance = new outerInstance.Inner println(innerInstance.sound()) // Output: Custom sound from Inner