It is a little puzzling why you thought of the idea of multiple inheritance after reading the most compelling reason you have quoted from the book. Multiple inheritance comes into question when a class (inner or not) wants to inherit behavior from more than one concrete implementation. Thus, unlike some other languages, in Java, you can not define a class like:
class Child extends Father, Mother {
// Child wants to inherit some behavior from Father and some from Mother
}
As you can see, nothing that only inner classes do can rectify or work around this Java decision (not to support multiple inheritance) in a straightforward way.
Then why do they exist, you may wonder! Well, in Java every class is either top-level or inner (also called nested). Any class that is defined inside another class is an inner class and any class that isn't so is a top-level class.
Naturally, one might wonder why to define classes (i.e. behavior) inside other classes. Aren't top-level classes enough?
The answer is yes. Java could always have only top-level classes. But the idea (perhaps) was there was no good reason to restrict classes from being members of other classes! Just like any predefined type (e.g. Integer
, String
etc.) can be a member of a class:
class Person {
private String name; // a field the models a Person's name
}
a programmer should be able to define a behavior of one's interest inside the class:
class Person {
private String name; // a field the models a Person's name
private Address address; // address is a type defined here
static class Address {
String street;
String city;
}
}
There's a lot going on here, especially with these things like private
, static
etc. which are called the modifiers. There are many technical details about them, but let us come back to them later. The essential idea is to be able to define behavior as a part of another class. Could the Address
class be defined outside Person
class, as a top-level class? Of course. But having this facility comes in handy.
Now, since this facility was introduced, it started serving another purpose and that purpose is called providing code as data. This is how design patterns emerge and it was thought until about 10 years ago that inner classes can be used to provide the data in the form of code. Perhaps this is somewhat puzzling to you. Consider the following code that I have taken almost verbatim from the JDK class: java.lang.String.java:
public static final Comparator<String> CASE_INSENSITIVE_ORDER
= new CaseInsensitiveComparator();
private static class CaseInsensitiveComparator
implements Comparator<String> {
public int compare(String s1, String s2) {
int n1 = s1.length();
int n2 = s2.length();
// details excluded for brevity
// return -1, 0, 1 appropriately
}
}
What has happened here?
We need a way to compare a String to another String and we need to be able to do a case-insensitive comparison. So, we created an implementation of the Comparator
interface right inside the outer class: String
! Isn't this handy? If inner class wouldn't be there, this would have to be:
public class String {
// ... the whole String class implementation
}
class CaseInsensitiveComparator
implements Comparator<String> {
// implements the comparator method
}
and that's not 'bad' per se, but it means a lot of classes polluting the name space. Inner classes restrict the scope of a behavior to the outer class. That comes in handy, as you'd perhaps see. The data in this case is the implementation of the Comparator
interface and the code is well, the same, because we are _new_ing up the inner class we defined.
This feature was exploited further using the anonymous inner classes (especially in the cases where you wanted the code to serve as data) up until Java 7 and they were effectively replaced by Lambda Expressions in Java 8. Nowadays, you might not see any new code that uses anonymous inner classes (in other words, language evolves).