Full Review#
Recall the Uses of Interfaces#
Interfaces are powerful and common. Below is a list of ways interfaces are used. You are not expected to understand these examples yet. Future lessons should add a lot of clarity.
For Abstraction & Extensibility: Here we see a method that accepts a
List<Integer>instead of anArrayList<Integer>. This allows the caller to use a variety of datastructures so long as they implementList, an interface.
public int calculateSumtin(List<Integer> list) { }
Functional Programming: It is powerful to pass around functions as arguments or to have functions return functions.
public static void main(String[] args) {
String[] fruit = { "apple", "cherry", "watermelon" };
Consumable<String> behavior = getBehavior("I have a");
actOnEach(Arrays.asList(fruit), behavior);
}
/**
* Return a Consumable function that prints out the argument using
* a given prefix. This makes use of a concept called "closures".
*/
public Consumable<String> getBehavior(String prefix) {
// create a function using the Lambda syntax
return s -> System.out.printf("%s %s\n", prefix, s);
}
public void actOnEach(List<String> list, Consumable<String> consumer) {
// consume the elements in natural sorted order
list.sort(null);
for (String s : list) {
consumer.accept(s);
}
}
Callbacks that handle Events is necessary and frequent in GUI Programming. This allows a program be be Event Driven, meaning that when the user of the application causes an event, code is triggered. Here is example code for how callbacks can be implemented in a GUI application when setting up a menu.
private void createMenuBar() {
JMenuBar bar = new JMenuBar();
this.setJMenuBar(bar);
JMenu menu = new JMenu("Options");
JMenu item = new JMenuItem("Tickle", 'T');
// Use a method pointer to fulfill the ActionListener interface
item.addActionListener(this::myCallback);
menu.add(item);
bar.add(menu);
}
private void myCallback(ActionEvent ae) {
System.out.println("That tickles!");
}
We can Customize functionality such as how a list is sorted. We have a lesson dedicated to this activity.
public static void main(String[] args) {
String[] fruit = { "apple", "cherry", "watermelon" };
List<String> list = Arrays.asList(fruit);
// get a Comparator from the Collections class to allow us
// to sort this list in reverse order
list.sort(Collections.reverseSort());
We can enable iteration via for-each loop on an a class that didn’t inheritly support it. We do this by implementing the
Iterableinterface and supplying (or implementing) theIteratorinterface.
public class Dogs implements Iterable<Dog> {
private List<Dog> dogs;
// Return the iterator provided by the List<Dog> object.
// We don't need to implement it directly. Whew!
public Iterator<Dog> iterator() {
return dogs.iterator();
}
public static void demoForEach() {
Dogs myDogs = new Dogs();
/* code that adds dogs not shown */
// Iterate over the Dogs class directly, NOT List<Dog>
for (Dog dog : myDogs) { ... }
}
}
Try-with-resources is built into Java and it allows a programmer to easily and safely close resources. We often do this when working with files so that we don’t need to call the
close()method. For example, the code below will create theScannerin a try-with-resources clause. Whether the file is found or not, we are guaranteed to close the Scanner object correctly. Any class that implementsAutoCloseablecan be used this way.
public void readFile(String filename) {
try (Scanner parser = new Scanner(new File(filename))) {
while (parser.hasNext()) {
System.out.println(parser.next());
}
} catch (FileNotFoundException ex) {
// Let the user know the file wasn't found
}
}
Streams are a powerful way to process data using functional programming. See future lessons on how to use Streams.
More Types of Method References#
We saw two types of method references here. Let’s look at two more. 3. Instance Method Reference (of an arbitrary object of a class)
Syntax: ClassName::instanceMethodName
Used when the method is called on an arbitrary instance of a class.
This will only work when the object on which the method is called is the first parameter of the functional interface.
public class Example {
public void showTwoExamples(List<Kid> kids) {
// `toUpperCase` requires an instance object, and the method takes zero arguments.
// Function<String, String> will provide one argument which gets turned into the instance.
Function<String, String> toUpperCase = String::toUpperCase;
System.out.println(toUpperCase.apply("hello")); // Output: HELLO
// List has a method named `sort` that takes a Comparator.
// The Kid class implements Comparable<Kid>, not Comparator.
// Comparable.compareTo requires an instance of a Kid and one argument of type Kid.
// When we use the following syntax and call Comparator.compare(k1, k2), we will
// use the k1 as the instance, and k2 as the argument to compareTo.
// In short, `Kid::comareTo` matches the signature of Comparator.
kids.sort(Kid::compareTo);
}
}
In the first example above, the string "hello" is the argument passed into apply. When apply is called, Java is effectively making the following call.
"hello".toUpperCase();
This type of method reference works only when the first argument to the method defined in the interface matches the object type needed. In our example, String::toUpperCase is an instance method on the String class. Therefore, the signature must accept a String as the first argument.
This type of method reference is useful when the one wants to define the interface “early” and define the object instance “later.” If you know the object, it can be more clear to simply use the object then.
In the second example, when Comparable is invoked, it get conceptually converted to work with compareTo.
// the following call
comparable.compare(kid1, kid2);
// gets converted to
kid1.compareTo(kid2);
In reality, this works because, under the covers, every instance method actually has a hidden, implicit this argument.
Implicit this Argument
In our Java code, when we want to call an instance method, we use the following syntax. obj.method(args)
In reality, the method gets invoked like: method(obj, args)
Java will allow us to write an instance method prototype that will explicitly identify the implicit this. Here is perfectly valid, working code to illustrate:
public class ExplicitThis {
private int x = 0;
public static void main(String[] args) {
ExplicitThis et = new ExplicitThis();
et.implicitPrint("This makes sense.");
et.printMsg("How does this work?!");
}
public void printMsg(ExplicitThis this, String msg) {
System.out.println(msg);
// this is still the reserved keyword this!
this.x = 5;
}
public void implicitPrint(String msg) {
System.out.println(msg);
this.x = 5;
}
}
Constructor Reference
Syntax: ClassName::new
Used to refer to a constructor. (This is rarely used.)
public class Example {
public static void main(String[] args) {
Supplier<StringBuilder> createStringBuilder = StringBuilder::new;
StringBuilder sb = createStringBuilder.get();
System.out.println(sb.append("Hello, Constructor Reference!")); // Output: Hello, Constructor Reference!
}
}
Abstract Classes vs Interfaces#
What differentiates an abstract class from an interface?
Both an abstract class and an interface:
Cannot be instantiated.
May have
abstractmethods.May have
staticmethods with implementations.May have
static finalfields.May have instance methods with implementation, but they are annotated slightly differently:
Abstract class: looks normal with no extra keywords.
Interface: must be annotated with the
defaultkeyword. Some restrictions apply.
May inherit via
extends, but with unique restrictions.Abstract class:
Uses
extendsto extend any otherclass.Uses
implementsto implement an interface.It may not use
extendson aninterface.
Interface:
May not extend a
class.Uses
extendsto extend any otherinterface.It may not use the keyword
implements.
|
|
|
|
|---|---|---|---|
Abstract Class |
âś… |
❌ |
âś… |
Interface |
❌ |
âś… |
❌ |
Only Abstract Class:
May have instance fields
Uses the
classkeyword in its definitionHas a class inheritance hierarchy
A class can inherit from only ONE class (
abstractor not).May use
publicandprivateunrestricted.
Only an Interface:
Uses the
interfacekeyword in its definitionDoes not impose a class inheritance hierarchy. The implementing class can inherit from any class it wants.
A class can implement MANY different interfaces.
Default method restrictions: (in the interface’s implementation)
Cannot access any instance fields (not even those in the implementing class).
Cannot use the
superkeywordprivate defaultmethods are not allowed.
Allowed:
private staticmethods andprivateinstance methods.Supports the ability for a developer to implement the interface with a Lambda Expression or Method Reference (Functional Interfaces only)
An interface cannot
implementsanother interface.
public interface Printable {
// Interface methods are implicitly public abstract
// Allowed
void print1();
public abstract void print2();
// The following methods are NOT abstract; they have implementation.
// Allowed
private static void privateStatic() { }
public static void publicStatic() { }
// Allowed
public default void publicDefault() { }
// Allowed
private void privateInstance() { }
// DISALLOWED: public instance are NOT allowed.
// They must use the `default` keyword or be abstract.
public void publicInstance() { }
// DISALLOWED: private default are NOT allowed
private default void privateDefault() { }
}
Conflicts#
Duplicate Methods#
If two interfaces each have a method with the same signature[1] (including the return type), then the implementing class would provide a single implementation that would be used for both.
For example:
public interface Printable {
public void print1();
public void print2();
}
public interface Printable2 {
public void print1();
public void print3();
}
class DemoConflict implements Printable, Printable2 {
@Override
public void print1() {
// Used to fulfill both interfaces
}
@Override
public void print2() { }
@Override
public void print3() { }
}
Resolving default Conflicts#
It is possible for a class to implement two different interfaces where each has a default method with the same signature (including return type). This causes a conflict that needs to be resolved by the implementing class; it must override the default method to provide a single, resolved implementation. If the impelmentation wants to use an implementation provided by the interface, this is allowed. Java enables a way to call the correct default method using super.
For example:
interface Interface1 {
default void display() {
System.out.println("Interface1");
}
}
interface Interface2 {
default void display() {
System.out.println("Interface2");
}
}
class MyClass implements Interface1, Interface2 {
// It is required to override to resolve the conflict
@Override
public void display() {
// we are allowed to use the Interface's version.
// Explicitly call using this special syntax.
Interface1.super.display();
// we can call both if we, but it is not required
Interface2.super.display();
// Interface1.display() won't work!!
// Without `super` the syntax would be indicating that the
// display() method is static, which it is not.
}
}
Note: If the
defaultmethod had identical signatures but with different return types, this would cause an unresolvable compiler error when attempting to have a single class implement both interfaces.
Unresolvable Conflicts#
If two interfaces have methods with identical signatures, but the return type is different, then those interfaces cannot be implemented by one class.
For Example:
public interface Printable {
public int print1(); // returns int
}
interface Printable2 {
public void print1(); // returns void
}
// Impossible to implement AS-IS
class DemoConflict implements Printable, Printable2 {
// This class cannot be implemented.
// The print1 methods have signatures that cannot be resolved.
}
Function<T, R>#
Let’s look at this generic interface more closely. We will look at two default methods.
/**
* The function TAKES something of type T
* The function RETURNS something of type R
*/
public interface Function<T, R> {
/**
* Applies this function to the given argument.
*
* @param t the function argument. The function TAKES something of type T
* @return the function result. The function RETURNS something of type R
*/
R apply(T t);
// The `Function<T,R>` interface has other methods such as `andThen` and `compose`.
// These other methods are `default` methods, meaning that the functionality is actually
// provided for you. The syntax can look pretty wonky!!
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
}
Implementation Types#
Students do not need to know about these implementation types.
An “implementation type” describes where code is written. Where code is written will define its scope (where and how code can be referenced). These implemenation types leverage advanced techniques, and these lessons will only make use of Top-Level and Anonymous. We illustrate this simply to be more complete.
While classes and interfaces have differences, they are mostly similar.
Type |
Description |
Class |
Interface |
|---|---|---|---|
Top-Level |
Defined in its own file |
Yes |
Yes |
Static Nested |
Statically defined inside another class |
Yes |
implicitly static |
Inner |
Non-static class inside another class |
Yes |
No |
Interface Nested |
Defined inside an interface |
No |
Yes |
Local |
Class inside a method |
Yes |
No |
Anonymous |
Inline Implementation |
Creates an anonymous class |
Can anonymously extend an interface |
Package-Private |
Non-public class in the same file |
Yes |
Yes |
TODO: Provide example Implementation for each of the types listed above
// Top-Level: in file MyInterface.java
public interface MyInterface {
void act();
}
// Static Nested: in file MyClass.java
public class MyClass {
public interface MyInterface {
void act();
}
}
Footnotes#
[1] Method Signature has a formal definition to be:
Method name
Parameter list (types and order)
Note: The return type of the method is NOT included in the method signature.
However, many people (including Mr. Stride) will sometimes be a bit informal and loose to also include the return type as a part of the method signature. While this is technically incorrect, it can help make some discussions a bit more brief. It can also lead to confusion! Be careful!