3

I have a list decorator that should, amongst a lot of other things, allow casting from one list to another. The idea is like that, and it already works:

public class ExtendedList<T> implements List<T> {

    private List<T> delegate;

    // constructors, methods, a collector, etc

    public <R> ExtendedList<R> castTo(Class<R> targetType) {
        return delegate.stream().map(item -> (R) item).collect(ExtendedList.toList());
    }
}

I wonder if (and how) it is possible to restrict type parameter R to valid casting targets like T, all superclasses of T and all interfaces that T implements, so that the compiler can already check if casting is possible when I use it:

ExtendedList<Vehicle> vehicles = cars.castTo(Vehicle.class);  // assuming Car implements Vehicle -> OK
ExtendedList<Dog> dogs = cars.castTo(Dog.class);              // assuming Car does not extend Dog -> not OK
Michael
  • 41,989
  • 11
  • 82
  • 128
Andreas Dolk
  • 113,398
  • 19
  • 180
  • 268
  • 1
    I can't test this at the moment, but does `public ` not work? – Jacob G. Feb 26 '20 at 15:43
  • Do you also want to allow `vehicles.castTo(Car.class)`? – Sweeper Feb 26 '20 at 15:43
  • @Sweeper no, that shouldn't work. Not every `Vehicle` is a `Car` so it would be OK for my method to not allow that. – Andreas Dolk Feb 26 '20 at 15:45
  • @JacobG. `` emits Unexpected Token errors. That's obviously no valid syntax. Was my first idea also. – Andreas Dolk Feb 26 '20 at 15:47
  • See [this](https://stackoverflow.com/questions/2800369/bounding-generics-with-super-keyword). The conclusion seems to be "it's not useful"... – Sweeper Feb 26 '20 at 15:57
  • In case you didn't know, streaming the elements one by one and casting them individually is pointless. [It will succeed](https://ideone.com/0SNs48) even if the elements do not match (the cast to `R` is erased to `Object` -- everything succeeds). You may as well just do `return (ExtendedList) delegate`. That is, unless you care about getting a copy of the list, but even in that case there are more concise ways to accomplish that than what you've written. – Michael Feb 26 '20 at 16:13
  • `(R) item` does nothing and in fact generates a compiler warning that tells you this, because at runtime there is no `R`. Use `targetType.cast(item)` instead. – VGR Feb 26 '20 at 16:28
  • @Michael Yes, it's pointless, from a technical perspective, but nevertheless, I have cases in the code where I get a `List` from somewhere and need to use that on a function that requires `List`. I can use raw types or waste CPU time to create a copy of the list with where all items have the expected type... That annoys me since Java 5 introduced the generics. – Andreas Dolk Feb 26 '20 at 16:38
  • 1
    "*to create a copy of the list with where all items have the expected type*" You missed my point. Your code does not *ensure* anything. It just copies elements from `List` to another, paying no attention to the types of the elements, and it does so inefficiently one at a time. See @VGR's point – Michael Feb 26 '20 at 16:41
  • @Sweeper Thanks for closing - the other question is not related to my question and does not answer it at all. Please pay more attention to that, when you vote for closing. I asked for a solution to my problem and not for an explanation why something we talked in the comments about does not work. – Andreas Dolk Feb 26 '20 at 18:00
  • @Andreas_D [An answer](https://stackoverflow.com/a/5671079/5133585) to the dupe target showed the same solution as the accepted answer here i.e. a static method. Therefore, I concluded that this question is a duplicate and closed. I do apologise for forgetting to point out which answer in the dupe target answered your question. – Sweeper Feb 26 '20 at 18:10
  • @Sweeper We close questions if the question is a duplicate, not if a possible answer can be found on another question that asked something different. Nevertheless, Artyer gave an answer and I can continue with that. – Andreas Dolk Feb 26 '20 at 18:21

1 Answers1

1

You can do it with a static method:

public <R> ExtendedList<R> castToUnchecked() {
    return delegate.stream().map(item -> (R) item).collect(ExtendedList.toList());
}

public static <R, U extends R> ExtendedList<R> castTo(ExtendedList<U> l) {
    return l.<R>castToUnchecked();
}

Which ensures that R is a superclass of U, used as:

ExtendedList<Vehicle> vehicles = ExtendedList.<Vehicle>castTo(cars);
ExtendedList<Dog> dogs = ExtendedList.<Dog>castTo(cars);  // error: `Car` doesn't extend `Dog`
Artyer
  • 31,034
  • 3
  • 47
  • 75
  • Thanks! That works with one exception: I have to give 2 type parameters when I use the function, like: `ExtendedList.castTo(cars)`, otherwise the compiler is unhappy. Should it work with just one, the target type? – Andreas Dolk Feb 26 '20 at 16:48