title: Limit() on Java stream with nested list not applying
body: <p>I have an List of <code>Category</code>, each of which have a List of <code>Phrase</code>s and I want to cut down this list to categories which have a minimum number of phrases, restrict the number of phrases to <em>Y</em>, then create a new object <code>CategoryAndPhrase</code> for all of the matching categories and phrases.</p>
  
  <p>This seemed like a good candidate for the Java 8 streams API but I'm not getting the results I expect. This is my code with comments next to each line explaining what I'm trying to achieve. There is a full code sample at the end of this question.</p>
  
  <pre><code>List&lt;CategoryAndPhrase&gt; categoriesAndPhrases = allCategories.stream()
              .filter(category -&gt; category.getPhrases().size() &gt;= 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 -&gt; new CategoryAndPhrase(phrase.getCategory(), phrase.getName())) // create the new object for each of the selected phrases
              .collect(Collectors.toCollection(ArrayList::new));
  </code></pre>
  
  <h1>Example</h1>
  
  <h2>Input data:</h2>
  
  <ul>
  <li>Sports teams <code>&lt;-- Category</code>
  
  <ul>
  <li>Manchester United <code>&lt;-- Phrase</code></li>
  <li>Arsenal</li>
  <li>Swansea</li>
  <li>Hartlepool United</li>
  </ul></li>
  <li>Greetings
  
  <ul>
  <li>Hello</li>
  </ul></li>
  <li>Goodbyes
  
  <ul>
  <li>Goodbye</li>
  <li>Adios</li>
  <li>Au revoir</li>
  </ul></li>
  <li>Languages
  
  <ul>
  <li>English</li>
  <li>Italian</li>
  <li>French</li>
  <li>German</li>
  </ul></li>
  </ul>
  
  <p>Number of categories: 2</p>
  
  <p>Number of phrases per category: 3</p>
  
  <h2>Expected output:</h2>
  
  <ul>
  <li>Category: [Sports Teams], Phrase: [Manchester United]</li>
  <li>Category: [Sports Teams], Phrase: [Arsenal]</li>
  <li>Category: [Sports Teams], Phrase: [Swansea]</li>
  <li>Category: [Goodbyes], Phrase: [Goodbye]</li>
  <li>Category: [Goodbyes], Phrase: [Adios]</li>
  <li>Category: [Goodbyes], Phrase: [Au revoir]</li>
  </ul>
  
  <h2>Actual output:</h2>
  
  <ul>
  <li>Category: [Sports Teams], Phrase: [Manchester United]</li>
  <li>Category: [Sports Teams], Phrase: [Arsenal]</li>
  <li>Category: [Sports Teams], Phrase: [Swansea]</li>
  <li><strong>Category: [Sports Teams], Phrase: [Hartlepool United]</strong></li>
  <li>Category: [Goodbyes], Phrase: [Goodbye]</li>
  <li>Category: [Goodbyes], Phrase: [Adios]</li>
  <li>Category: [Goodbyes], Phrase: [Au revoir]</li>
  </ul>
  
  <p>Note that the fourth Sports Team is included even though I only requested 3 phrases per category.</p>
  
  <p>The first <code>limit()</code> operation appears to work correctly as there are only two categories returned even though three match the criteria of having at least three phrases but the second one isn't working as expected.</p>
  
  <p>Where am I going wrong?</p>
  
  <h1>Full code</h1>
  
  <p>You can also find this code <a href="https://www.jdoodle.com/a/7b0" rel="nofollow noreferrer">on JDoodle</a>.</p>
  
  <pre><code>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&lt;Category&gt; allCategories = Arrays.asList(sportsTeams, greetings, goodbyes, languages);
          int numberOfCategories = 2;
          int numberOfPhrasesPerCategory = 3;
  
          List&lt;CategoryAndPhrase&gt; categoriesAndPhrases = allCategories.stream()
                  .filter(category -&gt; category.getPhrases().size() &gt;= 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 -&gt; new CategoryAndPhrase(phrase.getCategory(), phrase.getName())) // create the new object for each of the selected phrases
                  .collect(Collectors.toCollection(ArrayList::new));
  
          categoriesAndPhrases.forEach(categoryAndPhrase -&gt; System.out.println(categoryAndPhrase));
      }
  
      class Category {
  
          private final String name;
          private final List&lt;Phrase&gt; phrases;
  
          public Category(final String name, final List&lt;Phrase&gt; phrases) {
              this.name = name;
              this.phrases = phrases;
          }
  
          public String toString() {
              return name;
          }
  
          public String getName() {
              return name;
          }
  
          public List&lt;Phrase&gt; 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 + "]";
          }
      }
  }
  </code></pre>