views:

113

answers:

5

Let's say I have a class which, internally, stores a List of data:

import java.util.List;

public class Wrapper
{
    private List<Integer> list;

    public Wrapper(List<Integer> list)
    {
     this.list = list;
    }

    public Integer get(int index) { return list.get(index); }
}

For the sake of this example, pretend it's a useful and necessary abstraction. Now, here's my concern: As a programmer who knows the underlying implementation of this class, should I be specific about which type of List I ask for in the constructor? To demonstrate, I've made this test:

import java.util.List;
import java.util.ArrayList;
import java.util.LinkedList;

public class Main
{
    public static void main(String[] args)
    {
     long start;
     List<Integer> list1 = new ArrayList<Integer>();
     List<Integer> list2 = new LinkedList<Integer>();
     Wrapper wrapper1, wrapper2;

     for(int i = 0; i < 1000000; i++)
     {
      list1.add(i);
      list2.add(i);
     }

     wrapper1 = new Wrapper(list1);
     wrapper2 = new Wrapper(list2);

     start = System.currentTimeMillis();

     wrapper1.get(500000);

     System.out.println(System.currentTimeMillis() - start);

     start = System.currentTimeMillis();

     wrapper2.get(500000);

     System.out.println(System.currentTimeMillis() - start);
    }
}

As you most likely know, randomly accessing an element takes a bit more time with a linked list as opposed to an array. So, going back to the Wrapper constructor, should I be general and allow for any type of List, or should I specify that the user pass an ArrayList to ensure the best possible performance? While in this example, it may be easy for the user to guess as to what the underlying implementation of the method get is, you could imagine that this was something more complex. Thanks in advance!

+3  A: 

The whole point of interfaces is to allow for agnosticism about the underlying implementation. The very use of the List type as opposed to LinkedList or ArrayList is to allow general operations without having to worry about this kind of problem. As long as your code can be written without having to rely upon methods not exposed by List you don't need to worry.

It is likely a user of this class is writing code that has other requirements on the type of list they use, they might be appending lots into the middle of the list for instance where a LinkedList excels. As such you should accept the most generic type possible and assume the user has a good reason for using that type.

This isn't however to stop you perhaps including a javadoc comment that the use of an ArrayList might be better if there is no difference in any other use of the application.

Chris
You can also make your own copy of the list (using whatever implementation you want). This would be necessary if, for example, you wanted to make sure that any changes made after it was passed didn't affect your class.
kenj0418
Also you can say List<? extends Integer> to make it possible to send in lists of subtypes. Obviously not the case with Integer since its final -- but I'm assuming that was just a simplified example.
kenj0418
A: 

This probably is something one can argue about.

My arguments for requiring a specific type are:

  • The wrapping class really knows which type would be best-suited in all the situations the wrapping class is used in. Random access is a good example for this -- it's unlikely that there is a better list implementation for this scenario than a list backed by an array.
  • The wrapping class actually makes assumption about the implementation. If such assumptions are made, make sure they are fulfilled by requiring the appropriate type.

The argument against requiring a specific type is:

  • The wrapping class is used in different scenarios and there are different implementations of List that are more or less suited for a specific scenario. I.e., it should be up to the caller to find the implementation that suits the scenario best. There also may be cases in which the best (TM) implementation of the List had not been invented yet by the time the wrapper class was written.
rodion
'it's unlikely that there is a better list implementation for this scenario' - that's a very big assumption. There are lots of possibly reason for having a different implementation. For example , maybe you need one that's designed for concurrency because your using it in many threads. Don't force your implementation details on your callers.
kenj0418
kenj, this is admittedly a big assumption, and I won't say that it holds in general. However, I strongly believe that there are some cases where this assumption is justified, such as in the random-access (local to one CPU) example.
rodion
A: 

Go with ArrayList if you are going to be randomly accessing from this list or you think you will be adding to this list a lot.

Go with LinkedList if you are going to accessing the elements in series most of the time.

ddcruver
That wasn't his question - he asked whether he should REQUIRE that choice to be forced back on callers of the class.
kenj0418
+1  A: 

So, going back to the Wrapper constructor, should I be general and allow for any type of List?

Is the intention of the wrapper to support any type of list? or only ArrayList?

...or should I specify that the user pass an ArrayList to ensure the best possible performance?

If you leave the general List it will be just fine. You let the "client" of that class to decide whether or not he may use ArrayList. Is up to the client.

You may also use the RandomAccess interface to reflect your intention, but since it is only a marker interface probably it wont make much sense.

So again, leaving it as general List will be enough.

OscarRyz
A: 

I think it depends on what you're trying to achieve with the Wrapper class and/or what you expect clients of Wrapper to use it for.

If your intent is to just provide a wrapper for a List, where clients shouldn't expect a certain level of performance from get() (or whatever the method's name is), then your class looks fine to me as-is, except for the fact that it's just a reference to the constructor's listparameter that's getting copied, rather than the contents of the list itself (more on this in the 3rd point below).

If, however, you tell your clients to expect get() to be very responsive, some alternatives come to mind (there might be more):

  1. Write a set of constructors that accept only those implementations of a List that you know have high performance for whatever operation(s) get() will execute on it. For example:

} // if I knew how to avoid this brace I would...

 public Wrapper {
     Wrapper(ArrayList<Integer> list) { ... }
     Wrapper(KnownListImplementationThatWillMakeMyGetMethodFast<Integer> list) { ... }

     //...
 }

One drawback of doing this is that if another efficient List implementation comes along, you'll have to add another constructor for it.

  1. Leave your Wrapper class untouched, but inform your clients (via some form of documentation, say, class comments, a README file, etc.) that certain operations on whatever List implementation they pass in are expected to have a certain performance (e.g. "get() must return in constant time"). If they then "misuse" your wrapper by passing in a LinkedList, it's their fault.

  2. If you want to guarantee that your get() implementation is quick, it's worth copying the list you receive in the constructor into a member data structure that meets your performance constraints. This can be an ArrayList, some other List implementation you know of, or a container that doesn't implement the List interface altogether (e.g. something special-purpose you've written). With regard to what I said earlier about copying the List's contents vs. copying a reference to it, any "write" operations you perform on the reference you have will not flow through to your client's copy of the List if you copy its contents. This is usually a good way to avoid the client wondering why their copy of the list gets touched when invoking operations on your Wrapper, unless you explicitly tell them to expect this behavior.