Chapter 6 Interfaces, Lambda Expressions, and Inner Classes
6.1.1 The Interface Concept
An interface is a set of requirements for the classes that wan to conform to the interface.
All methods of an interface are automatically public.
Interfaces never have instance fields.
Use implement keyword to declare a class implements an interface.
Caution:
When implementing the interface, you must declare the method as public.
6.1.2 Properties of Interfaces
You can declare interface variables.
An interface variable must refer to an object of a class that implements the interface.
You can use instanceOf to check whether an object implements an interface:
if (anObject instanceOf Comparable) {…}
You can extend interfaces. This allows for multiple chains of interfaces that go form a greater degree of generality to a greater degree of specification.
You cannot put static methods in interfaces, however you can supply constants in them which are always public static final.
Use commas(,) to separate the interfaces that you want to implement.
6.1.3 Interfaces and Abstract Classes
A class can only extend a single class.
6.1.4 Static Methods
As of Java SE8, you can add static methods to interfaces.
6.1.5 Default Methods
You can supply a default implementation for any interface method. You must tag such a method with the default method modifier.
As of Java SE8, you can declare all of the methods as default methods that do nothing.
A default method can call other methods.
An important use for default methods is interface evolution.
Adding a method to an interface is binary compatible.
/*
binary compatible: a property of computer systems meaning that they can run the same executable code, typically machine code for a general-purpose computer CPU. (without recompilation)
source compatible: recompilation or interpretation is necessary.
*/
6.2 Examples of Interfaces
6.2.1 Interfaces and Callbacks
In the callback pattern, you specify the action that should occur whenever a particular event happens.
Passing an object is more flexible than passing a function because the object can carry additional information.
6.2.2 Comparator Interface
String.compareTo method compares strings in dictionary order.
To compare strings by length, define a class that implements Comarator<String>.
The compare method is called on the comparator (an instance of a class that implements the Comparator interface) object, not the string itself.
6.2.3 Object Cloning
A change to either variable also affects the other if the original and the copy are references to the same object.
The clone method is a protected method of Object, which means that your code cannot simply call it. A subclass can call a protected clone method only to clone its own objects. You must redefine clone method to be public to allow objects to be cloned by any method.
The default cloning operation is “shallow”—it does not clone objects that are referenced inside other objects. If the subobject shared between the original and the shallow clone is immutable, then the sharing is safe.
However, quite frequently, the subobjects are mutable, and you must redefine the clone method to make a deep copy that clones the subobjects as well.
For every class, you need to decide whether
- The default clone method is good enough;
- The default clone method can be patched up by calling clone on the mutable subobjects; and
- clone should not be attempted
To choose either the first or the second option, a class must
- Implement the Cloneable interface; and
- Redefine the clone method with the public access modifier
The Cloneable interface merely serves as a tag, indicating that the class designer understands the cloning process.
You need to implement the Cloneable interface, redefine clone to be public, and call super.clone().
To make a deep copy, you have to clone the mutable instance field.
Example:
class Sample implements Cloneable
{
...
public Sample clone() throws CloneNotSupportedException
{
// Call Object.clone()
Sample cloned = (Sample) super.clone();
//Clone mutable fields
cloned.hireDay = (Date) hireDay.clone();
return cloned;
}
}
6.3 Lambda Expressions
6.3.1 Why Lambdas?
A lambda expression is a block of code that you can pass around so that it can be executed later, once or multiple times.
6.3.2 The Syntax of Lambda Expressions
One form of lambda expressions in Java: parameters, the -> arrow, and an expression, enclosed in {} and with explicit return statements:
(DataType firstParam, DataType secondParam) ->
{
… return …;
…
}
If a lambda expression has no parameters, you still supply empty parentheses, just as with a parameterless method:
() -> { … ;}
If the parameter types of a lambda expression can be inferred, you can omit them.
// example
Comparator <String> comp = (first, second) // same as (String first, String second)
-> first.length() – second.length();
If a method has a single parameter with inferred type, you can even omit the parentheses:
ActionListener listener = event -> …; // instead of (event) -> … or (ActionListener event) -> …
You never specify the result type of a lambda expression. It is always inferred from the context.
It is illegal for a lambda expression to return a value in some branches but not in others.
6.3.3 Functional Interfaces
You can supply a lambda expression whenever an object of an interface with a single abstract method is expected.
A functional interface must have a single abstract method.
It is best to think of a lambda expression as function, not an object, and to accept that it can be passed to a functional interface.
In fact, conversion to a functional interface is the only thing that you can do with a lambda expression in Java.
You cannot assign a lambda expression to a variable of type Object–Object is not a functional interface.
Predicate (in the java.util.function):
public interface Predicate<T>
{
boolean test(T t);
// Additional default and static methods
}
The ArrayList class has a removeIf method whose parameter is a Predicate. It’s specifically designed to pass a lambda expression.
// example
List.removeIf(e -> e == null) // removes all null values from an array list
6.3.4 Method References
The :: operator separates the method name from the name of an object or class. There are 3 principle cases:
object::instanceMethod
Class::staticMethod
Class::instanceMethod
In the first 2 cases, the method reference is equivalent to a lambda expression that supplies parameters of the method.
In the third case, the first parameter becomes the target of the method.
You can capture the this , super parameter in a method reference.
Example
this::function is the same as x -> this.function(x)
The method expression
super::instanceMethod
uses this as the target and invokes the superclass version of the given method.
6.3.5 Constructor Reference
ClassName::new is a reference to a ClassName constructor.
You can form a constructor references with array types. Array constructor references overcome a limitation that you cannot construct an array of a generic type T. The expression new T[] is an error since it would be erased to new Object[n]. The stream library solves that problem with constructor references.
Pass ClassName ::new to the toArray method:
ClassName objectVar = stream.toArray(ClassName[] ::new);
The toArray method invokes this constructor to obtain an array of the correct type. Then it fills and returns the array.
6.3.6 Variable Scope
A lambda expression has three ingredients:
- A block of code
- Parameters
- Values for the free variables, that is, the variables that are not parameters and not defined inside the code.
Closure: a block of code together with the values of the free variables.
A lambda expression can capture the value of a variable in the enclosing scope.
In a lambda expression, you can only reference variables whose value doesn’t change.
Any captured variable in a lambda expression must be effectively final. An effectively final variable is a variable that is never assigned a new value after it has been initialized.
The body of a lambda expression has the same scope as a nested block. It’s illegal to declare a parameter or a local variable in the lambda that has the same name as a local variable.
When you use this keyword in a lambda expression, you refer to the this parameter of the method that creates the lambda.
6.3.7 Processing Lambda Expressions
The point of using lambda expression is deferred execution.
If you design your own interface with a single abstract method, you can tag it with the @FunctionalInterface annotation.
The Comparator interface has a number of convenient static methods for creating comparators. These methods are intended to be used with lambda expressions or method references.
Examples:
// sort by name
Arrays.sort(people, Comparator.comparing(Person::getName));
// sort by length
Arrays.sort(people, Comparator.comparing(Person::getName),(s, t) -> Integer.compare(s.length(), t.length()));
6.4 Inner Classes
An inner class is a class that is defined inside another class.
Inner class method can access the data from the scope in which they are defined—including the data that would otherwise be private.
Inner classes can be hidden from other classes in the same package.
Anonymous inner classes are handy when you want to define callbacks without writing a lot of code.
/*
A callback is any executable code that is passed as an argument to other code, which is expected to call back (execute) the argument at a given time.
*/
6.4.1 Use of an Inner Class to Access Object State
An inner class method gets to access both its own data fields and those of the outer object creating it. An object of an inner class always gets an implicit reference to the object that created it. The outer class reference is set in the constructor. The compiler modifies all inner class constructors, adding a parameter for the outer class reference.
Only inner class can be private. Regular classes always have either package or public visibility.
Any static fields declared in an inner class must be final.
6.4.4 Local inner Classes
If you need the name of the type only once when you create an object of that type, you can define the class locally in a single method.
Local classes are never declared with an access specifier (public or private). Their scope is always restricted to the block in which they are declared.
One great advantage—local classes are completely hidden from the outside world.
6.4.5 Accessing Variables from Outer Methods
Local classes can access local variables which are effectively final.
6.4.6 Anonymous Inner Class
If you want to make a single object of the class, you don’t need to give the class a name. Such a class is called an anonymous inner class.
In general, the syntax is
new SuperType(construction parameters) { inner class methods with data }
6.4.6 Anonymous Inner Class
If you want to make a single object of the class, you don’t need to give the class a name. Such a class is called an anonymous inner class.
In general, the syntax is
Here SuperType can be an interface; then the inner class implements that interface. SuperType can also be a class; then, the inner class extends that class.
An anonymous inner class cannot have constructors. Instead, the construction parameters are given to the superclass constructor. Particularly, whenever an inner class implements an interface, it cannot have any construction parameter.
Syntax:
new InterfaceType() { methods of data; }
Double Brace Initialization:
suppose you want to
construct an array list and pass it to a method, then you don’t need it again
and make it anonymous, how to add elements?
sampleMethod(new ArrayList<String>() {{add(“Sample-1”); add(“Sample-2”);}});
The outer {} makes an anonymous subclass of ArrayList. The inner {} is an object construction block.
new Object(){}
makes an anonymous object of an anonymous subclass of Object.
6.4.7 Static Inner Class
By declaring the inner class static, you can suppress the generation of the reference to the outer class object.
Only inner classes can be declared static.
Use a static inner class whenever the inner class does not need to access an outer class object.
Static inner classes can have static fields and methods.
Inner classes that are declared inside an interface are automatically static and public.
6.5 Proxies
6.5.1 When to Use Proxies
Suppose you want to construct an object of a class that implements one or more interface whose exact nature you may not know at compile time. You need to define a new class in a running program.
The proxy class has the following methods:
- All the methods required by the specified interfaces; and
- All methods defined in the Object class (toString, equals, and so on)
You cannot define new code for these methods at runtime unless you supply an invocation handler.
An invocation handler is an object of any class that implements the InvovationHandler interface. The interface has a single method:
Object invoke(Object proxy, Method method, Object[] args)
Whenever a method is called on the proxy object, the invoke method of the invocation handler gets called, with the Method object and parameters of the original call. The invocation handler must then figure out how to handle the call.
6.5.2 Creating Proxy Objects
To create a proxy object, use the newProxyInstance method of the Proxy class. The method has three parameters:
-
-
- A class loader, for now, we specify null to use the default class loader.
- An array of Class objects, one for each interface to be implemented.
- An invocation handler
-
Proxies can be used for purposes such as
-
-
- Routing method calls to remote servers
- Tracing method calls for debugging purposes
- Associating user interface events with actions in a running program
-
6.5.3 Properties of Proxy Classes
All proxy classes extend the class Proxy.
All proxy classes override the toString, equals, and hashCode methods of the Object class.
The name of proxy classes are not defined. The Proxy class in Oracle’s virtual machine generates class names that begin with the string $Proxy.
If you call the newProxyInstance method twice with the same class loader and interface array, you get 2 objects of the same class. You can also obtain that class with the getProxyClass method:
Class proxyClass = Proxy.getProxyClass(null, interfaces);
A proxy class is always public and final. All non-public interfaces must belong to the same package, and the proxy class will also belong to that package.
By calling the isProxyClass method of the Proxy class, you can test whether a particular Class object represents a proxy class.