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 typemyFace.Member
fred.contacts += barney // No—can't add amyFace.Member
to a buffer ofchatter.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.