我有一个List of Foo,在每个Foo上我应用一个处理器的方法来得到 ValidItem
. 如果在处理过程中出现了错误,那么我返回了 ErrorItem
.
现在如何处理这个由Java 8流得到的结果在2个不同的列表的形式
List<Foo> FooList = someList....;
class ValidItem extend Item{......}
class ErrorItem extend Item{......}
Item processItem(Foo foo){
return either an object of ValidItem or ErrorItem;
}
我相信我可以做到
Map<Class,List<Item>> itemsMap =
FooList
.stream()
.map(processItem)
.collect(Collectors.groupingBy(Object::getClass));
但由于 List<Parent>
是不是一个 List<Child>
所以,我不能把地图结果打造成 List<ValidItem>
在现实中 ErrorItem
和 ValidItem
是两个完全不同的类,完全没有关系,只是为了这个蒸汽处理和processItem方法,我通过扩展一个标记Item类,把它们保持在同一个层次结构中,在代码中的许多其他地方,我不能把ValidItem称为Item,因为它让人觉得它也可以是一个ErroItem。
在代码中的许多其他地方,我不应该把ValidItem称为Item,因为它让人觉得它也可以是一个ErroItem。
ErrorItem和ValidItem没有扩展同一个Item类?
正如我所说,ValidItem和ErrorItem不应该是一样的,所以我改变了过程方法的签名,并给它传递了一个列表。我知道这不是Stream应该使用的方式。如果你有更好的方法,请告诉我
List<Foo> FooList = someList....;
class ValidItem {......}
class InvalidFoo{......}
ValidItem processFoo(Foo foo, List<InvalidFoo> foolist){
Do some processing on foo.
either return new ValidItem ();
OR
fooList.add(new InvalidFoo()) , and then return null;
}
List<InvalidFoo> invalidFooList = new ArrayList();
List<ValidItem> validItem =
fooList
.stream()
.map(e->processItem(e,invalidFooList))
.filter(Objects::notNull)
.collect(Collectors.toList());
现在我有无效和有效的列表,但这看起来不像是一个干净的流代码。
随着最近的Java版本,你可以使用
对于 Item processItem(Foo foo)
方法返回的是 ValidItem
或 ErrorItem
:
Map.Entry<List<ValidItem>, List<ErrorItem>> collected = fooList.stream()
.map(this::processItem)
.collect(teeing(
flatMapping(x -> x instanceof ValidItem? Stream.of((ValidItem)x):null, toList()),
flatMapping(x -> x instanceof ErrorItem? Stream.of((ErrorItem)x):null, toList()),
AbstractMap.SimpleImmutableEntry::new
));
List<ValidItem> valid = collected.getKey();
List<ErrorItem> invalid = collected.getValue();
代表: ValidItem processFoo(Foo foo, List<InvalidFoo> foolist)
:
Map.Entry<List<ValidItem>, List<InvalidFoo>> collected = fooList.stream()
.map(foo -> {
List<InvalidFoo> invalid = new ArrayList<>(1);
ValidItem vi = processFoo(foo, invalid);
return new AbstractMap.SimpleImmutableEntry<>(
vi == null? Collections.<ValidItem>emptyList():
Collections.singletonList(vi),
invalid);
})
.collect(teeing(
flatMapping(e -> e.getKey().stream(), toList()),
flatMapping(e -> e.getValue().stream(), toList()),
AbstractMap.SimpleImmutableEntry::new
));
List<ValidItem> valid = collected.getKey();
List<InvalidFoo> invalid = collected.getValue();
负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: 负责人: flatMapping
在这个特定的情况下,在Java 9中引入了收集器,而不是用
flatMapping(x -> x instanceof ValidItem? Stream.of((ValidItem)x): null, toList())
您也可以使用
filtering(x -> x instanceof ValidItem, mapping(x -> (ValidItem)x, toList()))
但每个变体都需要Java 9,因为 filtering
在Java 8中也不存在。在Java 8中也不存在。teeing
收集器甚至需要Java 12。
然而,这些收集器并不难实现。
这个答案 包含一个Java 8兼容版本的 flatMapping
最后的收集器。如果你想用 filtering
和 mapping
,你可以找到一个Java 8兼容的版本。filtering
在 本回答. 最后: 本回答 包含一个兼容Java 8的变体 teeing
收集器。
当你把这些收集器添加到你的代码库中时,本答案开头的解决方案就可以在Java 8下工作,并将很容易适应未来的版本。假设一个 import static java.util.stream.Collectors.*;
在你的源文件中,你只需要删除这些方法的后传,就可以切换到标准的JDK版本。
如果 processItem
返回一个 Either
或 Pair
类型,而不是上面提到的两种变体。如果你不想使用第三方库,你可以使用一个叫做 Map.Entry
实例作为一个 "穷人的对子类型"。
有一个方法签名,比如
/** Returns an entry with either, key or value, being {@code null} */
Map.Entry<ValidItem,InvalidFoo> processItem(Foo foo){
可以通过返回一个 AbstractMap.SimpleImmutableEntry
,
一个JDK 9+的解决方案可能是这样的
Map.Entry<List<ValidItem>, List<InvalidFoo>> collected = fooList.stream()
.map(this::processItem)
.collect(teeing(
flatMapping(e -> Stream.ofNullable(e.getKey()), toList()),
flatMapping(e -> Stream.ofNullable(e.getValue()), toList()),
AbstractMap.SimpleImmutableEntry::new
));
和Java 8兼容(当使用链接的收集器后传)版本。
Map.Entry<List<ValidItem>, List<InvalidFoo>> collected = fooList.stream()
.map(this::processItem)
.collect(teeing(
flatMapping(e -> Stream.of(e.getKey()).filter(Objects::nonNull), toList()),
flatMapping(e -> Stream.of(e.getValue()).filter(Objects::nonNull), toList()),
AbstractMap.SimpleImmutableEntry::new
));
或
Map.Entry<List<ValidItem>, List<InvalidFoo>> collected = fooList.stream()
.map(this::processItem)
.collect(teeing(
mapping(Map.Entry::getKey, filtering(Objects::nonNull, toList())),
mapping(Map.Entry::getValue, filtering(Objects::nonNull, toList())),
AbstractMap.SimpleImmutableEntry::new
));
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.stream.Collectors;
public class Main {
public static void main(String[] args) {
List<Foo> FooList = new ArrayList<>();
for(int i = 0 ; i < 100; i++){
FooList.add(new Foo(i+""));
}
Map<Class,List<Item>> itemsMap =
FooList
.stream()
.map(Main::processItem)
.collect(Collectors.groupingBy(Object::getClass));
List<ValidItem> validItems = itemsMap.get(ValidItem.class).stream().map((o -> (ValidItem)o)).collect(Collectors.toList());
}
public static Item processItem(Foo foo){
Random random = new Random(System.currentTimeMillis());
if(Integer.parseInt(foo.name) % 2== 0){
return new ValidItem(foo.name);
}else{
return new ErrorItem(foo.name);
}
}
static class ValidItem extends Item{
public ValidItem(String name) {
super("valid: " + name);
}
}
static class ErrorItem extends Item{
public ErrorItem(String name) {
super("error: "+name);
}
}
public static class Item {
private String name;
public Item(String name) {
this.name = name;
}
}
}
我建议这个解决方案。
你可以使用Vavr库。
final List<String> list = Arrays.asList("1", ",", "1", "0");
final List<Either<ErrorItem, ValidItem>> eithers = list.stream()
.map(MainClass::processData)
.collect(Collectors.toList());
final List<ValidItem> validItems = eithers.stream()
.filter(Either::isRight)
.map(Either::get)
.collect(Collectors.toList());
final List<ErrorItem> errorItems = eithers.stream()
.filter(Either::isLeft)
.map(Either::getLeft)
.collect(Collectors.toList());
...
private static Either<ErrorItem,ValidItem> processData(String data){
if(data.equals("1")){
return Either.right(new ValidItem());
}
return Either.left(new ErrorItem());
}