Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 211
Happy Avatar asked Apr 20 '15 23:04

Happy


People also ask

Can a method return multiple types?

As per the Java Language Specification, the methods in Java can return only one value at a time. So returning multiple values from a method is theoretically not possible in Java.

How can I return multiple data types in Java?

We can use Pair in Java to return two values. We can encapsulate all returned types into a class and then return an object of that class.

Can a function return multiple values?

You can return multiple values from a function using either a dictionary, a tuple, or a list. These data types all let you store multiple values.


2 Answers

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.

like image 172
Paul Boddington Avatar answered Oct 30 '22 19:10

Paul Boddington


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);
    }
}
like image 41
sprinter Avatar answered Oct 30 '22 19:10

sprinter