Happy
Happy

Reputation: 1865

How to return an object with multiple types

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

Answers (3)

Paul Boddington
Paul Boddington

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

sprinter
sprinter

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

Bohemian
Bohemian

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

Related Questions