Using Interfaces with Generics and Strengthen bounded type parameters


Using Interfaces with Generics

Let's say you want to define an interface that allows publishing / consuming data to and from different types of channels (e.g. AMQP, JMS, etc), but you want to be able to switch out the implementation details

Let's define a basic IO interface that can be re-used across multiple implementations:

public interface IO<IncomingType, OutgoingType> {
	void publish(OutgoingType data);
	IncomingType consume();
	IncomingType RPCSubmit(OutgoingType data);
}

Now I can instantiate that interface, but since we don't have default implementations for those methods, it'll need an implementation when we instantiate it:

IO<String, String> mockIO = new IO<String, String>() {
 
	private String channel = "somechannel";
 
	@Override
	public void publish(String data) {
		System.out.println("Publishing " + data + " to " + channel);
	}
 
	@Override
	public String consume() {
		System.out.println("Consuming from " + channel);
		return "some useful data";
	}
 
	@Override
	public String RPCSubmit(String data) {
		return "received " + data + " just now ";
	}
};
 
mockIO.consume(); // prints: Consuming from somechannel
mockIO.publish("TestData"); // Publishing TestData to somechannel
System.out.println(mockIO.RPCSubmit("TestData")); // received TestData just no

We can also do something more useful with that interface, let's say we want to use it to wrap some basic RabbitMQ functions:

public class RabbitMQ implements IO<String, String> {
 
	private String exchange;
	private String queue;
 
	public RabbitMQ(String exchange, String queue){
		this.exchange = exchange;
		this.queue = queue;
	}
 
	@Override
	public void publish(String data) {
		rabbit.basicPublish(exchange, queue, data.getBytes());
	}
 
	@Override
	public String consume() {
		return rabbit.basicConsume(exchange, queue);
	}
 
	@Override
	public String RPCSubmit(String data) {
		return rabbit.rpcPublish(exchange, queue, data);
	}
}

Let's say I want to use this IO interface now as a way to count visits to my website since my last system restart and then be able to display the total number of visits - you can do something like this:

import java.util.concurrent.atomic.AtomicLong;
public class VisitCounter implements IO<Long, Integer> {
 
	private static AtomicLong websiteCounter = new AtomicLong(0);
 
	@Override
	public void publish(Integer count) {
		websiteCounter.addAndGet(count);
	}
 
	@Override
	public Long consume() {
		return websiteCounter.get();
	}
 
	@Override
	public Long RPCSubmit(Integer count) {
		return websiteCounter.addAndGet(count);
	}
}

Now let's use the VisitCounter:

VisitCounter counter = new VisitCounter();
 
// just had 4 visits, yay
counter.publish(4);
 
// just had another visit, yay
counter.publish(1);
 
// get data for stats counter
System.out.println(counter.consume()); // prints 5
 
// show data for stats counter page, but include that as a page view
System.out.println(counter.RPCSubmit(1)); // prints 6

When implementing multiple interfaces, you can't implement the same interface twice. That also applies to generic interfaces. Thus, the following code is invalid, and will result in a compile error:

interface Printer<T> {
	void print(T value);
}
 
// Invalid!
class SystemPrinter implements Printer<Double>, Printer<Integer> {
	@Override
	public void print(Double d){
		System.out.println("Decimal: " + d);
	}
 
	@Override
	public void print(Integer i){
		System.out.println("Discrete: " + i);
	}
}

Strengthen bounded type parameters

class SomeClass {
}
 
class Demo<T extends SomeClass> {
}
 
But a type parameter can only bind to a single class type.
 
An interface type can be bound to a type that already had a binding. This is achieved using the & symbol:
 
  interface SomeInterface {
  }
 
  class GenericClass<T extends SomeClass & SomeInterface> {
  }
 
This strengthens the bind, potentially requiring type arguments to derive from multiple types.
 
Multiple interface types can be bound to a type parameter:
 
  class Demo<T extends SomeClass & FirstInterface & SecondInterface> {
  }
 
But should be used with caution. Multiple interface bindings is usually a sign of a code smell, suggesting that a new 
type should be created which acts as an adapter for the other types:
 
  interface NewInterface extends FirstInterface, SecondInterface {
  }
 
  class Demo<T extends SomeClass & NewInterface> {
  }

Implementing interfaces in an abstract class

A method defined in an interface is by default public abstract. When an abstract class implements an interface, any methods which are defined in the interface do not have to be implemented by the abstract class. This is because a class that is declared abstract can contain abstract method declarations. It is therefore the responsibility of the first concrete sub-class to implement any abstract methods inherited from any interfaces and/or the abstract class.

public interface NoiseMaker {
	void makeNoise();
}
 
public abstract class Animal implements NoiseMaker {
	//Does not need to declare or implement makeNoise()
	public abstract void eat();
}
 
//Because Dog is concrete, it must define both makeNoise() and eat()
public class Dog extends Animal {
	@Override
	public void makeNoise() {
		System.out.println("Borf borf");
	}
 
	@Override
	public void eat() {
		System.out.println("Dog eats some kibble.");
	}
}

From Java 8 onward it is possible for an interface to declare default implementations of methods which means the method won't be abstract, therefore any concrete sub-classes will not be forced to implement the method but will inherit the default implementation unless overridden.

Basic Programs