如何使用 Java 8 流和过滤器过滤嵌套循环?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/38063533/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-03 03:04:58  来源:igfitidea点击:

How do you filter nested loops using Java 8 streams and filters?

javalambdafilterjava-8java-stream

提问by JavaFan

How do you filter nested loops using java8 streams and filters?

如何使用 java8 流和过滤器过滤嵌套循环?

Suppose I have a list of cars (List<Car>), each car having a list of Engines (List<Engine>), each engine having a List<Parts>. In regular Java this structure can be described as:

假设我有一个汽车列表 ( List<Car>),每辆车都有一个引擎列表 ( List<Engine>),每个引擎都有一个List<Parts>. 在常规 Java 中,这种结构可以描述为:

for(Car car : cars) {
    for (Engine engine : car.getEngines()) {
        for (Part part : engine.getParts()) {
            // ...
        }
    }
}

Suppose I initialise the list of cars as:

假设我将汽车列表初始化为:

List<Car> cars =  new ArrayList<Car>(Arrays.asList(new Car(), new Car(), new Car()));
cars.get(0).setEngines(null);
cars.get(1).setEngines(new ArrayList<Engine>());
cars.get(2).setEngines(new ArrayList<Engine>() {{
    add(new Engine());
    add(null);
    add(new Engine());  
}});

If I want to filter out nulls of List<Engine>, then I would do

如果我想过滤掉 的空值List<Engine>,那么我会这样做

cars.stream().filter(p -> p.getEngines() != null).forEach(System.out::println);

If I want to filter out empty arraylists of List, then I would do

如果我想过滤掉 List 的空数组列表,那么我会这样做

cars.stream().filter(p -> !p.getEngines().isEmpty()).forEach(System.out::println);

But how do I remove the null Enginein a 3rd car and yet keep two other engines attached to the original list structure? In other words, can we go into the 2nd, 3rd, nth level of hierarchy with Java 8 filters or do filters only work on the top-most layer? I also tried to use .anyMatch(), without much luck.

但是如何删除Engine第三辆车中的空值,同时将另外两个引擎连接到原始列表结构?换句话说,我们可以使用 Java 8 过滤器进入层次结构的第 2、3、n 层还是过滤器只在最顶层工作?我也尝试使用.anyMatch(),但运气不佳。

just to further clarify, consider the following example: I have 3 cars in my garage. Each car has 3 placeholders for engine. Each engine has 3 placeholders for parts that make up the engine:

为了进一步澄清,请考虑以下示例:我的车库里有 3 辆车。每辆车都有 3 个发动机占位符。每个引擎都有 3 个用于组成引擎的部件的占位符:

Car #1: 
 Engine#1: part1, part2, part3
 Engine#2: null, part2, empty
 Engine#3: part1, null, part3
Car #2: 
 Engine#1: part1, part2, part3
 empty:    null, null, null
 null:     null, null, null
Car #3: 
 Engine#1: null, empty, part3
 Engine#2: null, part2, empty
 Engine#3: part1, null, part3

Question: how do we use Java 8 .filter, such that when after filtering I get the following:

问题:我们如何使用 Java 8 .filter,以便在过滤后我得到以下信息:

Car #1: 
 Engine#1: part1, part2, part3
 Engine#2: part2, 
 Engine#3: part1, part3
Car #2: 
 Engine#1: part1, part2, part3
Car #1: 
 Engine#1: part3
 Engine#2: part2,
 Engine#3: part1,part3

=======================

========================

Another example

另一个例子

Guys I hope this example that I just made up is clearer:.. Essentially it is the same as above only it is more verbose and instead of cars we can think of banks to minimize abstraction. For conciseness I make all fields public, I hope you don't mind.

伙计们,我希望我刚刚编写的这个例子更清晰:...本质上与上面相同,只是它更冗长,我们可以考虑银行而不是汽车,以尽量减少抽象。为简洁起见,我将所有字段都公开,希望您不要介意。

Suppose I am affiliated with 4 banks in my 'bank wallet'
Bank#1:
I physically bank here. I am forced to have 3 accounts, but only are 2 filled with some cash and 3rd is yet to opened (ie null)
Bank #2:
I plan to bank here. Account support structure is created (empty ArrayList), but no accounts are added
Bank #3:
I filled out some marketing form. They have me in their CRM but no accounts will ever be opened
Bank #4:
This bank burned down, there is an artifact placeholder in the wallet, which is null.

假设我在我的“银行钱包”
银行#1 中隶属于 4 家银行:
我在这里进行实际银行业务。我被迫拥有 3 个账户,但只有 2 个账户装满了一些现金,而第 3 个尚未开设(即空)
银行 #2:
我打算在这里开银行。帐户支持结构已创建(空 ArrayList),但未添加帐户
银行 #3:
我填写了一些营销表格。他们的 CRM 中有我,但永远不会开设任何账户
银行 #4:
这家银行被烧毁,钱包中有一个工件占位符,它是空的。

The following code describes this:

以下代码对此进行了描述:

public class Bank_Wallet {

    public static void main(String[] args) {
        List<Bank> banks = new ArrayList<Bank>(Arrays.asList(new Bank(), new Bank(), new Bank(), null));
        // 1st bank with physical accounts, but one of them is null
        banks.get(0).accounts = Arrays.asList(new Account(), null, new Account());
        // 2nd bank with empty accounts  
        banks.get(1).accounts = new ArrayList<Account>();

        System.out.println("RAW original");
        banks.stream().forEach(System.out::println);

        System.out.println("\nFiltered result...   ");
        banks.stream()// get stream
                .filter(p -> p != null) // get rid of null banks
                .filter(p -> p.accounts != null) // get rid of null accounts
                .filter(p -> !p.accounts.isEmpty()) // get rid of empty accounts
                // .filter(p->p.accounts. ?????? ??) ?? how do I remove null account from the remaining bank entry?
                .forEach(System.out::println);

    }// main
}


The support classes are here:


支持类在这里:

public class Bank {
    public String name;
    public static int counter = 0;
    public List<Account> accounts;

    public Bank() {
        this.name = "Bank: #" + Bank.counter++;
    }

    @Override
    public String toString() {
        return "Bank [name=" + this.name + ", accounts=" + this.accounts + "]";
    }

public class Account {
    public String name;
    public static int counter;

    public Account() {
        this.name = "Account: " + Account.counter++;
    }

    @Override
    public String toString() {
        return "Account [name=" + this.name + "]";
    }

}

when you run this code you will see that after suggested filtering all I am left with is

当您运行此代码时,您会看到在建议过滤后,我剩下的就是

Bank [name=Bank: #0, accounts=[Account [name=Account: 0], null, Account [name=Account: 1]]]

Question: What other filter do I need do add to the code to get the above result not show null in the accounts and yet retain the overall structure (Bank->Account->etc->etc)

问题:我需要在代码中添加什么其他过滤器才能使上述结果在帐户中不显示为空,但仍保留整体结构(银行-> 帐户-> 等-> 等)

Bank [name=Bank: #0, accounts=[Account [name=Account: 0], Account [name=Account: 1]]]

回答by Andreas

The stream equivalent of

流相当于

for(Car car : cars) {
    for (Engine engine : car.getEngines()) {
        for (Part part : engine.getParts()) {
            // ...
        }
    }
}

is

cars.stream()
    .flatMap(car -> car.getEngines().stream())
    .flatMap(engine -> engine.getParts().stream())
    .forEach(part -> { /* ... */ });

The ...code will however not have access to carand engine.

...但是,该代码将无法访问carengine

To check for null, you can check in two places:

要检查空值,您可以在两个地方进行检查:

cars.stream()
    .flatMap(car -> car.getEngines().stream())
    .filter(engine -> engine != null)
    .flatMap(engine -> engine.getParts().stream())
    .forEach(part -> { /* ... */ });

or

或者

cars.stream()
    .flatMap(car -> car.getEngines()
                       .stream()
                       .filter(engine -> engine != null))
    .flatMap(engine -> engine.getParts().stream())
    .forEach(part -> { /* ... */ });

回答by Dici

And why do you not simply write this ?

你为什么不简单地写这个?

cars.stream()
    .filter(car -> notEmpty(car.getEngines()))
    .filter(car -> car.getEngines().stream().allMatch(engine -> notEmpty(engine.getParts())))
    .forEach(System.out::println);

public static <T> boolean notEmpty(Collection<T> collection) {
   return collection != null && !collection.isEmpty();
}

回答by Chota Bheem

How about following?

跟风怎么样?

    List<Car> cars = new ArrayList<Car>(Arrays.asList(new Car("C0"), new Car("C1"), new Car("C2")));
    cars.get(0).setEngines(new ArrayList<Engine>());
    cars.get(1).setEngines(new ArrayList<Engine>());
    cars.get(2).setEngines(Arrays.asList(new Engine("C2E1"), new Engine("C2E2"), null));

    cars.stream().filter(c -> Objects.nonNull(c.getEngines())).forEach(c -> {
        System.out.printf("Car %s ", c);
        c.getEngines().stream().filter(e -> Objects.nonNull(e) && Objects.nonNull(e.getParts())).forEach(e -> {
            System.out.printf(" Engine %s ", e);
            e.getParts().stream().filter(p -> Objects.nonNull(p))
                    .forEach(p -> System.out.printf("Part %s", p));
        });
        System.out.println();
    });

Produces following :

产生以下:

Car C0

汽车C0

Car C1

C1车

Car C2 Engine C2E1 Part DefaultPart Engine C2E2 Part DefaultPart

汽车 C2 发动机 C2E1 零件默认零件 发动机 C2E2 零件默认零件

Have overriden "toString" for Car/Engine/Part classes.

已覆盖 Car/Engine/Part 类的“toString”。

Hope this helps.

希望这可以帮助。

回答by HADDAR

you can use removeIf()like following:

你可以使用removeIf()如下:

 cars.removeIf(car -> car.getEngines() == null);

 cars.forEach(c->System.out.println("Car :"+c);