29
Java Nested Classes and Lambda Expressions
Java has a very rich set of features that gives developers a lot of options to choose their implementation from, two of them, nested classes and lambda expressions.
I was reading a certain part of a code base and realized that I have not really fully understood the differences between the types of nested classes. The section below is heavily based from the Java Documentation for Nested Classes, so in-depth explanations can be checked here.
A nested class is a class within another class, i.e. a member of its enclosing class. It is divided into two categories: non-static and static.
private
, public
, protected
or package-private
.public
or package-private
.public class OuterClass {
private class InnerClass {
// class content here
}
}
public class OuterClass {
static class StaticNestedClass {
// class contents here
}
}
ShadowTest.this.x
. Go to shadowing section of this link.ShadowTest
from the method.
System.out.println("ShadowTest.this.x = " + ShadowTest.this.x);
private
, public
, package-private
protected
can be used in an inner class just like how they are used for the instance fields of the outer class.public class SomeClass {
public void someMethod() {
class SomeLocalClass {
// class contents here
}
}
}
SomeAnonyMousClass anonClass = new SomeAnonyMousClass(){
// instance field declarations
// methods
// should contain no constructor
};
HelloWorld helloWorld = new HelloWorld() {
// code here
};
new
operator, the name of an interface to implement or a class to extend, parentheses that contain the arguments to a constructor, just like a normal class instance creation expression and a body, which is a class declaration body.A lambda expression consists of the ff.
->
p -> p.getAge() >= 18
&& p.getAge() <= 25
A functional interface is any interface that contains only one abstract method. It may contain one or more default methods or static methods. Because it only contains one abstract method, the name can be omitted when implementing it. By doing this instead of using an anonymous class expression, a lambda expression is used. The JDK defines several standard functional interfaces which can be found in the package
java.util.function
.Lambda expression's parameter {} cannot redeclare another local variable defined in an enclosing scope
. This is because lambda expressions do not introduce a new level of scoping. Consequently, lambdas can directly access fields, methods and local variables of the enclosing scope. So how can the type of a lambda expression be determined, e.g. the type of p in the example below?
p -> p.getAge() < 18
When the Java runtime invokes the method where the lambda is passed, it is expecting a specific datatype, so the lambda expression is of this type.The data type that these methods expect is called the target type. To determine the type of a lambda expression, the Java compiler uses the target type of the context or situation in which the lambda expression was found. Thus, lambda expressions can only be used in situation in which the Java compiler can determine the target type, i.e. in :
For method arguments, the Java compiler determines the target type with two other language features overload resolution and type argument interface.
For instance, if the functional interfaces
java.lang.Runnable
and java.util.Callable<V>
are implemented and overloaded by a certain class like this,void invoke(Runnable r) {
r.run();
}
<T> T invoke(Callable<T> c) {
return c.call();
}
Which method will be invoked by the statement below?
String s = invoke(() -> "done");
The method with argument
Callable<V>
will be invoked because the lambda returns a value, in this case the string done
. Note that the method invoke(Runnable)
does not return a value.A lambda can be serialized if its target type and its captured arguments are serializable. However, like inner classes, π the serialization of lambdas are strongly discouraged.
Lambdas can be used to create anonymous methods. However, there are times when it does nothing but call an existing method. In these cases, it is often clearer to refer to the existing method by name, called method referencing. They are compact, easy-to-read lambdas for methods that already have a name.
For instance this can be done in sorting an array of Person objects by age.
For instance this can be done in sorting an array of Person objects by age.
Arrays.sort(personListAsArray, Person::compareByAge);
The method reference
Person::compareByAge
is semantically the same as the lambda expression where compareByAge
is a static method of the Person class.(person1, person2) -> Person.compareByAge(person1, person1)
There are four types of method referencing
Kind | Syntax | Example |
---|---|---|
Reference to a static method | ContainingClass::staticMethodName |
Person::compareByAge |
Reference to an instance method of a particular object | containingObject::instanceMethodName |
person1::compareByName |
Reference to an instance method of an arbitrary object of a particular type | ContainingType::methodName |
String::concat |
Reference to a constructor | ClassName::new |
HashSet::new |
Nested Classes enable the logical grouping of classes that are only used in one place, increase the use of encapsulation, create more readable and maintainable code. Local classes, anonymous classes and lambda expressions also share the same advantages but they are usually used for more specific situations:
Lambda Expressions .
- Used for encapsulating a single unit of behavior that is passed to the other parts of the code.
- Used if a simple instance of a functional interface is needed and some other criteria like constructor, named type, fields or additional methods are not needed
Nested Class . Used for reasons similar to those of local classes, i.e. it is necessary to make the type more widely available, and access to local variables or method parameters are not needed.
- Inner class should be used if access to an enclosing instance's non-public fields and methods are required.
- Static class should be used if there is no instance field that needs to be accessed from the class.
Getting used and familiar with nested classes and advanced lambdas with generics certainly takes a lot of reading code and practice. We will eventually get there.
As always, cheers to continued growth and learning π·!
29