在一行中将子列表转换为 parents 列表
Converting List of childs to List of parents in one line
正在将 Banana
的 List
转换为 Fruit
的 List
...
public class Fruit { }
public class Banana extends Fruit { }
public List<Banana> allBananas() {
return new ArrayList<>(Arrays.asList(new Banana(), new Banana()));
}
...返回之前我必须执行以下操作 two-steps-casting:
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
object,这在性能方面不是很好。
我想我不明白某些事情,也许做错了。
我想知道我的代码是否完全正确和安全?如果是这样 - 有更好的投射方式吗?如果不是 - 有什么问题可以改进吗?
你可以做到
public List<Fruit> getFruits() {
return new ArrayList<>(allBananas());
}
请注意,您的转换示例不安全,您会收到编译警告。这是因为 List<Banana>
是 而不是 List<Fruit>
.
如果 List<Banana>
是 List<Fruit>
,您可以添加 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<Banana>
转换为 List<Fruit>
在技术上总是不正确的,这就是为什么 Java 不会让你直接这样做的原因。
您对 List<? extends Fruit>
的中间转换有点误导。在你的情况下,它恰好可以工作,因为 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;
除了将方法签名更改为 List<? extends Fruit>
,您还可以使用 Collections.unmodifiableList
获取 List<Banana>
的只读 List<Fruit>
视图:
public List<Fruit> getFruits() {
List<Banana> bananas = getBananas();
return Collections.unmodifiableList(bananas);
}
unmodifiableList
和将方法声明为 List<? extends Fruit>
都服务于相同的目标——防止此方法的客户端将橙子添加到香蕉列表中。
这真的取决于你想要实现什么。
如果您愿意创建列表的副本,那么我会使用类似的东西:
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<Banana>
是 而不是 List<Fruit>
.
您的安全选项是:
将方法 return 类型更改为 List<? extends Fruit>
,此时您不需要任何转换:
public List<? extends Fruit> getFruits() {
return allBananas();
}
现在呼叫者无法添加到列表(null
除外),但可以从列表中删除项目。
使用允许添加的 List<Fruit>
的实现,但在执行时检查每个项目都是 Banana
,例如
return (List<Fruit>) Collections.checkedList(allBananas(), Banana.class);
Return 列表的不可修改视图,例如
public List<Fruit> getFruits() {
return Collections.unmodifiableList(allBananas());
}
哪个选项合适(包括复制)取决于上下文。
正在将 Banana
的 List
转换为 Fruit
的 List
...
public class Fruit { }
public class Banana extends Fruit { }
public List<Banana> allBananas() {
return new ArrayList<>(Arrays.asList(new Banana(), new Banana()));
}
...返回之前我必须执行以下操作 two-steps-casting:
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
object,这在性能方面不是很好。
我想我不明白某些事情,也许做错了。 我想知道我的代码是否完全正确和安全?如果是这样 - 有更好的投射方式吗?如果不是 - 有什么问题可以改进吗?
你可以做到
public List<Fruit> getFruits() {
return new ArrayList<>(allBananas());
}
请注意,您的转换示例不安全,您会收到编译警告。这是因为 List<Banana>
是 而不是 List<Fruit>
.
如果 List<Banana>
是 List<Fruit>
,您可以添加 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<Banana>
转换为 List<Fruit>
在技术上总是不正确的,这就是为什么 Java 不会让你直接这样做的原因。
您对 List<? extends Fruit>
的中间转换有点误导。在你的情况下,它恰好可以工作,因为 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;
除了将方法签名更改为 List<? extends Fruit>
,您还可以使用 Collections.unmodifiableList
获取 List<Banana>
的只读 List<Fruit>
视图:
public List<Fruit> getFruits() {
List<Banana> bananas = getBananas();
return Collections.unmodifiableList(bananas);
}
unmodifiableList
和将方法声明为 List<? extends Fruit>
都服务于相同的目标——防止此方法的客户端将橙子添加到香蕉列表中。
这真的取决于你想要实现什么。
如果您愿意创建列表的副本,那么我会使用类似的东西:
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<Banana>
是 而不是 List<Fruit>
.
您的安全选项是:
将方法 return 类型更改为
List<? extends Fruit>
,此时您不需要任何转换:public List<? extends Fruit> getFruits() { return allBananas(); }
现在呼叫者无法添加到列表(
null
除外),但可以从列表中删除项目。使用允许添加的
List<Fruit>
的实现,但在执行时检查每个项目都是Banana
,例如return (List<Fruit>) Collections.checkedList(allBananas(), Banana.class);
Return 列表的不可修改视图,例如
public List<Fruit> getFruits() { return Collections.unmodifiableList(allBananas()); }
哪个选项合适(包括复制)取决于上下文。