我有一个 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 的想法。
有没有一种正确的方法来使用流来做到这一点,最后我得到了 2 个列表。 和错误项和 有效项未扩展相同的项类?
#### 更新########
我知道这不是应该使用流的方式。让我知道你是否有更好的方法
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 版本,您可以使用
-
对于返回
ValidItem
或ErrorItem
的Item processItem(Foo foo)
方法: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,因为Java 8中也不存在filtering
。teeing
收集器甚至需要Java 12。
但是,这些收集器并不难实现。
这个答案在末尾包含一个 Java 8 兼容版本的flatMapping
收集器。如果您想将替代方案与filtering
和mapping
一起使用,您可以在此答案中找到 Java 8 兼容版本的filtering
。最后,这个答案包含teeing
收集器的 Java 8 兼容变体。
当您将这些收集器添加到代码库时,本答案开头的解决方案在 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());
}