【问题标题】:Converting List of childs to List of parents in one line在一行中将孩子列表转换为父母列表
【发布时间】:2015-05-23 07:57:35
【问题描述】:

ListBanana 转换为ListFruit ...

public class Fruit { }

public class Banana extends Fruit { }

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

...在返回之前我必须做以下两步铸造:

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

为了避免中间步骤,可以这样做:

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

但在这种情况下,我必须创建新的 List 对象,这在性能方面不太好。

我想我不明白某些事情,也许做错了。 我想知道我的代码是否完全正确且安全?如果是这样 - 有没有更好的投射方式?如果不是 - 有什么问题可以改进?

【问题讨论】:

  • 无论如何,您在第一个版本中的演员阵容是不安全的。从根本上说,List&lt;Banana&gt;不是List&lt;Fruit&gt;。我们不知道这里的上下文是什么 - 你应该实际上做的只是将签名更改为List&lt;? extends Fruit&gt;getFruit()`
  • @ytterrr:我看不出这个答案与我的建议有什么关系......
  • 但这里我建议List&lt;? extends Fruit&gt;,所以它仍然相当具体。从根本上说,尽管您还没有告诉我们上下文。您想要现有列表的视图,还是现有列表的副本
  • 对 - 如果有人在该列表中调用 view.add(new Apple()),您会期望发生什么?如果你提交了List&lt;? extends Fruit&gt;,他们就不能这样做,所以没关系 - 但如果你提交了List&lt;Fruit&gt;,那么他们可以,此时你要么破坏原来的List&lt;Banana&gt;,要么你必须检测它在执行时抛出一个异常......你还没有在你的问题中谈到你需要什么任何,这使得它无法回答。
  • 嗯,这只是一个评论,因为它不能成为答案——因为问题不够清楚回答。如果您更新您的问题以使其可以回答,我可以添加答案...

标签: java generics inheritance polymorphism java-8


【解决方案1】:

除了将方法签名更改为List&lt;? extends Fruit&gt;,您还可以使用Collections.unmodifiableList 获得List&lt;Banana&gt; 的只读List&lt;Fruit&gt; 视图:

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

unmodifiableList 和将方法声明为List&lt;? extends Fruit&gt; 都服务于相同的目标——阻止此方法的客户端将橙色添加到香蕉列表中。

【讨论】:

  • return Collections.unmodifiableList(allBananas()); 满足“一行”:-)
【解决方案2】:

你可以这样做

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

请注意,您的转换示例是不安全的,您会收到编译警告。这是因为List&lt;Banana&gt;不是List&lt;Fruit&gt;

如果List&lt;Banana&gt; List&lt;Fruit&gt;,你可以添加any Fruit 到它,你显然不应该这样做:

// 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);

所以将List&lt;Banana&gt; 转换为List&lt;Fruit&gt; 在技术上总是不正确的,这就是Java 不允许你直接这样做的原因。

您对List&lt;? extends Fruit&gt; 的中间转换有点误导。在您的情况下,由于the way Java generics are implemented,它恰好可以工作,但这并没有比做这样的事情更好:

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

它的编译原因与下面的代码编译的原因相同,但是这个在运行时会失败,因为类型擦除不适用:

// DON'T DO THIS EITHER
Object obj="a string";
Integer i=(Integer) obj;

【讨论】:

  • 我想你的意思是Integer i = (Integer) obj;
  • 不完全。 obj 而不是 blah。 :)。对不起:)。
  • @artaxerxe 再次感谢。我的原始代码中显然有(Integer) blah;,然后双击编辑了错误的单词,试图将blah 更改为obj。无需为提供帮助而道歉:-)
【解决方案3】:

这真的取决于你想要达到的目标。

如果您乐于创建列表的副本,那么我会使用类似的内容:

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

现在它独立于原始列表,因此调用者可以用它做他们想做的事。 (如果他们修改任何现有的香蕉,当然会看到这些修改,但这并不是对列表本身的更改。)

但是,听起来您不想复制列表,这意味着您实际上希望在现有列表上获得某种视图。这里的棘手之处在于安全地做到这一点。您的投射代码安全:

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

这就是它坏掉的原因:

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?

您实际上会在最后一行得到一个无效的强制转换异常,因为您在 getFruits() 方法中基本上违反了类型安全。 List&lt;Banana&gt;不是List&lt;Fruit&gt;

您的安全选项是:

  • 将方法返回类型更改为List&lt;? extends Fruit&gt;,此时您不需要任何转换:

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

    现在调用者无法添加到列表中(null 除外),但可以从列表中删除项目。

  • 使用List&lt;Fruit&gt; 的实现,它允许添加,但在执行时检查每个项目是否为Banana,例如

    return (List<Fruit>) Collections.checkedList(allBananas(), Banana.class);
    
  • 返回列表的不可修改视图,例如

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

哪个选项合适(包括复制)取决于上下文。

【讨论】:

  • 对于您的第二个安全选项,使用Collections.checkedList(list, Banana.class) 将确保在运行时仅将香蕉添加到列表中。不过,未经检查的强制转换仍然是必需的,因为静态类型系统对运行时检查一无所知。
猜你喜欢
  • 2014-05-19
  • 1970-01-01
  • 2015-04-13
  • 2011-09-24
  • 1970-01-01
  • 2018-05-18
  • 1970-01-01
  • 2015-12-14
  • 2018-11-12
相关资源
最近更新 更多