How to return an object with multiple types
Asked Answered
S

3

6

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?

Savor answered 20/4, 2015 at 23:1 Comment(3)
Try using | (or operator) instead of the & (and operator). I'm not sure if this will work, but it's worth a try.Brahmanism
So if you want to limit the developer to RandomAccess "to be sure he will not ruin performances", then why would you still want to allow them to invoke your constructor with List, which clearly doesn't implement RandomAccess? Is this just for cosmetic reason?Coplanar
I need to call List.get().Savor
J
1

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.

Johnie answered 20/4, 2015 at 23:11 Comment(0)
B
1

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);
    }
}
Borras answered 21/4, 2015 at 2:9 Comment(1)
Your answer and pbabcdefp's are good enough for me. I store some data if only I need to. So I would like to prevent the developer he should use a List which implements RandomAccess. Is there a programmatic way to do this? Or the only alternative I have is javadoc?Savor
P
0

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.

Pluton answered 20/4, 2015 at 23:30 Comment(4)
Your assumption that 'you have to do it at some point' could well be false. It's entirely possible that this is a list of many items in which very few are actually ever accessed (hence the need for random access).Borras
@Borras I just did a performance test: running this code with an input list size of 10 random numbers took less than 5 microseconds (on an average PC, after warm up) to create the new list of multiplied numbers - that's 0.0000005 seconds per element. I don't know about you, but I can live with that "performance hit" to purge all that code.Pluton
By many items I meant many millions. Frankly if you have 10 item in a list then the data structure doesn't much matter.Borras
Moreover you suggest me to store the elements of the list and I'm trying to avoid that. And I cannot assume a O(n) constructor. This solution could be implemented through a static factory method.Savor

© 2022 - 2024 — McMap. All rights reserved.