Inner Classes in Scala and Java

When I used the Scala combinator library in earnest a few months ago, I got confusing error messages when I used classes that are nested inside other classes. There were several blog posts that made the feature look complicated, so I reserved a chapter of my upcoming ”Scala for the Impatient” to explain class nesting. It turns out that won't be necessary. It's simpler in Scala than in Java, and I can explain it in a few paragraphs.

In Scala, you can nest just about anything inside anything. You can define functions inside other functions, and classes inside other classes. Here is a simple example of the latter. (I follow this explanation in a hopefully more intuitive context.)

import collection.mutable._
class Network {
  class Member(val name: String) {
    val contacts = new ArrayBuffer[Member]
  }

  private val members = new ArrayBuffer[Member]

  def join(name: String) = {
    val m = new Member(name)
    members += m
    m
  }
}

Of course, you can do the same in Java:

import java.util.*;
public class Network {
  public class Member {
    private String name;
    private ArrayList<Member> contacts = new ArrayList<>();
    public Member(String name) { this.name = name; }
    public String getName() { return name; }
    public ArrayList<Member> getContacts() { return contacts; }
  }

  private ArrayList<Member> members = new ArrayList<>();

  public Member join(String name) {
    Member m = new Member(name);
    members.add(m);
    return m;
  }
}

But there is a difference. In Scala, each instance has its own class Member, just like each instance has its own field members. Consider two networks.

val chatter = new Network
val myFace = new Network

Now chatter.Member and myFace.Member are different classes. In contrast, in Java, there is only one inner class Network.Chatter.

The Scala approach is more regular. For example, to make a new inner object, you simply use new with the type name:

val fred = new chatter.Member("Fred")

In Java, you need to use a special syntax.

Member fred = chatter.new Member("Fred");

And in Scala, the compiler can do useful type checking. In our network example, you can add a member within its own network, but not across networks.

val fred = chatter.join("Fred")
val wilma = chatter.join("Wilma")
fred.contacts += wilma // Ok
val barney = myFace.join("Barney") // Has type myFace.Member 
fred.contacts += barney 
  // No—can't add a myFace.Member to a buffer of chatter.Member elements

For networks of people, this behavior probably makes sense. If you don't want it, there are two solutions.

First, you can move the Member type somewhere else. A good place would be the Network companion object.

object Network {
  class Member(val name: String) {
    val contacts = new ArrayBuffer[Member]
  }  
}

class Network {
  private val members = new ArrayBuffer[Network.Member]
  ...
}

Companion objects are used throughout Scala for class-based features, so this is no surprise.

Alternatively, you can use a type projection Network#Member, which means “a Member of any Network”. For example,

class Network {
  class Member(val name: String) {
    val contacts = new ArrayBuffer[Network#Member]
  }
  ...
}

You would do that if you want the fine-grained “inner class per object” feature in some places of your program, but not everywhere.

So, which language is more complex, Scala or Java? Except possibly for the Network#Member syntax, I think Scala wins hands-down. It is more regular, and it offers more functionality at the same time. It does that with a handful of basic principles, systematically applied. (Before you flame me, consider that ”less familiar” is not the same as “more complex”.)

In contrast, with Java, you can see that inner classes were bolted onto an existing language. Did I mention that Java has restrictions on accessing local outer variables in local inner classes? And “static” inner classes? Don't get me going. It's half a chapter in Core Java. Scala doesn't have any of that.