Nested Classes
Java supports nested classes:
class Foo {
class Bar {
// TODO good stuff
}
Bar bellyUp() {
return new Bar();
}
// TODO even more good stuff
}
Foo.Bar bar = new Foo().bellyUp();
Similarly, Kotlin supports nested classes. But, as with many things in Kotlin, there are some differences between Java’s approach and Kotlin’s.
Simple Nested Classes
You can have a class
inside of a class
easily enough in Kotlin:
class Foo {
class Bar {
fun whatever() {
println("You were expecting something profound?")
}
// TODO add good stuff
}
// TODO add even more good stuff
}
fun main() {
val bar = Foo.Bar()
bar.whatever()
}
However, this is not quite the same as the Java example above. The default behavior in Kotlin is more akin to Java’s static
class:
class Foo {
static class Bar {
// TODO good stuff
}
// TODO even more good stuff
}
Foo.Bar bar = new Foo.Bar();
An instance of Bar
is not tied to any instance of Foo
. In many respects, the nested class is only connected to its outer class by name — you might just as easily have:
class Bar {
// TODO good stuff
}
class Foo {
// TODO even more good stuff
}
val bar = Bar()
The biggest value of a nested class, over a completely independent class, is that a nested class has access to private
values that are in an eligible scope. That would include values held by an object
inside of the outer class:
class Foo {
private object Data {
const val VALUE = 1
}
class Bar {
fun value() = Foo.Data.VALUE
// TODO add good stuff
}
// TODO add even more good stuff
}
fun main() {
val bar = Foo.Bar()
println(bar.value())
}
Here, Bar
can access Foo.Data
, even though that object is private
, since Bar
is nested inside of Foo
. Classes and objects outside of Foo
have no direct access to Data
.
Inner Classes
Let’s go back to the original Java snippet:
class Foo {
class Bar {
// TODO good stuff
}
Bar bellyUp() {
return new Bar();
}
// TODO even more good stuff
}
Foo.Bar bar = new Foo().bellyUp();
In Java, by default — as in the case with Foo
and Bar
above — the nested class is an “inner” class. An instance of Bar
has access to everything inside of an enclosing instance of Foo
.
class Foo {
int count = 1;
class Bar {
int getCount() {
return count;
}
}
}
Foo.Bar bar = new Foo().bellyUp();
int theCount = bar.getCount();
Our instance of Bar
has the ability to “reach into” an enclosing instance of Foo
and access its members, including fields and methods. In this case, Bar
accesses the count
field of Foo
.
The equivalent in Kotlin requires you to use the inner
keyword when declaring the inner class:
class Foo {
val count = 1
inner class Bar {
fun count() = count
}
}
fun main() {
val foo = Foo()
val bar = foo.Bar()
println(bar.count())
}
An instance of Bar
can reference the count
inside of the enclosing instance of Foo
.
In both Java and Kotlin, only an instance of the outer class can create an instance of the inner class. In our case, we need an instance of Foo
to be able to ask it to create for us an instance of Bar
. In the case of Kotlin, because creating new objects does not require a keyword, we can just invoke the Bar()
constructor on an instance of Foo
. In Java, typically we use some method on the outer class to create instances of the inner class for us (e.g., bellyUp()
).
Also, to make this work, the instance of the inner class has an implicit reference to the instance of the outer class. We do not have an explicit field for it, but it is there. This causes garbage collection problems, as developers sometimes forget that a Bar
has a hidden reference to a Foo
, and therefore the Foo
cannot be garbage-collected while we are holding onto a Bar
.
Which this
is this
?
Inner classes raise a thorny problem: when we use this
, what instance is it referring to?
By default, this
refers to the instance of the inner class, so this code prints class Bar
or class Foo$Bar
, depending on what Kotlin platform you run on:
class Foo {
inner class Bar {
fun that() = this
}
}
fun main() {
val foo = Foo()
val bar = foo.Bar()
println(bar.that()::class)
}
($
notation indicates an inner class relationship, and Foo$Bar
means “the Bar
class that is an inner class of Foo
”, but this will only be seen in Kotlin/JVM, not the Kotlin/JS that Klassbook uses)
In other words, this
by default refers to the instance of the inner class (Bar
, in this case).
Occasionally, though, we need to specifically get a reference to the instance of the outer class. Android Java developers run into this frequently:
- In some
Activity
, you define an anonymous inner class to use for a callback, such as aView.OnClickListener
- In a method in that anonymous inner class, you need a reference to the
Activity
- You try using
this
, and you get compile errors, saying thatthis
is not anActivity
(orContext
or other superclass)
Java has syntax to reaching the outer class instance:
-
this
refers to the inner class instance -
OuterClass.this
refers to the outer class instance that created the current inner class instance that a plainthis
refers to
Kotlin has the same concept, with different syntax:
-
this
refers to the inner class instance -
this@OuterClass
refers to the outer class instance that created the current inner class instance that a plainthis
refers to
class Foo {
val count = 1
inner class Bar {
fun count() = count
fun that() = this
fun theOuterThis() = this@Foo
}
}
fun main() {
val foo = Foo()
val bar = foo.Bar()
println(bar.that()::class)
println(bar.theOuterThis()::class)
}
This snippet prints something like:
class Bar
class Foo
this@Foo
returns a Foo
, specifically the instance of Foo
that created the Bar
instance on which we called theOuterThis()
.
Prev Table of Contents Next
This book is licensed under the Creative Commons Attribution-ShareAlike 4.0 International license.