Skip to main content

Inner Classes in Scala and Java

Posted by cayhorstmann on August 5, 2011 at 4:19 PM PDT

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 [prettify]myFace.Member
fred.contacts += barney // No—can't add a myFace.Member to a buffer of chatter.Member elements[/prettify]

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.

 

Related Topics >>

Comments

This is fun, because it demonstrates how one can make clear ...

This is fun, because it demonstrates how one can make clear examples, formally perfect, and still people see them in opposite ways :-)

Maybe Scala grammar is more regular and, right, Java added inner classes in a second moment with some quirks. But in the end, what I see is that to do the same thing of Java, with Scala I have to learn two concepts: regular classes and another one picked from "companion objects" or "type projections". Well, indeed I have to learn all of the three concepts, because I suspect there will be cases in which a companion object is better and others in which a type projection is better. So, Java wins hands down in simplicity for me. Also because the thing I gain from the extra stuff is the incompatibility from the networks of different people, something that I can't imagine the utility of. Even when somebody shows me a few cases where it would be useful, I have to pay the extra knowledge payload for some corner case.

Note that I've been careful in using the term "complexity", but I talked about the need of "learning more things". Indeed, I could have referred that as complexity, just pointing out that there's a complexity of the language, and a complexity of people learning it. Is it this because of Java (and other similar languages) being more familiar? Perhaps, but this is a Scala problem, not a Java problem. In the end, I think there will be always two groups of people, the former preferring a more elegant end regular conceptual scheme such as Scala and the latter preferring the tweaked one, but easier to learn, such as Java.

You make a convincing argument that Java is easier to ...

You make a convincing argument that Java is easier to understand than Scala if you already know Java and don't yet know Scala :-)