1

Converting List of Banana to List of Fruit ...

public class Fruit { }

public class Banana extends Fruit { }

public List<Banana> allBananas() {
    return new ArrayList<>(Arrays.asList(new Banana(), new Banana()));
}

... before returning I have to do following two-steps-casting:

public List<Fruit> getFruits() {
    List<? extends Fruit> fruits = (List<? extends Fruit>) allBananas();
    return (List<Fruit>) fruits;
}

To avoid the intermediate step it can be done as:

public List<Fruit> getFruits() {
    return allBananas().stream().collect(Collectors.toList());
}

But in this case I have to create new List object which is not so good in terms of performance.

I think I don't understand something and maybe doing it wrong. I want to know is my code fully correct and safe? If so - is there a better way to cast? If not - what is wrong and can be improved?

ytterrr
  • 3,036
  • 6
  • 23
  • 32
  • 2
    Your cast in the first version is unsafe anyway. Fundamentally a `List` is *not* a `List`. We have no idea what the context is here - it could well be that what you should *actually* do is just change the signature to `List extends Fruit>` getFruit()` – Jon Skeet May 23 '15 at 08:04
  • Is it technically legal to cast `allBananas()` of initial type `List` to `(List extends Fruit>)` when `Banana extends Fruit`? @JonSkeet – MChaker May 23 '15 at 08:11
  • Something like `Dog and Cat extends Animal` and cast `Dog` to `Cat`. I think it is illogical. @JonSkeet – MChaker May 23 '15 at 08:13
  • @JonSkeet in some cases changing the signature makes return type too wide like in [this](http://stackoverflow.com/a/29984452/4556720) my answer to another question. – ytterrr May 23 '15 at 08:19
  • 1
    @ytterrr: I don't see how that answer is relevant to my suggestion... – Jon Skeet May 23 '15 at 08:37
  • @JonSkeet I mean `List extends Object> findUserNamesByLastname` in that answer. – ytterrr May 23 '15 at 08:40
  • 1
    But here I'm suggesting `List extends Fruit>`, so it's still fairly specific. Fundamentally though you still haven't told us the context. Do you want a view on the existing list, or a *copy* of the existing list? – Jon Skeet May 23 '15 at 08:41
  • @JonSkeet I think _view_ of the existing list is preferable in most cases because we don't have to create another `List` (at least in terms of performance) – ytterrr May 23 '15 at 08:47
  • 2
    Right - so what would you expect to happen if someone called `view.add(new Apple())` to that list? They can't do that if you've presented a `List extends Fruit>`, so that's okay - but if you've presented a `List` then they can, at which point either you break the original `List` or you have to detect it at execution time and throw an exception... and you haven't talked about what you need for *any* of this in your question, making it unanswerable. – Jon Skeet May 23 '15 at 08:51
  • @JonSkeet Thank you for great explanation. It is a pity that I can not accept comment in this site. – ytterrr May 23 '15 at 09:01
  • 1
    Well it's only a comment because it can't be an answer - because the question isn't clear enough *to* answer. If you update your question so that it's answerable, I can add an answer... – Jon Skeet May 23 '15 at 09:01
  • @JonSkeet the question was updated. I think now it's answerable. – ytterrr May 23 '15 at 09:12
  • 1
    Well, it still doesn't say what you actually want - but I've answered with lots of options. – Jon Skeet May 23 '15 at 09:20

3 Answers3

3

You can just do

public List<Fruit> getFruits() {
    return new ArrayList<>(allBananas());
}

Note that your casting example is unsafe, and you'll get compilation warnings. This is because a List<Banana> is not a List<Fruit>.

If a List<Banana> was a List<Fruit>, you would be able to add any Fruit to it, which you clearly shouldn't:

// won't compile, because List<Banana> is not a List<Fruit>
List<Fruit> fruit = (List<Fruit>) myBananaList();

// if it did compile (or if you do it with casting), this would "work"
fruit.add(new Apple()); // doesn't make sense

// but if something uses same Banana list as a Banana list
// and gets an Apple instead of a banana, it will cause a class cast exception
Banana banana = bananas.get(index);

So casting List<Banana> to List<Fruit> is always technically incorrect, and that's why Java won't let you do it directly.

Your intermediate cast to List<? extends Fruit> is a little misleading. In your case it happens to work because of the way Java generics are implemented, but it's not really any better than doing something like this:

// DON'T DO THIS!
public List<Fruit> getFruits_bad() {
    Object fruits = allBananas();
    return (List<Fruit>) fruits;
}

It compiles for the same reason the code below compiles, but this one will fail at runtime because type erasure won't apply:

// DON'T DO THIS EITHER
Object obj="a string";
Integer i=(Integer) obj;
Community
  • 1
  • 1
CupawnTae
  • 14,192
  • 3
  • 29
  • 60
3

Apart from changing the method signature to List<? extends Fruit>, you can use Collections.unmodifiableList to get a read-only List<Fruit> view of a List<Banana>:

public List<Fruit> getFruits() {
    List<Banana> bananas = getBananas();
    return Collections.unmodifiableList(bananas);
}

Both unmodifiableList and declaring the method as List<? extends Fruit> serve the same goal -- keep the client of this method from adding an orange to a list of bananas.

Misha
  • 27,433
  • 6
  • 62
  • 78
2

It really depends on what you want to achieve.

If you're happy to create a copy of the list, then I'd just use something like:

public List<Fruit> getFruits() {
    return new ArrayList<>(allBananas());
}

That's now independent of the original list, so the caller can then do what they want with it. (If they modify any of the existing bananas, those modifications will be seen of course, but that's not a change to the list itself.)

However, it sounds like you don't want to copy the list, which means you effectively want some kind of view on the existing list. The tricky bit here is doing that safely. Your casting code is not safe:

// Don't do this!
public List<Fruit> getFruits() {
    List<? extends Fruit> fruits = (List<? extends Fruit>) allBananas();
    return (List<Fruit>) fruits;
}

Here's why it's broken:

List<Banana> bananas = allBananas();
List<Fruit> fruit = getFruits();
System.out.println(bananas == fruits); // Same list
fruit.add(new Apple()); // This is fine, right?
Banana banana = bananas.get(0); // This should be fine, right?

You'll actually end up with an invalid cast exception in the last line, because you've basically violated type safety in your getFruits() method. A List<Banana> is not a List<Fruit>.

Your safe options are:

  • Change the method return type to List<? extends Fruit> at which point you don't need any casting:

    public List<? extends Fruit> getFruits() {
        return allBananas();
    }
    

    Now the caller can't add to the list (except null), but can remove items from the list.

  • Use an implementation of List<Fruit> which permits additions, but checks at execution time that each item is a Banana, e.g.

    return (List<Fruit>) Collections.checkedList(allBananas(), Banana.class);
    
  • Return an unmodifiable view of the list, e.g.

    public List<Fruit> getFruits() {
        return Collections.unmodifiableList(allBananas());
    }
    

Which option is appropriate (including copying) is very context-dependent.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • To your second safe option, using `Collections.checkedList(list, Banana.class)` would ensure at runtime that only Bananas are added to the list. The unchecked casts are still necessary, though, since the static type system doesn't know anything about the runtime checking. – Stuart Marks Jun 02 '15 at 05:45