Programming Abstractly through Interfaces

Interface Implementation

When you create a interface you are defining a contract for what a class can do, without saying anything about how the class will do it. A interface is a contract. Interfaces can be implemented by any class from any inheritance tree, this lets you take radically different classes and give them a common characterstic. By defining a interface within the class you agree to provide the code for any of the interfaces methods, an interface generally has abstract methods (a abstract class can contain both abstract and non-abstract methods). There are many interfaces supplied by Java for example java.lang.Comparable, java.io.Serializable and java.util.List.

The rules of an interface are

Interface declaration
public abstract interface Rollable { ... }       # should use the below declaration as the keyword abstract is
                                                 # redundant, but this is acceptable
interface Rollable { ... }                       # there is no need to specify the abstract or public keywords
Method declaration

public interface Bounceable {
   public abstract void bounce();
   void setBounceFactor();
}

Note: you do not need to specify the public abstract modifiers (now redundant), so both above methods are acceptable, all methods are public and abstract no matter what you see.

Default method
interface Bounceable {
    default void defaultMethod() {
        System.out.println("This is an interface default method");    
    }
}

Note: default methods must be implemented and are not abstract but can be overriden, also becareful of implementing multiple default methods (with the same signature) from multiple interfaces, you will get a compiler error
Interface Constants

public interface Bounceable {
   public static final int LOW_BOUNCE = 1;
   int HIGH_BOUNCE = 10;                      # HIGH_BOUNCE will be implicity be defined as public final static
}

Note: by putting constants in an interface you are guaranteeing that any class implementing the interface will have access to the same constant. They are also used to associate values with easy names to remember. as with the same with methods you do not need to specify the public final static modifiers (now redundant), so both above declarations are acceptable, all constants are public final static no matter what you see.

Implementing a interface

public class rubberBall implements Bounceable {
   public void bounce() { ... }               # must supply the code for the implemented interfaces method
}

Note: you are binded by a contract to supply the code for any of the interfaces methods, otherwise the compiler will complain. The signature of the method must be same as in the interface.

Also a abstract class can implement a interface but at somepoint the interface methods code must be implemented to honour the binding contract.

Using a static method in a interface
interface Bounceable {
    static String publicStaticMethod() {
        return "Interfaces public static method";
    }
}

Note you can then use the public static method by calling Bounceable.publicStaticMethod();
Implementing multiple interfaces

public class rubberBall implements Bounceable, Serializable, Runnable { ... }

Extending an interface public interface Bounceable extends Moveable { ... }

Note: an interface can itself extend another interface but never implement anything

A complete example explaining implementing an interface

Implementing an interface class arrayTest {
   public static void main(String[] args) {

      test t1 = new test();
      t1.methodA();
   }
}

class test implements inter1 {                        // enter into the interfaces contract, abstraction rules now apply

   public void methodA() {                            // method body for abstract methodA
      System.out.println("interface inter1 - methodA");
      count = 50;                                     // illegal - count has been marked public, static and final by the interface
      System.out.println("count" + count);
   }
}

interface inter1 {                                    // 100% abstract class - all methods are abstract
   int count = 20;                                    // implicity marked public, static and final - even if not stated

   public void methodA();                             // implicity marked public and abstract - even if not stated

   public void methodB() {                            // illegal - must be abstract method - no curly braces
      System.out.println("class test - methodB");
   }
}

Note: this program will not compile as there are a number of errors.


Distinguish class inheritance from interface inheritance including abstract classes

Here is a table of the differences between abstract class and an interface. If you want to provide common, implemented functionality among all implementations of your component, use an abstract class. Abstract classes allow you to partially implement your class, whereas interfaces contain no implementation for any members.

Abstract class Interface
Abstract class can have abstract and non-abstract methods. Interface can have only abstract methods. Since Java 8, it can have default and static methods also.
Abstract class can define public, protected, package-private or private concrete methods All abstract methods are public
An abstract method must be declared abstract If a method has no body by default its implicity abstract
Abstract class doesn't support multiple inheritance. Interface supports multiple inheritance.
Abstract class can have final, non-final, static and non-static variables. Interface has only public, static and final variables.
Abstract class can provide the implementation of interface. Interface can't provide the implementation of abstract class.
The abstract keyword is used to declare abstract class. The interface keyword is used to declare interface.
An abstract class can extend another Java class and implement multiple Java interfaces. An interface can extend another Java interface only.
An abstract class can be extended using keyword "extends". An interface can be implemented using keyword "implements".
A Java abstract class can have class members like private, protected, etc. Members of a Java interface are public by default.

Declare and use List and ArrayList Instances

Declaration of java.util.list is an interface that extends the Collection interface. List is an ordered collection, you have control on where each element is inserted. Elements can be accessed by their index and you can search the list. Lists can have duplicate and null elements.

Declaration of java.util.ArrayList is a class that extends AbstractList and implements List among other interfaces. ArrayList is a resizeable array implementation of the List interface. This class is the same as Vector except that it is unsynchronized, also ArrayList does not support primitive data types, use primitive data wrappers instead.

Instantiation of ArrayList
ArrayList rawList = new ArrayList(List.of(1, 2, 3, 4, 5));                 # This is a raw type of ArrayList
      
ArrayList <String> stringArray = new ArrayList<String>();                  # Using generics to set the data type
ArrayList <String> stringArray = new ArrayList<String>(10);                # You can set the capacity of the ArrayList, 10 in this example
ArrayList <String> stringArray = new ArrayList<>(10);                      # You can use a blank diamond
ArrayList <Integer> stringArray = new ArrayList<>(List.of(10, 20, 30));    # You can pass values into the ArrayList
ArrayList <String> stringArray = new ArrayList<>();                        # You dont have to specify the capacity
Some ArrayList examples
integerArray.add(intNumber);                              # Add to end of the list
integerArray.add(i, intNumber);                           # add element to specific index location
integerArray.addAll(List.of(15,20,25);                    # add a list of int's to the end of a list

stringArray.removeIf(s -> s.contains"_2"));               # you can even use lambda expressions
stringArray.removeAll(List.of("String_1", "String_2"));   # you can pass Lists to ArrayList methods

There are many methods that can be used for ArrayList most of which are on the List interface.

Type of Functionality ArrayList methods
Comparison equals
isempty
Searches contains
containsAll
indexOf
lastIndexOf
Data Manipulation add
addAll
clear
get
hashCode
remove
removeAll
replaceAll
retainAll
set
size
sort
Data Transformation copyOf (Java 10)
iterator
listIterator
of (Java 9)
spliterator (Java 9)
subList
toArray

Although I am not covering the complete collections I give you the tables belows so that you can understand what is available and when you should use them.

The collections come in three basic flavors each having subflavors of Sorted, Unsorted, Ordered and Unordered

Ordered means that you iterate through the collection in a specific order (not random), for an example you would iterate through an array starting at index position 0, this does not means that the object you retrieve are in any sorted order.

Sorted means you retrieve the objects that are sorted naturally, what i mean here is Strings objects would be alphabetically ordered, int would be numerically ordered.

List (a list cares about the index)
ArrayList

Think of this as a growable array, it is a ordered collection (uses a index) but it is not sorted, this interface implements a RandomAccess interface

Choose this over a LinkedList when you need fast iteration but aren't as likely to be doing a lot of insertion and deletion

Vector

Vector and Hashtable were the original collections. A Vector is the same as a ArrayList but a Vector methods are synchronized for thread safety. Vectors also implement a RandomAccess interface.

Use a ArrayList if you don't need synchronization at it decreases performance.

LinkedList

LinkedList is ordered by index position, like ArrayList but doubly-linked up.

LinkedLists are ideal for stacks and queues, its also a good candidate for fast insertion and deletion

Set (a set cares about the uniqueness)
HashSet

A HashSet is an unsorted, unordered Set. It uses the hashcode of the object being inserted, so the better the hashcode the better performance you will get.

Use this when you want a collection with no duplicates and you don't really care about the order.

LinkedHashSet

A LinkedHashSet is an ordered version of a HashSet that maintains a doubly-linked List across all elements.

use this collection when you care about the order

TreeSet It uses a Red-Black tree structure that guarantees that elements will be in ascending order, according to the natural order of the elements (String would be a,b,c,d,etc , int would be 1,2,3,4), there will also be no duplicates.
Map (a map cares about unique identifiers)
HashMap

The HashMap gives you an unsorted, unordered Map

Use when you need a Map but don't care about the order, fast updates (key/value pairs), allows one null key and many null values.

Hashtable

Hashtable is the synchronized version of a HashMap

If you don't need synchronization then use a HashMap

LinkedHashMap LinkedHashMap maintains insertion order or last accessed.
TreeMap A TreeMap is a sorted Map by the natural order of elements

A quick reference regarding collections

Class
Map
Set
List
Ordered
Sorted
HashMap
X
No
No
Hashtable
X
No
No
TreeMap
X
Sorted
By natural order or custom comparison rules
LinkedHashMap
X
By insertion order or last access order
No
HashSet
X
No
No
TreeSet
X
Sorted
By natural order or custom comparison rules
LinkedHashSet
X
By insertion order or last access order
No
ArrayList
X
By Index
No
Vector
X
By Index
No
LinkedList
X
By Index
No

Lambda Expressions

Before Lambda expression, anonymous inner classes were the feature that enabled you to make your code more concise. Anonymous classes are like local classes, except that they do not have a name. You use them if you need to use a local class only once. Lambda expressions replace the bulkiness of anonymous inner classes.

Functional interfaces provide target types for lambda expressions and method references. Each functional interface has a single abstract method, called the function method for that functional interface, to which the lambda's expression's parameter and return types are matched or adapted. Functional interfaces can provide a target type in multiple contexts such as assignment context, method invocation or cast context.

Lambda Expression Syntax
Argument List         Arrow Token           Body
(int x, int y)            ->                x + y
Parameters:
  • A comma-separated list of formal parameters enclosed in parentheses
  • You can obmit the data type of the parameters in a lambda expression
  • If you include data types you must declare a data type for each parameter
  • You can omit the parenthese if there is only one parameter
  • If you have no parameters you can represent this with an empty parenthese set ()
  • Parenthese are always used when you have multiple parameters
Body:
  • Can consist of a single expression
  • You do not have to enclose a void method invocation in braces
  • Can consist of a statement block, a return statement is not an expression, you must enclose statements in braces {}
Valid Lambda Expression
() -> System.out.println("Hello")             # No parameter syntax, () is required
a -> a + 1                                    # Single parameter syntax, no parenthese required
(a) -> a + 1                                  # Single parameter syntax, parenthese valid but optional
(int a) -> a + 1                              # Single parameter syntax, specifying data type
(var a) -> a + 1                              # Single parameter syntax with local varible type inference

(int a, int b) -> a + b                       # Multiple parameter syntax with declared types, all must have a type
(a, b) -> a + b                               # Multiple parameter syntax with no types, if one has not type all must have no types
(var a, var b) -> a + b                       # Multiple parameter syntax the local variable type, if one has var type then all must
have as well

Note: parenthese are always required for m ultiple parameters

() -> System.out.println("Hello")             # Single line with a statement is valid for methods which have void return type
() -> isTrue && isPlusible                    # Single line expression must be an expression which evaluates to the correct return type or covariant, if method has a return type.
(a) -> {                                      # Using return requires curly braces, all statements must terminate with a semi-colon, you can use local variables
    int count = 1;
    return a + count + 2;
}

Note: You cannot mix var data types with actual data types

There are a number of Generic and Primitaive interfaces that statisfy common patterns and sveral of these are in the java.util.function package

Functional Interface Generic Functional Interface Typed
UnaryOperator<T> DoubleUnaryOperator
IntUnaryOperator
LongUnaryOperator
BinaryOperator<T> DoubleBinaryOperator
IntBinaryOperator
LongBinaryOperator
Consumer<T> DoubleConsumer
IntConsumer
LongConsumer
Predicate<T> DoublePredicate
IntPredicate
LongPredicate
Supplier<T> DoubleSupplier
IntSupplier
LongSupplier
Function<T,R> DoubleFunction<R>
IntFunction<R>
LongFunction<R>

Below is a complete example of a few Lambda expressions

Lambda example
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;

public class SuppliedInterfaceTests {

    public static void main(String[] args) {

        // Set up some test data
        String[] dictionary = {"Angry", "Apple", "Art",
                "Ball", "Box", "Bump",
                "Cap", "Car", "Cone",
                "Dart", "Dog", "Duck"};

        // -- Consumer Example
        // Method returns no result, just does something on
        // the parameter passed
        Consumer str = s -> {
            StringBuilder sb = new StringBuilder(s);
            sb.insert(0, "MyApplication: SuppliedInterfaceTests : ");
            sb.insert(0, LocalDateTime.now().toLocalTime() + ": ");
            System.out.println(sb);
        };
        str.accept("I want to log this statement");

        // -- Predicate Example
        // Method returns a boolean, accepts one parameter
        Predicate aWords = p -> p.indexOf("A") == 0;
        ArrayList a = new ArrayList(Arrays.asList(dictionary));
        // demonstrate with ArrayList.removeIf method which accepts a
        // Predicate as a parameter
        a.removeIf(aWords);
        System.out.println(a);

        // Now, we demonstrate test method on Predicate
        String apple = "Apple";
        if (aWords.test(apple)) {
            System.out.println(apple + " starts with an A");
        }

        // -- Supplier Example
        // Method returns an object, accepts no parameter
        Supplier stringSupplier =
                () -> new String("returning a new String Object");
        System.out.println("stringSupplier.get() = " + stringSupplier.get());

        // -- Function Example
        // Method returns a result, and accepts one parameter
        Function funkyFunction = (s) -> {
            s = s.repeat(5);
            return s;
        };

        System.out.println("funkyFunction.apply() = "
                + funkyFunction.apply("oh no "));

    }
}