• Status: Solved
  • Priority: Medium
  • Security: Public
  • Views: 1530
  • Last Modified:

How to make a generic extend a parameterized class

I am trying to create a generic class that wraps any object that implements the List interface.  Because the wrapper class will only add one method and override one method (the add method), I wanted my wrapper class to extend the parameterized class (i.e. create a is-a relationship) as opposed to containing the parameterized class, as the latter would require the wrapper delegating every method of the List interface to the contained class (there are a lot of methods there).

Here's the code I would like to have been able to write, except that it's not valid code:

public class LockableList<T, E> extends E { // Error: Cannot refer to the type parameter E as a supertype
      private boolean isLocked = false;
      
      // New method
      public void setLock(boolean isLocked) {
            this.isLocked = isLocked;
      }
      
      // Overriden method
      public boolean add(E o) {
            if (isLocked)
                  throw new Exception();
            
            super.add(o); // Error: The method add(E) is undefined for the type Object
      }
}

// Using the generic

LockableList<ArrayList, Integer> myList = new LockableList<ArrayList, Integer>();  // A wrapped ArrayList that contains Integers

myList.add(new Integer(1));  // Ok to add, not locked yet
myList.setLock(true); // No more adds allowed
myList.add(new Integer(2));  // Throws Exception
mylist.clear();  // Because myList is an instance of a class that extends ArrayList, the clear() method in ArrayList is called.
0
mbmast
Asked:
mbmast
2 Solutions
 
Bart CremersJava ArchitectCommented:
I don't think you can get there. What you want here can be simply achieved by subclassing the ArrayList, but it will mean you'll have to subclass for every collection.

You can get close with using a dynamic proxy:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;

/**
 * @author Bart
 * @since 10-sep-2007 10:02:34
 */
public class Test {
    public static void main(String[] args) {
        LockableList<Integer> myList = (LockableList<Integer>) Proxy.newProxyInstance(Test.class.getClassLoader(),
                                                                                      new Class[]{LockableList.class},
                                                                                      new LockedListInvocationHandler<Integer>(
                                                                                          new ArrayList<Integer>()));

        myList.add(new Integer(1));  // Ok to add, not locked yet
        myList.setLock(true); // No more adds allowed
        myList.add(new Integer(2));  // Throws Exception
        myList
            .clear();  // Because myList is an instance of a class that extends ArrayList, the clear() method in ArrayList is called.        String id = "AA";
    }

}

interface LockableList<E> extends List<E> {
    void setLock(boolean lock);
}

class LockedListInvocationHandler<T> implements InvocationHandler {
    private final List<T> list;
    private boolean locked;

    public LockedListInvocationHandler(List<T> list) {
        this.list = list;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("setLock".equals(method.getName())) {
            locked = true;
        } else if (locked && "add".equals(method.getName())) {
            throw new IllegalArgumentException("List is locked");
        } else {
            return method.invoke(list, args);
        }
        return null;
    }
}


but I'm not sure if it is not easier to simply subclass the collections you want to extend:


public class LockableList<E> extends ArrayList<E> {
      private boolean isLocked = false;
     
      // New method
      public void setLock(boolean isLocked) {
            this.isLocked = isLocked;
      }
     
      // Overriden method
      public boolean add(E o) {
            if (isLocked)
                  throw new IllegalArgumentException();
           
            super.add(o);
      }
}
0
 
mbmastAuthor Commented:
Thanks.  The proxy method really defeats the performance benefits of inheritance.  It's my feeling that I'm not trying to do anything that unusual or uncommon.  Lots of devs must need to override methods in container classes, so why isn't overriding (i.e. inheritance) supported by generics.  Seems like a no-brainer to me (but hey, I'm an ex-C++er).  I'm gonna fiddle some more... maybe try your suggestion.
0
 
malfunction84Commented:
If you're looking to make an unmodifiable list...

List<Integer> normalList = new ArrayList<Integer>();
normalList.add(new Integer(3)); // works
List<Integer> lockedList = Collections.unmodifiableList(normalList);
lockedList.add(new Integer(4)); //throws exception

Of course, lockedList can't ever be unlocked.

Overriding is supported by generics.  The difficulty here is that you're trying to allow this to work for every implementation of the List interface, but you're trying to use generics to that end.  That's not really what generics are for.  You're trying to replace a method in every implementation of the List interface.  Really, you're trying to insert code between the specification (List interface) and each of its implementations (classes, e.g. ArrayList).  That's exactly what a proxy is designed to do.  So if you want to be able to swap between lockable and unlockable without using a dynamic proxy, just use a static one:

public class LockableList<E> implements List<E> {
    private List<E> wrappedList;
    private boolean locked = false;

    public LockedList(List<E> wrappedList) {
        this.wrappedList = wrappedList;
    }

    public void setLocked(boolean locked) {
        this.locked = locked;
    }

    public boolean isLocked() {
        return locked;
    }

    public boolean add(E o) {
        if (isLocked()) {
            throw new OperationNotSupportedException();
        } else {
            return wrappedList.add(o);
        }
    }

    /*
     * implement all other methods of the List interface...
     */
}

Now you just pass a List into the constructor:

LockableList<Integer> myList = new LockableList<Integer>(new ArrayList<Integer>());
myList.add(new Integer(1)); // works
myList.setLocked(true);
myList.add(new Integer(2)); //throws exception

You can use any implementation of List that you want.  ArrayList, LinkedList, Stack, Vector... they'll all work as arguments to the constructor.  This "static proxy" is exactly how the "unmodifiableList" method creates unmodifiable lists, except that this way gives you more control via the setLocked(boolean) method.
0

Featured Post

Hire Technology Freelancers with Gigs

Work with freelancers specializing in everything from database administration to programming, who have proven themselves as experts in their field. Hire the best, collaborate easily, pay securely, and get projects done right.

Tackle projects and never again get stuck behind a technical roadblock.
Join Now