If you would like to create a superhero, what would you do? Of course, you would open your favorite development environment and create a Superhero class!
As appropriate, our superhero will need a set of special items, including, for example, a magic cloak or a hammer. Here may come a problem. What is the best way to organize the classes describing the equipment? And how can you show that only the Superhero class can use them?
Here appears our savior — an instrument called nested classes. They help us group classes logically and increase the encapsulation of our code.
What is a nested class?
You can create a class within another class and such classes are called nested.
Take a look at our superhero:
class Superhero {
class MagicCloak {
}
class Hammer {
}
}
Both classes MagicCloak and Hammer are nested classes. The Superhero class is often called an outer class, while a nested class, along with its properties, functions, and constructors, is a member of an outer class.
The problem here is that MagicCloak and Hammer aren't really bound to Superhero. It's just classes:
class Superhero {
val power = 1000
class MagicCloak {
// you cannot access something from Superhero here
val magicPower = 100
}
// you need to create a MagicCloak object to access its members
val magicPower = power * MagicCloak().magicPower
class Hammer {
// you cannot access power property from Superhero here
val mightPower = 100
}
val mightPower = power * Hammer().mightPower
}
If you need to use MagicCloak and Hammer outside the class Superhero, you must create the corresponding objects:
val cloak = Superhero.MagicCloak()
val hammer = Superhero.Hammer()
As you can see, a simple nested class is not really connected with the outer class. In this topic, we are going to focus on a special case of nested classes — inner classes.
Inner class
A regular nested class cannot access members of its outer class. But a nested class marked as an inner class can.
Let's move to another example. Imagine that you are writing a class Cat representing cats. Cats may have a lot of properties and functions, but we may also use inner class structures. Let's say you want a cat to have a ribbon bow; then you need to create a new class Bow. This class Bow needs to be quite small and specific, and you know you won't need a bow without a cat. The solution is to create a class Bow inside the class Cat:
class Cat(val name: String) {
inner class Bow(val color: String) {
fun printColor() {
println("The cat named $name has a $color bow.")
}
}
}
Let's create a cat named Bob with a red bow:
fun main() {
val cat: Cat = Cat("Bob")
val bow: Cat.Bow = cat.Bow("red")
bow.printColor()
}
We have created an instance of Cat and then created an instance of Bow using quite an interesting syntax.
The output for the code above will be:
The cat named Bob has a red bow.
Remember that to use inner classes, we must create an instance of the outer class. In our example, we created a Cat. And you can use the inner class freely in the outer class:
class Cat(val name: String) {
inner class Bow(val color: String) {
fun printColor() {
println("The cat named $name has a $color bow.")
}
}
val catBow = Bow("Green")
}Scope of the inner class
Now let's discuss what we can see from the inner class and who can access the inner class from outside.
Here is our class Cat with a new function sayMeow and an inner class Bow with a new function putOnABow.
class Cat(val name: String) {
fun sayMeow() {
println("$name says: \"Meow\".")
}
inner class Bow(val color: String) {
fun putOnABow() {
sayMeow()
println("The bow is on!")
}
fun printColor() {
println("The cat named $name has a $color bow.")
}
}
}
You can see that inside the Bow class, we have access to all members of the class Cat: the name property and the sayMeow function.
How about creating a cat named Princess with a golden bow to prove that our code works?
fun main() {
val cat: Cat = Cat("Princess")
val bow: Cat.Bow = cat.Bow("golden")
bow.printColor()
bow.putOnABow()
}
And, yes, the bow is on!
The cat named Princess has a golden bow.
Princess says: "Meow".
The bow is on!
You may encounter inner classes that have members with the same names as their outer classes. For example, both Cat and Bow may have the property color. How can you access the outer class members from the inner class in such cases? The qualified this expression will help! Write [email protected] in the inner class code and you will get the color of the outer class, while using color or this.color will always give you the color property of the current class:
class Cat(val name: String, val color: String) {
inner class Bow(val color: String) {
fun printColor() {
println("The cat named $name is ${[email protected]} and has a $color bow.")
}
}
}
Now let's put all the rules together!
Rules for Inner classes
An inner class can access all members of its outer class.
To access the inner class, you need to instantiate the outer class first, as inner classes are associated with an instance of their enclosing class. In the example below, the constructor of the inner class Inner is called with an instance of the containing class:
val outer = Outer()
val inner = outer.Inner()Reasons to use Inner Classes
Have you noticed what our Superhero and Cat examples have in common? Sure you have — we hid our inner classes from the outside world. This increases the encapsulation of the code — now only Cat may put on a bow.
Using the inner classes structure, you can also organize your code more conveniently. With all the magic equipment for Superhero being held in one place, it is easier to navigate between classes and to understand the structure of your code.
Conclusion
A class declared inside another class is called nested.
An inner class is a special case of a nested class that can access the members of its outer class.
Inner classes carry a reference to an object of an outer class, so to use inner classes, we must instantiate an outer class first.
The main idea of inner classes is to hide some code from other classes and increase encapsulation.