1. Lista över klassens Streammetoder

Klassen Streamskapades för att göra det enkelt att konstruera kedjor av dataströmmar. För att uppnå detta Stream<T>har klassen metoder som returnerar nya Streamobjekt.

Var och en av dessa dataströmmar gör en enkel åtgärd, men om du kombinerar dem till kedjor och lägger till intressanta lambda-funktioner har du en kraftfull mekanism för att generera den utdata du vill ha. Snart får du se själv.

Här är klassens metoder Stream(endast de mest grundläggande):

Metoder Beskrivning
Stream<T> of()
Skapar en ström från en uppsättning objekt
Stream<T> generate()
Genererar en ström enligt den angivna regeln
Stream<T> concat()
Sammanfogar två strömmar
Stream<T> filter()
Filtrerar data och skickar bara vidare data som matchar den angivna regeln
Stream<T> distinct()
Tar bort dubbletter. Skickar inte vidare data som redan har påträffats
Stream<T> sorted()
Sorterar data
Stream<T> peek()
Utför en åtgärd på varje element i flödet
Stream<T> limit(n)
Returnerar en ström som är trunkerad så att den inte är längre än den angivna gränsen
Stream<T> skip(n)
Hoppar över de första n elementen
Stream<R> map()
Konverterar data från en typ till en annan
Stream<R> flatMap()
Konverterar data från en typ till en annan
boolean anyMatch()
Kontrollerar om det finns minst ett element i strömmen som matchar den angivna regeln
boolean allMatch()
Kontrollerar om alla element i strömmen matchar den angivna regeln
boolean noneMatch()
Kontrollerar om inget av elementen i strömmen matchar den angivna regeln
Optional<T> findFirst()
Returnerar det första hittade elementet som matchar regeln
Optional<T> findAny()
Returnerar alla element i flödet som matchar regeln
Optional<T> min()
Söker efter minimielementet i dataströmmen
Optional<T> max()
Returnerar det maximala elementet i dataströmmen
long count()
Returnerar antalet element i dataströmmen
R collect()
Läser all data från strömmen och returnerar den som en samling

2. Mellan- och terminaloperationer av Streamklassen

Som du kan se returnerar inte alla metoder i tabellen ovan en Stream. Detta är relaterat till det faktum att klassens metoder Streamkan delas in i mellanliggande (även känd som icke-terminala ) metoder och terminalmetoder .

Mellanliggande metoder

Mellanliggande metoder returnerar ett objekt som implementerar gränssnittet, Streamoch de kan kedjas ihop.

Terminalmetoder

Terminalmetoder returnerar ett annat värde än Stream.

Metodanropspipeline

Således kan du bygga en strömpipeline som består av valfritt antal mellanliggande metoder och ett enda terminalmetodanrop i slutet. Detta tillvägagångssätt låter dig implementera ganska komplex logik, samtidigt som kodläsbarheten ökar.

Metodanropspipeline

Data inuti en dataström förändras inte alls. En kedja av mellanliggande metoder är ett smidigt (deklarativt) sätt att specificera en databehandlingspipeline som kommer att exekveras efter att terminalmetoden anropats.

Med andra ord, om terminalmetoden inte anropas, så bearbetas inte data i dataströmmen på något sätt. Först efter att terminalmetoden anropats börjar data bearbetas enligt reglerna som anges i strömpipelinen.

stream()
  .intemediateOperation1()
  .intemediateOperation2()
  ...
  .intemediateOperationN()
  .terminalOperation();
Generellt utseende av en pipeline

Jämförelse av mellanliggande och terminala metoder:

mellanliggande terminal
Returtyp Stream inte aStream
Kan kombineras med flera metoder av samma typ för att bilda en pipeline ja Nej
Antal metoder i en enda pipeline några inte mer än en
Ger slutresultatet Nej ja
Börjar bearbeta data i strömmen Nej ja

Låt oss titta på ett exempel.

Anta att vi har en klubb för djurälskare. Imorgon firar klubben Ginger Cat Day. Klubben har husdjursägare, som var och en har en lista över husdjur. De är inte begränsade till katter.

Uppgift: du måste identifiera alla namn på alla ingefärskatter för att kunna skapa personliga gratulationskort för dem för morgondagens "yrkeshelg". Gratulationskorten ska sorteras efter kattens ålder, från äldst till yngst.

Först ger vi några klasser som hjälper dig att lösa denna uppgift:

public enum Color {
   WHITE,
   BLACK,
   DARK_GREY,
   LIGHT_GREY,
   FOXY,
   GREEN,
   YELLOW,
   BLUE,
   MAGENTA
}
public abstract class Animal {
   private String name;
   private Color color;
   private int age;

   public Animal(String name, Color color, int age) {
      this.name = name;
      this.color = color;
      this.age = age;
   }

   public String getName() {
      return name;
   }

   public Color getColor() {
      return color;
   }

   public int getAge() {
      return age;
   }
}
public class Cat extends Animal {
   public Cat(String name, Color color, int age) {
      super(name, color, age);
   }
}
public class Dog extends Animal {
   public Dog(String name, Color color, int age) {
      super(name, color, age);
   }
}
public class Parrot extends Animal {
   public Parrot(String name, Color color, int age) {
      super(name, color, age);
   }
}
public class Pig extends Animal {
   public Pig(String name, Color color, int age) {
      super(name, color, age);
   }
}
public class Snake extends Animal {
   public Snake(String name, Color color, int age) {
      super(name, color, age);
   }
}
public class Owner {
   private String name;
   private List<Animal> pets = new ArrayList<>();

   public Owner(String name) {
      this.name = name;
   }

   public List<Animal> getPets() {
      return pets;
   }
}

Låt oss nu titta på Selectorklassen, där urvalet kommer att göras enligt de angivna kriterierna:

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class Selector {
   private static List<Owner> owners;

   private static void initData() {
      final Owner owner1 = new Owner("Ronan Turner");
      owner1.getPets().addAll(List.of(
            new Cat("Baron", Color.BLACK, 3),
            new Cat("Sultan", Color.DARK_GREY, 4),
            new Dog("Elsa", Color.WHITE, 0)
      ));

      final Owner owner2 = new Owner("Scarlet Murray");
      owner2.getPets().addAll(List.of(
            new Cat("Ginger", Color.FOXY, 7),
            new Cat("Oscar", Color.FOXY, 5),
            new Parrot("Admiral", Color.BLUE, 3)
      ));

      final Owner owner3 = new Owner("Felicity Mason");
      owner3.getPets().addAll(List.of(
            new Dog("Arnold", Color.FOXY, 3),
            new Pig("Vacuum Cleaner", Color.LIGHT_GREY, 8)
      ));

      final Owner owner4 = new Owner("Mitchell Stone");
      owner4.getPets().addAll(List.of(
            new Snake("Mr. Boa", Color.DARK_GREY, 2)
      ));

      final Owner owner5 = new Owner("Jonathan Snyder");
      owner5.getPets().addAll(List.of(
            new Cat("Fisher", Color.BLACK, 16),
            new Cat("Zorro", Color.FOXY, 14),
            new Cat("Margo", Color.WHITE, 3),
            new Cat("Brawler", Color.DARK_GREY, 1)
      ));

      owners = List.of(owner1, owner2, owner3, owner4, owner5);
   }
}

Det som återstår är att lägga till kod till mainmetoden. För närvarande kallar vi först initData()metoden, som fyller listan över djurägare i klubben. Sedan väljer vi namnen på ingefärskatter sorterade efter deras ålder i fallande ordning.

Låt oss först titta på kod som inte använder strömmar för att lösa denna uppgift:

public static void main(String[] args) {
   initData();

   List<String> findNames = new ArrayList<>();
   List<Cat> findCats = new ArrayList<>();
   for (Owner owner : owners) {
      for (Animal pet : owner.getPets()) {
         if (Cat.class.equals(pet.getClass()) && Color.FOXY == pet.getColor()) {
            findCats.add((Cat) pet);
         }
      }
   }

   Collections.sort(findCats, new Comparator<Cat>() {
      public int compare(Cat o1, Cat o2) {
         return o2.getAge() - o1.getAge();
      }
   });

   for (Cat cat : findCats) {
      findNames.add(cat.getName());
   }

   findNames.forEach(System.out::println);
}

Låt oss nu titta på ett alternativ:

public static void main(String[] args) {
   initData();

   final List<String> findNames = owners.stream()
           .flatMap(owner -> owner.getPets().stream())
           .filter(pet -> Cat.class.equals(pet.getClass()))
           .filter(cat -> Color.FOXY == cat.getColor())
           .sorted((o1, o2) -> o2.getAge() - o1.getAge())
           .map(Animal::getName)
           .collect(Collectors.toList());

   findNames.forEach(System.out::println);
}

Som du kan se är koden mycket mer kompakt. Dessutom är varje rad i strömledningen en enda åtgärd, så de kan läsas som meningar på engelska:

.flatMap(owner -> owner.getPets().stream())
Gå från a Stream<Owner>till aStream<Pet>
.filter(pet -> Cat.class.equals(pet.getClass()))
Behåll endast katter i dataströmmen
.filter(cat -> Color.FOXY == cat.getColor())
Behåll endast ingefärskatter i dataströmmen
.sorted((o1, o2) -> o2.getAge() - o1.getAge())
Sortera efter ålder i fallande ordning
.map(Animal::getName)
Få namnen
.collect(Collectors.toList())
Lägg resultatet i en lista

3. Skapa strömmar

Klassen Streamhar tre metoder som vi inte har täckt ännu. Syftet med dessa tre metoder är att skapa nya trådar.

Stream<T>.of(T obj)metod

Metoden of()skapar en ström som består av ett enda element. Detta behövs vanligtvis när, säg, en funktion tar ett Stream<T>objekt som ett argument, men du bara har ett Tobjekt. Då kan du enkelt och enkelt använda of()metoden för att få en stream som består av ett enda element .

Exempel:

Stream<Integer> stream = Stream.of(1);

Stream<T> Stream.of(T obj1, T obj2, T obj3, ...)metod

Metoden of()skapar en ström som består av passerade element . Vilket antal element som helst är tillåtna. Exempel:

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);

Stream<T> Stream.generate(Supplier<T> obj)metod

Metoden generate()låter dig ställa in en regel som kommer att användas för att generera nästa element i strömmen när den efterfrågas. Du kan till exempel ge ut ett slumpmässigt tal varje gång.

Exempel:

Stream<Double> s = Stream.generate(Math::random);

Stream<T> Stream.concat(Stream<T> a, Stream<T> b)metod

Metoden concat()sammanfogar de två passerade strömmarna till en . När data läses läses den först från den första strömmen och sedan från den andra. Exempel:

Stream<Integer> stream1 = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> stream2 = Stream.of(10, 11, 12, 13, 14);
Stream<Integer> result = Stream.concat(stream1, stream2);

4. Filtrera data

Ytterligare 6 metoder skapar nya dataströmmar, som låter dig kombinera strömmar till kedjor (eller pipelines) av varierande komplexitet.

Stream<T> filter(Predicate<T>)metod

Den här metoden returnerar en ny dataström som filtrerar källdataströmmen enligt den godkända regeln . Metoden måste anropas på ett objekt vars typ är Stream<T>.

Du kan specificera filtreringsregeln med en lambda-funktion , som kompilatorn sedan konverterar till ett Predicate<T>objekt.

Exempel:

Fastkedjade bäckar Förklaring
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5);
Stream<Integer> stream2 = stream.filter(x -> (x < 3));

Behåll endast nummer mindre än tre
Stream<Integer> stream = Stream.of(1, -2, 3, -4, 5);
Stream<Integer> stream2 = stream.filter(x -> (x > 0));

Behåll endast siffror större än noll

Stream<T> sorted(Comparator<T>)metod

Den här metoden returnerar en ny dataström som sorterar data i källströmmen . Du skickar in en komparator , som anger reglerna för att jämföra två element i dataströmmen.

Stream<T> distinct()metod

Den här metoden returnerar en ny dataström som endast innehåller de unika elementen i källdataströmmen . Alla dubbletter av data kasseras. Exempel:

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 2, 2, 2, 3, 4);
Stream<Integer> stream2 = stream.distinct(); // 1, 2, 3, 4, 5

Stream<T> peek(Consumer<T>)metod

Den här metoden returnerar en ny dataström , även om data i den är densamma som i källströmmen. Men när nästa element begärs från strömmen, anropas funktionen du skickade till peek()metoden med den.

Om du skickar funktionen System.out::printlntill peek()metoden kommer alla objekt att visas när de passerar genom strömmen.

Stream<T> limit(int n)metod

Den här metoden returnerar en ny dataström som endastn innehåller de första elementen i källdataströmmen . Alla andra uppgifter kasseras. Exempel:

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 2, 2, 2, 3, 4);
Stream<Integer> stream2 = stream.limit(3); // 1, 2, 3

Stream<T> skip(int n)metod

Den här metoden returnerar en ny dataström som innehåller alla samma element som källströmmen , men hoppar över (ignorerar) de första nelementen. Exempel:

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 2, 2, 2, 3, 4);
Stream<Integer> stream2 = stream.skip(3); // 4, 5, 2, 2, 2, 3, 4