Overview

In this third part regarding Scala classes we review nested classes.

Scala Classes 3

In Scala, you can nest just about anything inside anything [1]. For example we can define functions inside other functions, and classes inside other classes. We discuss the latter feature here.

import scala.collection.mutable.ArrayBuffer
import scala.collection.mutable.ArrayBuffer
class Grid{
    
    class Element(val idx: Int){
        
        val children = new ArrayBuffer[Element]
    }
    
    private val elements = new ArrayBuffer[Element]
    
    def addElement() : Unit = {
        
        val element = new Element(this.elements.size)
        elements += element
    }
    
    def addElement(element: Element){
        elements += element
    }
    
    def nElements(): Int = this.elements.size
}
defined class Grid
val grid = new Grid
grid: Grid = ammonite.$sess.cmd10$Helper$Grid@662422e8
grid.addElement()
println(" Number of elements " + grid.nElements)
 Number of elements 1

In Scala, each instance has its own class Element , just like each instance has its own field members [1]. You can see this below

val grid_2 = new Grid
grid_2: Grid = ammonite.$sess.cmd10$Helper$Grid@6116d1b9
grid.addElement(new grid_2.Element(0))
cmd15.sc:1: type mismatch;
 found   : cmd15.this.cmd14.grid_2.Element
 required: cmd15.this.cmd11.grid.Element
val res15 = grid.addElement(new grid_2.Element(0))
                            ^Compilation Failed
Compilation Failed

Assuming that grid holds quad elemennts and grid_2 holds triangular elements this behaviour makes sense. However, we may want to remove this behaviour. We can do this in two ways [1]

  • Use a companion object
  • Use type projection
object Grid{
    
     class Element(val idx: Int){
        
        val children = new ArrayBuffer[Element]
    }
    
}
defined object Grid
class Grid{
    
    private val elements = new ArrayBuffer[Grid.Element]
    
    def addElement() : Unit = {
        
        val element = new Grid.Element(this.elements.size)
        elements += element
    }
    
    def addElement(element: Grid.Element){
        elements += element
    }
    
    def nElements(): Int = this.elements.size
}
defined class Grid

With type projection, we write our class as shown below

class Grid{
    
    class Element(val idx: Int){
        
        val children = new ArrayBuffer[Element]
    }
    
    private val elements = new ArrayBuffer[Element]
    
    def addElement() : Unit = {
        
        val element = new Element(this.elements.size)
        elements += element
    }
    
    def addElement(element: Element){
        elements += element
    }
    
    def nElements(): Int = this.elements.size
}
defined class Grid

Type projection means, for our case, Element from any Grid

val grid = new Grid
val grid2 = new Grid
grid: Grid = ammonite.$sess.cmd19$Helper$Grid@3bc5f2f5
grid2: Grid = ammonite.$sess.cmd19$Helper$Grid@5f5eafec
grid.addElement(new grid_2.Element(0))
cmd21.sc:1: type mismatch;
 found   : cmd21.this.cmd14.grid_2.Element
 required: cmd21.this.cmd20.grid.Element
val res21 = grid.addElement(new grid_2.Element(0))
                            ^Compilation Failed
Compilation Failed

Finally, in a nested class, we can access the this reference of the enclosing class as EnclosingClass.this [1]. This is similar to Java. Furthermore, we can establish an alias for that reference as shown below

class Grid{ outer =>
    
    class Element(val idx: Int){
        
        val children = new ArrayBuffer[Element]
    }
    
    private val elements = new ArrayBuffer[Element]
    
    def addElement() : Unit = {
        
        val element = new Element(this.elements.size)
        elements += element
    }
    
    def addElement(element: Element){
        elements += element
    }
    
    def nElements(): Int = this.elements.size
}
defined class Grid

The class Grid{ outer => syntax makes the variable outer refer to Grid.this. Note that we can choose any name for this variable. The name self is common, but perhaps confusing when used with nested classes [1].

References

  1. Cay Horstmann, Scala for the Impatient 1st Edition