How to return an object with multiple types

Let's look at an example to make it simpler. I create a list that the constructor accepts integer and List<Integer> . My list will contain all the elements of this list multiplied by integer . My list does not store new elements, but calculates 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 the RandomAccess constructor a list, which is also RandomAccess , so that it does not ruin the performance.

I tried changing the constructor with:

 public <E extends List<Integer> & RandomAccess> MyList(int multiplier, E list) 

MyList not a problem, but now we can not call the constructor without using the implementation of both List<Integer> and RandomAccess , like ArrayList<Integer> . So, someone who has this list: List<Integer> list = new ArrayList<>(); cannot do new MyList(3, list); (because it is declared with List<Integer> instead of ArrayList<Integer> ).

Another solution that I have is the following:

 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 can’t check at compile time if the list implements RandomAccess , and I need to use instanceof , and I hate doing it.

I am sure there is a better way, but what is it?

+6
source share
3 answers

You can make the decision used by Collections.unmodifiableList . Instead of a public constructor, there is a static method that returns one of two implementations, one implementation of RandomAccess , and 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 that you do not like to use instanceof . Me too, but sometimes it's the best you can do.

Note that the solution using the constructor

 public <E extends List<Integer> & RandomAccess> MyList(int multiplier, E list) 

not just ugly, because it makes the programmer distinguish (e.g. with an ArrayList ), but it actually doesn't work. For example, if list is an instance of Collections$UnmodifiableRandomAccessList , it could not even be applied to a type that implements both list and RandomAccess , because Collections$UnmodifiableRandomAccessList is private.

+1
source

I would suggest using instanceof . In fact, this is exactly what the RandomAccess documentation RandomAccess :

The generalized list algorithms are recommended to check whether the given list is an instance of this interface before applying an algorithm that will provide poor performance if it is applied to a sequential access list and change their behavior, if necessary, to guarantee acceptable performance.

Your constructor could potentially have two implementations. If RandomAccess implemented, then it saves a reference to the List , otherwise it creates a new ArrayList and copies all the elements into 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); } } 
+1
source

If your class needs a random access list, then your class should handle this, and not push your class requirements to the caller. Also, it would be easier to do the multiplication in the constructor - you should do it at some point, but doing it early means that you can throw a lot of code:

 class MyList extends ArrayList<Integer> { public MyList(int multiplier, List<Integer> list) { for (Integer i : list) add(i * multiplier); } } 

This is all you need, and it is safer: with your burden, both your list and the caller have a link to the list. If, after calling the constructor, the caller changes the list, another code using the multiplied list will unexpectedly see the values ​​in the list change.

0
source

Source: https://habr.com/ru/post/985557/


All Articles