Java流上的Limit(),嵌套列表不适用

时间:2017-09-14 17:16:12

标签: java java-stream

我有一个Category列表,每个列表都有一个Phrase列表,我想将此列表缩减为具有最少短语数的类别,限制短语的数量到 Y ,然后为所有匹配的类别和短语创建一个新对象CategoryAndPhrase

这似乎是Java 8流API的一个很好的候选者,但我没有得到我期望的结果。这是我的代码,每行旁边都有注释,解释了我想要实现的目标。在这个问题的最后有一个完整的代码示例。

List<CategoryAndPhrase> categoriesAndPhrases = allCategories.stream()
            .filter(category -> category.getPhrases().size() >= numberOfPhrasesPerCategory) // remove the categories which don't have enough phrases to match our criteria
            .limit(numberOfCategories) // reduce the list to the number categories we require
            .map(Category::getPhrases) // change the stream so it is now a stream of the phrases for the selected categories
            .limit(numberOfPhrasesPerCategory) // reduce the phrases for each of the selected categories to the number of phrases we require
            .flatMap(List::stream)
            .map(phrase -> new CategoryAndPhrase(phrase.getCategory(), phrase.getName())) // create the new object for each of the selected phrases
            .collect(Collectors.toCollection(ArrayList::new));

实施例

输入数据:

  • 运动队<-- Category
    • 曼联<-- Phrase
    • 阿森纳
    • 斯旺西
    • Hartlepool United
  • 问候
    • 你好
  • 道别
    • 再见
    • 阿迪奥斯
    • Au revoir
  • 语言
    • 意大利
    • 德国

分类数量:2

每个类别的词组数量:3

预期输出:

  • 类别:[运动队],短语:[曼联]
  • 类别:[运动队],短语:[阿森纳]
  • 类别:[运动队],短语:[斯旺西]
  • 类别:[再见],短语:[再见]
  • 类别:[再见],短语:[Adios]
  • 类别:[再见],短语:[Au revoir]

实际输出:

  • 类别:[运动队],短语:[曼联]
  • 类别:[运动队],短语:[阿森纳]
  • 类别:[运动队],短语:[斯旺西]
  • 类别:[运动队],短语:[Hartlepool United]
  • 类别:[再见],短语:[再见]
  • 类别:[再见],短语:[Adios]
  • 类别:[再见],短语:[Au revoir]

请注意,即使我每个类别只要求3个短语,也会包含第四个运动队。

第一个limit()操作似乎正常工作,因为只返回了两个类别,即使三个匹配至少有三个短语但第二个没有按预期工作的标准。

我哪里错了?

完整代码

您还可以找到此代码on JDoodle

import java.util.List;
import java.util.Arrays;
import java.util.stream.Collectors;
import java.util.LinkedList;
import java.util.ArrayList;

public class MyClass {
    public static void main(String args[]) {
        new MyClass().run();
    }

    public void run() {
        Phrase sportsTeam1 = new Phrase("Manchester United", "Sports Teams");
        Phrase sportsTeam2 = new Phrase("Arsenal", "Sports Teams");
        Phrase sportsTeam3 = new Phrase("Swansea", "Sports Teams");
        Phrase sportsTeam4 = new Phrase("Hartlepool United", "Sports Teams");
        Category sportsTeams = new Category("Sports Teams", Arrays.asList(sportsTeam1, sportsTeam2, sportsTeam3, sportsTeam4));

        Phrase greeting1 = new Phrase("Hello", "Greetings");
        Category greetings = new Category("Greetings", Arrays.asList(greeting1));

        Phrase goodbye1 = new Phrase("Goodbye", "Goodbyes");
        Phrase goodbye2 = new Phrase("Adios", "Goodbyes");
        Phrase goodbye3 = new Phrase("Au revoir", "Goodbyes");
        Category goodbyes = new Category("Goodbyes", Arrays.asList(goodbye1, goodbye2, goodbye3));

        Phrase language1 = new Phrase("English", "Languages");
        Phrase language2 = new Phrase("Italian", "Languages");
        Phrase language3 = new Phrase("French", "Languages");
        Phrase language4 = new Phrase("German", "Languages");
        Category languages = new Category("Languages", Arrays.asList(language1, language2, language3, language4));

        List<Category> allCategories = Arrays.asList(sportsTeams, greetings, goodbyes, languages);
        int numberOfCategories = 2;
        int numberOfPhrasesPerCategory = 3;

        List<CategoryAndPhrase> categoriesAndPhrases = allCategories.stream()
                .filter(category -> category.getPhrases().size() >= numberOfPhrasesPerCategory) // remove the categories which don't have enough phrases to match our criteria
                .limit(numberOfCategories) // reduce the list to the number categories we require
                .map(Category::getPhrases) // change the stream so it is now a stream of the phrases for the selected categories
                .limit(numberOfPhrasesPerCategory) // reduce the phrases for each of the selected categories to the number of phrases we require
                .flatMap(List::stream)
                .map(phrase -> new CategoryAndPhrase(phrase.getCategory(), phrase.getName())) // create the new object for each of the selected phrases
                .collect(Collectors.toCollection(ArrayList::new));

        categoriesAndPhrases.forEach(categoryAndPhrase -> System.out.println(categoryAndPhrase));
    }

    class Category {

        private final String name;
        private final List<Phrase> phrases;

        public Category(final String name, final List<Phrase> phrases) {
            this.name = name;
            this.phrases = phrases;
        }

        public String toString() {
            return name;
        }

        public String getName() {
            return name;
        }

        public List<Phrase> getPhrases() {
            return phrases;
        }
    }

    class Phrase {

        private final String name;
        private final String category;

        public Phrase(final String name, final String category) {
            this.name = name;
            this.category = category;
        }

        public String toString() {
            return name;
        }

        public String getName() {
            return name;
        }

        public String getCategory() {
            return category;
        }
    }

    class CategoryAndPhrase {

        private final String category;
        private final String phrase;

        public CategoryAndPhrase(final String category, final String phrase) {
            this.category = category;
            this.phrase = phrase;
        }

        public String toString() {
            return "Category: [" + category + "], Phrase: [" + phrase + "]";
        }
    }
}

2 个答案:

答案 0 :(得分:3)

limit()在看到给定数量的元素后停止处理流元素。

以下是您的代码实际执行的操作:

List<CategoryAndPhrase> categoriesAndPhrases = allCategories.stream()
        .filter(category -> category.getPhrases().size() >= numberOfPhrasesPerCategory) // remove the categories which don't have enough phrases to match our criteria
        .limit(numberOfCategories) // reduce the list to the number categories we require
        .map(Category::getPhrases) // transforms the Stream<Category> into a Stream<List<Phrase>>, where each category of the original stream is "replaced" by its list of phrases.
        .limit(numberOfPhrasesPerCategory) // only process N lists among all the lists of phrases

你应该做的是:

List<CategoryAndPhrase> categoriesAndPhrases = allCategories.stream()
        .filter(category -> category.getPhrases().size() >= numberOfPhrasesPerCategory) // remove the categories which don't have enough phrases to match our criteria
        .limit(numberOfCategories) // reduce the list to the number categories we require
        .flatMap(category -> this.createCategoryAndPhrases(category, numberOfPhrasesPerCategory))
        .collect(Collectors.toList());

createCategoryAndPhrases方法应如下所示:

private Stream<CategoryAndPhrase> createCategoryAndPhrases(Category category, int maxNumberOfPhrasesPerCategory) {
    return category.getPhrases().stream()
        .limit(maxNumberOfPhrasesPerCategory)
        .map(phrase -> new CategoryAndPhrase(category, phrase.getName()));
}

答案 1 :(得分:2)

在您的代码中,您基本上将限制应用于原始流两次。而不是这里

.flatMap(List::stream)

你应该在其上返回调用limit的流:

.flatMap(d -> d.stream().limit(numberOfPhrasesPerCategory))