I have a couple of Java classes that extend various implementations of the generic List interface. They simply log anything that is added to the List.
The LoggingArrayList is shown below. As the name suggests, it extends ArrayList. The LoggingLinkedList class is identical except that it extends LinkedList.
My main objective is to avoid having to duplicate all of the common code just so that I can use a different base class. I'm trying to adhere to the DRY principle (Don't Repeat Yourself) as much as possible.
First of all, please don't suggest a better way of logging. That's not at all my real application. It's just an easy way to demo the problem I'm having.
I have two closely related questions. The first one is the question in the title. How can I reference a "super" method in a Java class that implements an interface but does not extend another class?
The LoggingArrayList class as shown below works fine but when I change the class declaration from ...extends ArrayList to ...implements List then the three references to super.method() are no longer callable, hence my first question.
A good answer to my second question will almost make the first question moot. The second question is this: Is there a way to declare an abstract base class or perhaps an interface that extends List with default implementations of the various add() methods so that I can simply extend that abstract base class or implement that interface and only specify what kind of List will be the basis for the concrete class?
For example, I'd like to do something like this:
interface LoggingList<T extends Object, L extends List<T>> extends L
{
// overloaded methods go here as shown below
// with overloaded methods declared as default for the interface
}
...then I could simply implement LoggingList one time for each concrete implementation of List without duplicating all of the common code. The concrete classes might then look something like this, with no additional code needed inside their curly braces:
public class LoggingArrayList<T extends Object> implements LoggingList<T, ArrayList<T>> {}
public class LoggingLinkedList<T extends Object> implements LoggingList<T, LinkedList<T>> {}
The problem is that the interface definition as I have proposed it is invalid (won't compile) and also, the references to super.method(s) in the code shown below are unavailable unless I make LoggingList an abstract subclass instead of an interface and then I end up right back where I am now.
Thanks in advance for any ideas on how to accomplish my DRY goal.
Here's my whole LoggingArrayList class.
public abstract class LoggingArrayList<T extends Object>
extends ArrayList<T>
{
protected void log(T e)
{
System.out.println(e == null ? "null" : e.toString());
}
@Override
public boolean add(T e) {
log(e);
// How do I reference a super.method()
// in a class that implements an interface
// but does not extend another class?
return super.add(e);
}
@Override
public boolean addAll(Collection<? extends T> clctn) {
boolean anyChanges = false;
for(T e : clctn)
{
// ensure that we call our overridden version of add()
// so it gets logged.
anyChanges = anyChanges || add(e);
}
return anyChanges;
}
@Override
public boolean addAll(int i, Collection<? extends T> clctn) {
for(T e : clctn)
{
// ensure that we call our overridden version of add()
// so it gets logged.
add(i, e);
i++; // keep the inserted elements in their original order
}
return !clctn.isEmpty();
}
@Override
public T set(int i, T e) {
log(e);
// How do I reference a super.method()
// in a class that implements an interface
// but does not extend another class?
return super.set(i, e);
}
@Override
public void add(int i, T e) {
log(e);
// How do I reference a super.method()
// in a class that implements an interface
// but does not extend another class?
super.add(i, e);
}
}
Once way to do this would be: Delegate.
Instead of having multiple implementations for different kinds of Lists, you could just have one
public class LoggingList<T extends Object> implements List<T>
protected List<T> superList;
public LoggingList(List<T> anotherList) {
superList= anotherList;
}
protected void log(T e) {
System.out.println(e == null ? "null" : e.toString());
}
@Override
public boolean add(T e) {
log(e);
return superList.add(e);
}
And then instead of calling super.
you'd call superList.
You would still have to adapt your constructors away from new LoggingLinkedList()
to new LoggingList(new LinkedList());
- but that should be no biggie...
You can use JDK proxy for this purpuse. Just google "how to use jdk proxy class".
Here described a your use case.
Just for future reference, here's a complete working solution that I put together using the proxy approach as Sergey Morozov suggested. The core of it is less than 50 lines of code. Over half of the code, included at the end, is just a unit test for the actual functionality.
I'm not sure yet if I'll ultimately use this approach for my purpose, but it was a very useful exercise. Thanks for suggesting it Sergey.
public class LoggingListProxyFactory
{
protected static void log(String methodName, Object element)
{
System.out.println(methodName
+ ": ["
+ (element == null ? "null" : element.toString())
+ "]"
);
}
public static <T extends Object> List<T> getProxy(final List<T> list) {
return (List<T>) Proxy.newProxyInstance(
list.getClass().getClassLoader(),
new Class[]{ List.class },
new LoggingInvocationHandler(list)
);
}
private static class LoggingInvocationHandler<T>
implements InvocationHandler
{
final List<T> underlyingList;
public LoggingInvocationHandler(List<T> list) {
this.underlyingList = list;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable
{
// These are the List interface methods that we want to log.
// In every case, the new elements happen to be the last parameter.
//
// boolean add(Object e)
// void add(int index, Object element)
// boolean addAll(Collection c)
// boolean addAll(int index, Collection c)
// Object set(int index, Object element)
String methodName = method.getName();
if( ( "add".equals(methodName)
| "addAll".equals(methodName)
| "set".equals(methodName)
)
// a few additional probably unnecessary checks
&& args != null
&& args.length == method.getParameterCount()
&& method.getParameterCount() > 0
)
{
log(methodName, args[args.length-1]);
}
return method.invoke(underlyingList, args);
}
}
public void testGetProxy() {
List<String>[] testLists = new List[] {
new ArrayList<>(),
new LinkedList<>()
};
for(List<String> aList : testLists)
{
List<String> proxy = LoggingListProxyFactory.getProxy(aList);
// aList.add(42); // type is enforced at compile time
aList.add(aList.getClass().getSimpleName());
aList.add("unlogged");
aList.add(null);
// proxy.add(42); // type is enforced at compile time
proxy.add(proxy.getClass().getSimpleName());
// exercise each the methods that are being logged
proxy.add("foo");
proxy.add(0, "bar");
proxy.add(null);
proxy.addAll(aList);
proxy.addAll(7, aList);
proxy.set(5, "five");
System.out.println();
System.out.println(aList.getClass().getSimpleName()
+ ".size() = " + aList.size());
aList.stream().forEach(System.out::println);
System.out.println();
System.out.println("proxy.size() = " + proxy.size());
proxy.stream().forEach(System.out::println);
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With