Reputation: 1865
Let's take an example to make it easier. I build a list which the constructor takes an integer
and a List<Integer>
. My list will contains all the elements of the given list multiplied by the integer
. My list does not store the new elements but compute them on the fly:
class MyList extends AbstractList<Integer> implements RandomAccess {
private final int multiplier;
private final List<Integer> list;
public MyList(int multiplier, List<Integer> list) {
this.multiplier = multiplier;
this.list = list;
}
@Override
public Integer get(int index) {
return list.get(index) * multiplier;
}
@Override
public int size() {
return list.size();
}
}
Then we can call new MyList(3, list)
with list = [0, 1, 2, 3]
to get [0, 3, 6, 9]
.
I would like to limit the developer to give to the MyList
constructor a list which is also RandomAccess
, to be sure he will not ruin performances.
I tried to change the constructor with:
public <E extends List<Integer> & RandomAccess> MyList(int multiplier, E list)
MyList
is not the issue but now we cannot invoke the constructor without using an implementation of both List<Integer>
and RandomAccess
like ArrayList<Integer>
. So someone who have this list: List<Integer> list = new ArrayList<>();
cannot do new MyList(3, list);
(Because it is declared with List<Integer>
instead of ArrayList<Integer>
).
The other solution I have is this one:
public MyList(int multiplier, List<Integer> list) {
if(!(list instanceof RandomAccess)) {
// Do something like log or throw exception
}
this.multiplier = multiplier;
this.list = list;
}
But now I cannot check at compile time if the list implements RandomAccess
, and I need to use instanceof
and I hate doing this.
I'm pretty sure there is a better way but what is it?
Upvotes: 6
Views: 103
Reputation: 37655
You could adopt the solution used by Collections.unmodifiableList
. Instead of a public constructor, have a static method that returns one of two implementations, one implementing RandomAccess
, the other not.
Here is the code for Collections.unmodifiableList
.
public static <T> List<T> unmodifiableList(List<? extends T> list) {
return (list instanceof RandomAccess ?
new UnmodifiableRandomAccessList<>(list) :
new UnmodifiableList<>(list));
}
I know you said you don't like using instanceof
. Neither do I, but sometimes it's the best thing to do.
Note that the solution using the constructor
public <E extends List<Integer> & RandomAccess> MyList(int multiplier, E list)
is not just ugly, in that it forces the programmer to cast (e.g. to an ArrayList
), but it wouldn't actually work. For example, if list
is an instance of Collections$UnmodifiableRandomAccessList
, it would not even be possible to cast it to a type implementing both List
and RandomAccess
, because Collections$UnmodifiableRandomAccessList
is private.
Upvotes: 1
Reputation: 27986
I would suggest using instanceof
. In fact this is exactly what the RandomAccess
documentation suggests:
Generic list algorithms are encouraged to check whether the given list is an instanceof this interface before applying an algorithm that would provide poor performance if it were applied to a sequential access list, and to alter their behavior if necessary to guarantee acceptable performance.
Your constructor could potentially have two implementations. If RandomAccess
is implemented then it stores a reference to the List
otherwise it creates a new ArrayList
and copies all elements to it:
class MyList {
private final int multiplier;
private final List<Integer> list;
public MyList(int multiplier, List<Integer> list) {
this.multiplier = multiplier;
if (list instanceof RandomAccess)
this.list = list;
else
this.list = new ArrayList<>(list);
}
public int get(int index) {
return multiplier * list.get(index);
}
}
Upvotes: 1
Reputation: 425328
If your class needs a random access list, then your class should deal with that and not push your class's needs onto the caller. Further, it would be simpler to do the multiplication in the constructor - you have to do it at some point, but doing it early means you can throw away lots of code:
class MyList extends ArrayList<Integer> {
public MyList(int multiplier, List<Integer> list) {
for (Integer i : list)
add(i * multiplier);
}
}
That's all you need, and it is safer: with your immemtation, both your list and the caller have a reference to the list. If after invoking the constructor the calling changes the list, other code using the multiplied list will unexpectedly see values in the list change.
Upvotes: 0