Sök…


Introduktion

En Stream representerar en sekvens av element och stöder olika slags operationer för att utföra beräkningar på dessa element. Med Java 8 har Collection gränssnittet två metoder för att generera en Stream : stream() och parallelStream() . Stream är antingen mellanliggande eller terminal. Mellanoperationer returnerar en Stream så att flera mellanoperationer kan kedjas innan Stream stängs. Terminaloperationer är antingen ogiltiga eller returnerar ett icke-strömresultat.

Syntax

  • collection.stream ()
  • Arrays.stream (matris)
  • Stream.iterate (firstValue, currentValue -> nextValue)
  • Stream.generate (() -> värde)
  • Stream.of (elementOfT [, elementOfT, ...])
  • Stream.empty ()
  • StreamSupport.stream (iterable.spliterator (), false)

Använda strömmar

En Stream är en sekvens av element på vilka sekventiella och parallella aggregerade operationer kan utföras. Varje given Stream kan potentiellt ha en obegränsad mängd data som strömmar genom den. Som ett resultat behandlas data som mottas från en Stream individuellt när de anländer, i motsats till att utföra batchbehandling på data helt. I kombination med lambda-uttryck ger de ett kortfattat sätt att utföra operationer på datasekvenser med en funktionell strategi.

Exempel: ( se det fungerar på Ideone )

Stream<String> fruitStream = Stream.of("apple", "banana", "pear", "kiwi", "orange");

fruitStream.filter(s -> s.contains("a"))
           .map(String::toUpperCase)
           .sorted()
           .forEach(System.out::println);

Produktion:

ÄPPLE
BANAN
ORANGE
PÄRON

Operationerna som utförs med ovanstående kod kan sammanfattas enligt följande:

  1. Skapa en Stream<String> innehåller en sekvensbeställd Stream av String med den statiska fabriksmetoden Stream.of(values) .

  2. filter() behåller endast element som matchar ett givet predikat (elementen som testas av predikatet returnerar sant). I det här fallet behåller det elementen som innehåller ett "a" . Predikatet ges som ett lambda-uttryck .

  3. map() -operationen omvandlar varje element med en given funktion, kallad en mapper. I detta fall, varje frukt String avbildas till dess versaler String version med metoden-referens String::toUppercase .

    Observera att map() -operationen kommer att returnera en ström med en annan generisk typ om mappningsfunktionen returnerar en typ som skiljer sig från sin ingångsparameter. Till exempel på en Stream<String> .map(String::isEmpty) returnerar en Stream<Boolean>

  4. Den sorted() operationen sorterar elementen i Stream enligt deras naturliga ordning (leksikografiskt, i fallet med String ).

  5. Slutligen forEach(action) en åtgärd som verkar på varje element i Stream och överför den till en konsument . I exemplet skrivs varje element helt enkelt ut på konsolen. Denna operation är en terminaloperation, vilket gör det omöjligt att arbeta på den igen.

    Observera att operationer definierade på Stream utförs på grund av terminaloperationen. Utan en terminaloperation bearbetas inte strömmen. Strömmar kan inte återanvändas. När en terminaloperation har kallats, blir Stream objektet oanvändbart.

Kedjade verksamheter

Verksamheten (som ses ovan) är kedjade ihop för att bilda vad som kan ses som en fråga på data.


Stäng strömmar

Observera att en Stream allmänhet inte behöver stängas. Det krävs endast att stänga strömmar som fungerar på IO-kanaler. De flesta Stream typer fungerar inte på resurser och därför inte kräver stängning.

Stream gränssnittet utökar AutoCloseable . Strömmar kan stängas genom att ringa close eller genom att använda försök med resursuttalanden.

Ett exempel på ett användningsfall där en Stream bör stängas är när du skapar en Stream av rader från en fil:

try (Stream<String> lines = Files.lines(Paths.get("somePath"))) {
    lines.forEach(System.out::println);
}

Stream gränssnittet förklarar också Stream.onClose() som låter dig registrera Runnable hanterare som kommer att Runnable när strömmen är stängd. Ett exempel på användningsfall är där kod som producerar en ström måste veta när den konsumeras för att utföra någon sanering.

public Stream<String>streamAndDelete(Path path) throws IOException {
    return Files.lines(path).onClose(() -> someClass.deletePath(path));
}

Körhanteraren körs endast om metoden close() kallas, antingen uttryckligen eller implicit av ett försök med resurser.


Behandlar order

En Stream objektets bearbetning kan vara sekventiell eller parallell .

I en sekventiell mod, är elementen behandlas i den ordning av källan av den Stream . Om Stream beställs (t.ex. en SortedMap implementering eller en List ) garanteras bearbetningen matchning av källans beställning. I andra fall bör man dock vara försiktig så att man inte beror på beställningen (se: är Java HashMap keySet() iterationsordning beständig? ).

Exempel:

List<Integer> integerList = Arrays.asList(0, 1, 2, 3, 42); 

// sequential 
long howManyOddNumbers = integerList.stream()
                                    .filter(e -> (e % 2) == 1)
                                    .count(); 

System.out.println(howManyOddNumbers); // Output: 2

Live på Ideone

Parallellt läge tillåter användning av flera trådar på flera kärnor men det finns ingen garanti för ordningen i vilken element behandlas.

Om flera metoder anropas i en sekventiell Stream måste inte alla metoder åberopas. Till exempel, om en Stream filtreras och antalet element reduceras till ett, kommer ett efterföljande samtal till en metod som sort inte att inträffa. Detta kan öka prestandan för en sekvensiell Stream - en optimering som inte är möjlig med en parallell Stream .

Exempel:

// parallel
long howManyOddNumbersParallel = integerList.parallelStream()
                                            .filter(e -> (e % 2) == 1)
                                            .count();

System.out.println(howManyOddNumbersParallel); // Output: 2

Live på Ideone


Skillnader från behållare (eller samlingar )

Medan vissa åtgärder kan utföras på både behållare och strömmar, tjänar de i slutändan olika syften och stöder olika operationer. Behållare är mer fokuserade på hur elementen lagras och hur dessa element kan nås effektivt. En Stream å andra sidan ger inte direkt tillgång och manipulation till dess element; det är mer dedikerat till gruppen av objekt som en kollektiv enhet och utför operationer på den enheten som helhet. Stream och Collection är separata abstraktioner på hög nivå för dessa olika syften.

Samla element i en ström i en samling

Samla med toList() och toSet()

Element från en Stream kan enkelt samlas in i en behållare med hjälp av Stream.collect :

System.out.println(Arrays
    .asList("apple", "banana", "pear", "kiwi", "orange")
    .stream()
    .filter(s -> s.contains("a"))
    .collect(Collectors.toList())
);
// prints: [apple, banana, pear, orange]

Andra samlingsinstanser, som en Set , kan göras med hjälp av andra inbyggda metoder för Collectors . Till exempel samlar Collectors.toSet() elementen i en Stream i en Set .


Explicit kontroll över implementeringen av List eller Set

Enligt dokumentation av Collectors#toList() och Collectors#toSet() finns det inga garantier för typ, mutabilitet, serierbarhet eller gängsäkerhet för List eller Set returneras.

För uttrycklig kontroll över implementeringen som ska returneras kan Collectors#toCollection(Supplier) användas istället, där den givna leverantören returnerar en ny och tom samling.

// syntax with method reference
System.out.println(strings
        .stream()
        .filter(s -> s != null && s.length() <= 3)
        .collect(Collectors.toCollection(ArrayList::new))
);

// syntax with lambda
System.out.println(strings
        .stream()
        .filter(s -> s != null && s.length() <= 3)
        .collect(Collectors.toCollection(() -> new LinkedHashSet<>()))
);

Samla element med toMap

Collector samlar element i en karta, där nyckeln är student-id och värde är studentvärde.

  List<Student> students = new ArrayList<Student>(); 
    students.add(new Student(1,"test1"));
    students.add(new Student(2,"test2"));
    students.add(new Student(3,"test3"));
    
    Map<Integer, String> IdToName = students.stream()
        .collect(Collectors.toMap(Student::getId, Student::getName));
    System.out.println(IdToName);

Utgång:

{1=test1, 2=test2, 3=test3}

Collectors.toMap har en annan implementering Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends U> valueMapper, BinaryOperator<U> mergeFunction) . MergeFunction används oftast för att välja antingen nytt värde eller behålla gammalt värde om tangenten upprepas när du lägger till en ny medlem i kartan från en lista.

MergeFunction ser ofta ut: (s1, s2) -> s1 att behålla värde som motsvarar den upprepade tangenten, eller (s1, s2) -> s2 att sätta nytt värde för den upprepade tangenten.

Samla element till karta över samlingar

Exempel: från ArrayList till karta <String, List <>>

Ofta kräver det att göra en karta över listan ur en primärlista. Exempel: Från en studentlista måste vi göra en karta över listan med ämnen för varje student.

    List<Student> list = new ArrayList<>();
    list.add(new Student("Davis", SUBJECT.MATH, 35.0));
    list.add(new Student("Davis", SUBJECT.SCIENCE, 12.9));
    list.add(new Student("Davis", SUBJECT.GEOGRAPHY, 37.0));

    list.add(new Student("Sascha", SUBJECT.ENGLISH, 85.0));
    list.add(new Student("Sascha", SUBJECT.MATH, 80.0));
    list.add(new Student("Sascha", SUBJECT.SCIENCE, 12.0));
    list.add(new Student("Sascha", SUBJECT.LITERATURE, 50.0));

    list.add(new Student("Robert", SUBJECT.LITERATURE, 12.0));

    Map<String, List<SUBJECT>> map = new HashMap<>();
    list.stream().forEach(s -> {
                map.computeIfAbsent(s.getName(), x -> new ArrayList<>()).add(s.getSubject());
            });
    System.out.println(map);

Produktion:

{ Robert=[LITERATURE], 
Sascha=[ENGLISH, MATH, SCIENCE, LITERATURE], 
Davis=[MATH, SCIENCE, GEOGRAPHY] }

Exempel: från ArrayList till karta <String, Map <>>

    List<Student> list = new ArrayList<>();
    list.add(new Student("Davis", SUBJECT.MATH, 1, 35.0));
    list.add(new Student("Davis", SUBJECT.SCIENCE, 2, 12.9));
    list.add(new Student("Davis", SUBJECT.MATH, 3, 37.0));
    list.add(new Student("Davis", SUBJECT.SCIENCE, 4, 37.0));

    list.add(new Student("Sascha", SUBJECT.ENGLISH, 5, 85.0));
    list.add(new Student("Sascha", SUBJECT.MATH, 1, 80.0));
    list.add(new Student("Sascha", SUBJECT.ENGLISH, 6, 12.0));
    list.add(new Student("Sascha", SUBJECT.MATH, 3, 50.0));

    list.add(new Student("Robert", SUBJECT.ENGLISH, 5, 12.0));

    Map<String, Map<SUBJECT, List<Double>>> map = new HashMap<>();

    list.stream().forEach(student -> {
        map.computeIfAbsent(student.getName(), s -> new HashMap<>())
                .computeIfAbsent(student.getSubject(), s -> new ArrayList<>())
                .add(student.getMarks());
    });

    System.out.println(map);

Produktion:

{ Robert={ENGLISH=[12.0]}, 
Sascha={MATH=[80.0, 50.0], ENGLISH=[85.0, 12.0]}, 
Davis={MATH=[35.0, 37.0], SCIENCE=[12.9, 37.0]} }

Fusklapp

Mål Koda
Samla till en List Collectors.toList()
Samla till en ArrayList med fördelad storlek Collectors.toCollection(() -> new ArrayList<>(size))
Samla till en Set Collectors.toSet()
Samla till en Set med bättre iterationsprestanda Collectors.toCollection(() -> new LinkedHashSet<>())
Samla till en okänslig Set<String> Collectors.toCollection(() -> new TreeSet<>(String.CASE_INSENSITIVE_ORDER))
Samla till en EnumSet<AnEnum> (bästa prestanda för enums) Collectors.toCollection(() -> EnumSet.noneOf(AnEnum.class))
Samla till en Map<K,V> med unika nycklar Collectors.toMap(keyFunc,valFunc)
Karta MyObject.getter () till unika MyObject Collectors.toMap(MyObject::getter, Function.identity())
Karta MyObject.getter () till flera MyObjects Collectors.groupingBy(MyObject::getter)

Oändliga strömmar

Det är möjligt att generera en Stream som inte slutar. Att ringa en terminalmetod i en oändlig Stream gör att Stream in i en oändlig slinga. limit för en Stream kan användas för att begränsa antalet termer för den Stream som Java bearbetar.

Det här exemplet genererar en Stream av alla naturliga nummer, börjar med siffran 1. Varje på varandra följande term i Stream är en högre än föregående. Genom att ringa gränsmetoden för denna Stream beaktas och skrivs endast de första fem termerna i Stream .

// Generate infinite stream - 1, 2, 3, 4, 5, 6, 7, ...
IntStream naturalNumbers = IntStream.iterate(1, x -> x + 1);

// Print out only the first 5 terms
naturalNumbers.limit(5).forEach(System.out::println);

Produktion:

1
2
3
4
5


Ett annat sätt att generera en oändlig ström är att använda metoden Stream.generate . Denna metod tar en lambda av typen Leverantör .

// Generate an infinite stream of random numbers
Stream<Double> infiniteRandomNumbers = Stream.generate(Math::random);

// Print out only the first 10 random numbers
infiniteRandomNumbers.limit(10).forEach(System.out::println);

Konsumerar strömmar

En Stream kommer bara att korsas när det finns en terminaloperation , som count() , collect() eller för forEach() . Annars kommer ingen operation på Stream att utföras.

I följande exempel läggs ingen terminaloperation till Stream , så filter() -operationen kommer inte att åberopas och ingen utgång kommer att produceras eftersom peek() INTE är en terminaloperation .

IntStream.range(1, 10).filter(a -> a % 2 == 0).peek(System.out::println);

Live på Ideone

Detta är en Stream sekvens med en giltig terminaloperation , så att en utgång produceras.

Du kan också använda för forEach istället för att peek :

IntStream.range(1, 10).filter(a -> a % 2 == 0).forEach(System.out::println); 

Live på Ideone

Produktion:

2
4
6
8

När terminaloperationen har utförts konsumeras Stream och kan inte återanvändas.


Även om ett visst strömobjekt inte kan återanvändas, är det enkelt att skapa en återanvändbar Iterable som delegerar till en strömrörledning. Detta kan vara användbart för att returnera en modifierad vy av en live-datauppsättning utan att behöva samla resultat till en tillfällig struktur.

List<String> list = Arrays.asList("FOO", "BAR");
Iterable<String> iterable = () -> list.stream().map(String::toLowerCase).iterator();

for (String str : iterable) {
    System.out.println(str);
}
for (String str : iterable) {
    System.out.println(str);
}

Produktion:

foo
bar
foo
bar

Detta fungerar eftersom Iterable förklarar en enda abstrakt metod Iterator<T> iterator() . Det gör det effektivt till ett funktionellt gränssnitt, implementerat av en lambda som skapar en ny ström för varje samtal.


I allmänhet fungerar en Stream som visas i följande bild:

Strömdrift

OBS : Argumentkontroller utförs alltid, även utan terminaloperation :

try {
    IntStream.range(1, 10).filter(null);
} catch (NullPointerException e) {
    System.out.println("We got a NullPointerException as null was passed as an argument to filter()");
}

Live på Ideone

Produktion:

Vi fick en NullPointerException eftersom noll överfördes som ett argument för att filtrera ()

Skapa en frekvenskarta

groupingBy(classifier, downstream) samlaren tillåter insamling av Stream till en Map genom att klassificera varje element i en grupp och utföra en nedströmsoperation på elementen klassificerade i samma grupp.

Ett klassiskt exempel på denna princip är att använda en Map att räkna förekomsten av element i en Stream . I det här exemplet är klassificeringen helt enkelt identitetsfunktionen, som returnerar elementet som det är. Nedströmsoperationen räknar antalet lika element med counting() .

Stream.of("apple", "orange", "banana", "apple")
      .collect(Collectors.groupingBy(Function.identity(), Collectors.counting()))
      .entrySet()
      .forEach(System.out::println);

Nedströmsoperationen är själv en samlare ( Collectors.counting() ) som fungerar på element av typen String och ger ett resultat av typen Long . Resultatet av collect är en Map<String, Long> .

Detta skulle producera följande utgång:

banan = 1
apelsin = 1
äpple = 2

Parallell ström

Obs! Innan du bestämmer vilken Stream ska användas ska du titta på ParallelStream vs Sequential Stream-beteende .

När du vill utföra Stream samtidigt kan du använda något av dessa sätt.

List<String> data = Arrays.asList("One", "Two", "Three", "Four", "Five");
Stream<String> aParallelStream = data.stream().parallel();

Eller:

Stream<String> aParallelStream = data.parallelStream();

För att utföra de operationer som definierats för parallellströmmen, ring en terminaloperatör:

aParallelStream.forEach(System.out::println);

(En möjlig) som matas ut från den parallella Stream :

Tre
fyra
Ett
Två
Fem

Ordningen kan ändras när alla element behandlas parallellt (vilket kan göra det snabbare). Använd parallelStream när beställningen inte spelar någon roll.

Effektpåverkan

I fallet nätverk är inblandat, parallell Stream s kan försämra den totala prestandan för en applikation, eftersom alla parallella Stream s bruk en gemensam gaffel ansluta tråden pool för nätverket.

Å andra sidan kan parallella Stream förbättra prestandan betydligt i många andra fall beroende på antalet tillgängliga kärnor i den löpande CPU för tillfället.

Konvertera en valfri ström till en värdeström

Du kan behöva konvertera en Stream avger Optional till en Stream värden, endast avger värden från befintliga Optional . (dvs: utan null och inte handlar om Optional.empty() ).

 Optional<String> op1 = Optional.empty();
 Optional<String> op2 = Optional.of("Hello World");

 List<String> result = Stream.of(op1, op2)
                             .filter(Optional::isPresent)
                             .map(Optional::get)
                             .collect(Collectors.toList());

 System.out.println(result); //[Hello World]

Skapa en ström

Alla java Collection<E> s har stream() och parallelStream() -metoder från vilka en Stream<E> kan konstrueras:

Collection<String> stringList = new ArrayList<>();
Stream<String> stringStream = stringList.parallelStream();

En Stream<E> kan skapas från en matris med hjälp av en av två metoder:

String[] values = { "aaa", "bbbb", "ddd", "cccc" };
Stream<String> stringStream = Arrays.stream(values);
Stream<String> stringStreamAlternative = Stream.of(values);

Skillnaden mellan Arrays.stream() och Stream.of() är att Stream.of() har en parameter varargs, så den kan användas som:

Stream<Integer> integerStream = Stream.of(1, 2, 3);

Det finns också primitiva Stream som du kan använda. Till exempel:

IntStream intStream = IntStream.of(1, 2, 3);
DoubleStream doubleStream = DoubleStream.of(1.0, 2.0, 3.0);

Dessa primitiva strömmar kan också konstrueras med Arrays.stream() -metoden:

IntStream intStream = Arrays.stream(new int[]{ 1, 2, 3 });

Det är möjligt att skapa en Stream från en matris med ett specificerat intervall.

int[] values= new int[]{1, 2, 3, 4, 5};
IntStream intStram = Arrays.stream(values, 1, 3);

Observera att valfri primitiv ström kan konverteras till ström av boxad typ med metoden i boxed :

Stream<Integer> integerStream = intStream.boxed();

Detta kan vara användbart i vissa fall om du vill samla in data eftersom primitiv ström inte har någon collect som tar en Collector som argument.

Återanvända mellanoperationer i en strömkedja

Strömmen stängs när någonsin terminaldrift kallas. Återanvända strömmen av mellanoperationer när endast terminaldrift endast varierar. vi kan skapa en strömleverantör för att konstruera en ny ström med alla mellanliggande operationer som redan är installerade.

Supplier<Stream<String>> streamSupplier = () -> Stream.of("apple", "banana","orange", "grapes", "melon","blueberry","blackberry")
.map(String::toUpperCase).sorted();

  streamSupplier.get().filter(s ->   s.startsWith("A")).forEach(System.out::println);

// APPLE

  streamSupplier.get().filter(s -> s.startsWith("B")).forEach(System.out::println);

  // BANANA
  // BLACKBERRY
  // BLUEBERRY

int[] matriser kan konverteras till List<Integer> hjälp av strömmar

int[] ints = {1,2,3};
List<Integer> list = IntStream.of(ints).boxed().collect(Collectors.toList());

Hitta statistik om numeriska strömmar

Java 8 tillhandahåller klasser som kallas IntSummaryStatistics , DoubleSummaryStatistics och LongSummaryStatistics som ger ett tillståndsobjekt för att samla in statistik som count , min , max , sum och average .

Java SE 8
List<Integer> naturalNumbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
IntSummaryStatistics stats = naturalNumbers.stream()
                                           .mapToInt((x) -> x)
                                           .summaryStatistics();
System.out.println(stats);

Vilket kommer att resultera i:

Java SE 8
IntSummaryStatistics{count=10, sum=55, min=1, max=10, average=5.500000}

Skaffa en bit av en ström

Exempel: Skaffa en Stream med 30 element, som innehåller 21 till 50: e (inklusive) element i en samling.

final long n = 20L; // the number of elements to skip
final long maxSize = 30L; // the number of elements the stream should be limited to
final Stream<T> slice = collection.stream().skip(n).limit(maxSize);

Anmärkningar:

  • IllegalArgumentException kastas om n är negativ eller maxSize är negativ
  • både skip(long) och limit(long) är mellanoperationer
  • om en ström innehåller färre än n element, skip(n) en tom ström
  • både skip(long) och limit(long) är billiga operationer på sekventiella strömledningar, men kan vara ganska dyra på beställda parallella rörledningar

Slå samman strömmar

Variabel deklaration för exempel:

Collection<String> abc = Arrays.asList("a", "b", "c");
Collection<String> digits = Arrays.asList("1", "2", "3");
Collection<String> greekAbc = Arrays.asList("alpha", "beta", "gamma");

Exempel 1 - Concatenate två Stream s

final Stream<String> concat1 = Stream.concat(abc.stream(), digits.stream());

concat1.forEach(System.out::print);
// prints: abc123

Exempel 2 - Concatenate fler än två Stream s

final Stream<String> concat2 = Stream.concat(
    Stream.concat(abc.stream(), digits.stream()),
    greekAbc.stream());

System.out.println(concat2.collect(Collectors.joining(", ")));
// prints: a, b, c, 1, 2, 3, alpha, beta, gamma

Alternativt för att förenkla concat() kapslade concat() kan Stream s också sammanlänkas med flatMap() :

final Stream<String> concat3 = Stream.of(
    abc.stream(), digits.stream(), greekAbc.stream())
    .flatMap(s -> s);
    // or `.flatMap(Function.identity());` (java.util.function.Function)

System.out.println(concat3.collect(Collectors.joining(", ")));
// prints: a, b, c, 1, 2, 3, alpha, beta, gamma

Var försiktig när du Stream strömmar från upprepad sammanlänkning, eftersom åtkomst till ett element i en djupt sammankopplad Stream kan resultera i djupa samtalskedjor eller till och med en StackOverflowException .

IntStream to String

Java har inte en Char Stream , så när du arbetar med String s och konstruerar en Stream of Character s är ett alternativ att få en IntStream av kodpunkter med String.codePoints() -metoden. Så IntStream kan erhållas enligt nedan:

public IntStream stringToIntStream(String in) {
  return in.codePoints();
}

Det är lite mer involverat att göra konverteringen tvärtom, dvs IntStreamToString. Det kan göras på följande sätt:

public String intStreamToString(IntStream intStream) {
  return intStream.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append).toString();
}

Sortera med hjälp av ström

List<String> data = new ArrayList<>();
data.add("Sydney");
data.add("London");
data.add("New York");
data.add("Amsterdam");
data.add("Mumbai");
data.add("California");

System.out.println(data);

List<String> sortedData = data.stream().sorted().collect(Collectors.toList());

System.out.println(sortedData);

Produktion:

[Sydney, London, New York, Amsterdam, Mumbai, California]
[Amsterdam, California, London, Mumbai, New York, Sydney]

Det är också möjligt att använda olika jämförelsemekanismer eftersom det finns en överbelastad sorted version som tar en komparator som sitt argument.

Du kan också använda ett lambda-uttryck för sortering:

List<String> sortedData2 = data.stream().sorted((s1,s2) -> s2.compareTo(s1)).collect(Collectors.toList());

Detta skulle producera [Sydney, New York, Mumbai, London, California, Amsterdam]

Du kan använda Comparator.reverseOrder() att ha en komparator som sätter reverse till den naturliga beställningen.

List<String> reverseSortedData = data.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList());

Strömmar av primitiva

Java tillhandahåller specialiserade Stream för tre typer av primitiva IntStream (för int ), LongStream (för long s) och DoubleStream (för double s). Förutom att de är optimerade implementationer för sina respektive primitiv, tillhandahåller de också flera specifika terminalmetoder, vanligtvis för matematiska operationer. T.ex:

IntStream is = IntStream.of(10, 20, 30);
double average = is.average().getAsDouble(); // average is 20.0

Samla resultat av en ström till en matris

Analog för att få en samling för en Stream genom att collect() en matris kan erhållas med Stream.toArray() :

List<String> fruits = Arrays.asList("apple", "banana", "pear", "kiwi", "orange");

String[] filteredFruits = fruits.stream()
    .filter(s -> s.contains("a"))
    .toArray(String[]::new);     

// prints: [apple, banana, pear, orange]
System.out.println(Arrays.toString(filteredFruits));

String[]::new är en speciell typ av metodreferens: en konstruktorreferens.

Hitta det första elementet som matchar ett predikat

Det är möjligt att hitta det första elementet i en Stream som matchar ett villkor.

I det här exemplet hittar vi det första Integer vars kvadrat är över 50000 .

IntStream.iterate(1, i -> i + 1) // Generate an infinite stream 1,2,3,4...
    .filter(i -> (i*i) > 50000) // Filter to find elements where the square is >50000
    .findFirst(); // Find the first filtered element

Detta uttryck kommer att returnera en OptionalInt med resultatet.

Observera att med en oändlig Stream kommer Java att kontrollera varje element tills det hittar ett resultat. Med en ändlig Stream , om Java slutar på element men fortfarande inte kan hitta ett resultat, returnerar det en tom OptionalInt .

Använda IntStream för att iterera över index

Stream av element tillåter vanligtvis inte åtkomst till indexvärdet för det aktuella objektet. För att iterera över en matris eller ArrayList medan du har tillgång till index använder du IntStream.range(start, endExclusive) .

String[] names = { "Jon", "Darin", "Bauke", "Hans", "Marc" };

IntStream.range(0, names.length)
    .mapToObj(i -> String.format("#%d %s", i + 1, names[i]))
    .forEach(System.out::println);

Metoden range(start, endExclusive) returnerar en annan ÌntStream och mapToObj(mapper) returnerar en ström av String .

Produktion:

# 1 Jon
# 2 Darin
# 3 Bauke
# 4 Hans
# 5 Marc

Detta liknar mycket att använda en normal for slinga med en räknare, men med fördel av rörledning och parallellisering:

for (int i = 0; i < names.length; i++) {
    String newName = String.format("#%d %s", i + 1, names[i]);
    System.out.println(newName);
}

Flatten Streams med flatMap ()

En Stream av artiklar som i sin tur är streambara kan plattas ut i en enda kontinuerlig Stream :

Array of List of Items kan konverteras till en enda lista.

List<String> list1 =  Arrays.asList("one", "two");
      List<String> list2 =  Arrays.asList("three","four","five");
      List<String> list3 =  Arrays.asList("six");
          List<String> finalList = Stream.of(list1, list2, list3).flatMap(Collection::stream).collect(Collectors.toList());
System.out.println(finalList);

// [one, two, three, four, five, six]

Karta som innehåller lista över objekt som värden kan plattas till en kombinerad lista

Map<String, List<Integer>> map = new LinkedHashMap<>();
map.put("a", Arrays.asList(1, 2, 3));
map.put("b", Arrays.asList(4, 5, 6));

List<Integer> allValues = map.values() // Collection<List<Integer>>
        .stream()                      // Stream<List<Integer>>
        .flatMap(List::stream)         // Stream<Integer>
        .collect(Collectors.toList());

System.out.println(allValues);
// [1, 2, 3, 4, 5, 6]

List över Map kan plattas ut i en enda kontinuerlig Stream

List<Map<String, String>> list = new ArrayList<>();
Map<String,String> map1 = new HashMap();
map1.put("1", "one");
map1.put("2", "two");

Map<String,String> map2 = new HashMap();
map2.put("3", "three");
map2.put("4", "four");
list.add(map1);
list.add(map2);


Set<String> output= list.stream()  //  Stream<Map<String, String>>
    .map(Map::values)              // Stream<List<String>>
    .flatMap(Collection::stream)   // Stream<String>
    .collect(Collectors.toSet());  //Set<String>
// [one, two, three,four]

Skapa en karta baserad på en ström

Enkelt fall utan duplikatnycklar

Stream<String> characters = Stream.of("A", "B", "C");

Map<Integer, String> map = characters
            .collect(Collectors.toMap(element -> element.hashCode(), element -> element));
// map = {65=A, 66=B, 67=C}

För att göra saker mer förklarande kan vi använda statisk metod i Function - Function.identity() . Vi kan ersätta detta lambda- element -> element med Function.identity() .

Fall där det kan finnas duplikatnycklar

Javadoc för Collectors.toMap anger:

Om de mappade nycklarna innehåller dubbletter (enligt Object.equals(Object) ) kastas en IllegalStateException när insamlingsoperationen utförs. Om de mappade knapparna kan ha dubbletter, använd istället toMap(Function, Function, BinaryOperator) .

Stream<String> characters = Stream.of("A", "B", "B", "C");

Map<Integer, String> map = characters
            .collect(Collectors.toMap(
                element -> element.hashCode(),
                element -> element,
                (existingVal, newVal) -> (existingVal + newVal)));

// map = {65=A, 66=BB, 67=C}

BinaryOperator skickas till Collectors.toMap(...) genererar värdet som ska lagras vid en kollision. Det kan:

  • returnera det gamla värdet, så att det första värdet i strömmen har företräde,
  • returnera det nya värdet, så att det sista värdet i strömmen har företräde, eller
  • kombinera de gamla och nya värdena

Gruppera efter värde

Du kan använda Collectors.groupingBy när du behöver utföra motsvarigheten till en databas kaskad "grupp efter" operation. För att illustrera skapar följande en karta där människors namn mappas till efternamn:

List<Person> people = Arrays.asList(
    new Person("Sam", "Rossi"),
    new Person("Sam", "Verdi"),
    new Person("John", "Bianchi"),
    new Person("John", "Rossi"),
    new Person("John", "Verdi")
);

Map<String, List<String>> map = people.stream()
        .collect(
                // function mapping input elements to keys
                Collectors.groupingBy(Person::getName, 
                // function mapping input elements to values,
                // how to store values
                Collectors.mapping(Person::getSurname, Collectors.toList()))
        );

// map = {John=[Bianchi, Rossi, Verdi], Sam=[Rossi, Verdi]}

Live på Ideone

Generera slumpmässiga strängar med strömmar

Det är ibland användbart att skapa slumpmässiga Strings , kanske som session-ID för en webbtjänst eller ett initialt lösenord efter registrering för en applikation. Detta kan enkelt uppnås med hjälp av Stream .

Först måste vi initiera en slumptalsgenerator. För att förbättra säkerheten för de genererade String är det en bra idé att använda SecureRandom .

Obs! Att skapa ett SecureRandom är ganska dyrt, så det är bästa praxis att bara göra detta en gång och ringa en av dess setSeed() -metoder då och då för att sätta in den igen.

private static final SecureRandom rng = new SecureRandom(SecureRandom.generateSeed(20)); 
//20 Bytes as a seed is rather arbitrary, it is the number used in the JavaDoc example

När vi skapar slumpmässiga String vill vi att de bara använder vissa tecken (t.ex. bara bokstäver och siffror). Därför kan vi skapa en metod som returnerar en boolean som senare kan användas för att filtrera Stream .

//returns true for all chars in 0-9, a-z and A-Z
boolean useThisCharacter(char c){
    //check for range to avoid using all unicode Letter (e.g. some chinese symbols)
    return c >= '0' && c <= 'z' && Character.isLetterOrDigit(c);
}

Därefter kan vi använda RNG för att generera en slumpmässig sträng av specifik längd som innehåller charset som passerar vår useThisCharacter kontroll.

public String generateRandomString(long length){
    //Since there is no native CharStream, we use an IntStream instead
    //and convert it to a Stream<Character> using mapToObj.
    //We need to specify the boundaries for the int values to ensure they can safely be cast to char
    Stream<Character> randomCharStream = rng.ints(Character.MIN_CODE_POINT, Character.MAX_CODE_POINT).mapToObj(i -> (char)i).filter(c -> this::useThisCharacter).limit(length);

    //now we can use this Stream to build a String utilizing the collect method.
    String randomString = randomCharStream.collect(StringBuilder::new, StringBuilder::append, StringBuilder::append).toString();
    return randomString;
}

Använda strömmar för att implementera matematiska funktioner

Stream , och särskilt IntStream s, är ett elegant sätt att implementera summeringsvillkor (∑). Intervallen av Stream kan användas som gränserna för summeringen.

Till exempel, Madhavas approximation av Pi ges med formeln (Källa: wikipedia ): Madhavas approximation

Detta kan beräknas med godtycklig precision. Till exempel för 101 villkor:

double pi = Math.sqrt(12) * 
            IntStream.rangeClosed(0, 100)
                     .mapToDouble(k -> Math.pow(-3, -1 * k) / (2 * k + 1))
                     .sum();

Obs: Med double precision är det att välja en övre gräns på 29 tillräckligt för att få ett resultat som inte kan Math.Pi från Math.Pi

Använda strömmar och metodreferenser för att skriva självdokumenterande processer

Metodreferenser gör utmärkt självdokumenterande kod, och att använda metodreferenser med Stream gör komplicerade processer enkla att läsa och förstå. Tänk på följande kod:

public interface Ordered {
    default int getOrder(){
        return 0;
    }
}

public interface Valued<V extends Ordered> {
    boolean hasPropertyTwo();
    V getValue();
}

public interface Thing<V extends Ordered> {
    boolean hasPropertyOne();
    Valued<V> getValuedProperty();
}

public <V extends Ordered> List<V> myMethod(List<Thing<V>> things) {
    List<V> results = new ArrayList<V>();
    for (Thing<V> thing : things) {
        if (thing.hasPropertyOne()) {
            Valued<V> valued = thing.getValuedProperty();
            if (valued != null && valued.hasPropertyTwo()){
                V value = valued.getValue();
                if (value != null){
                    results.add(value);
                }
            }
        }
    }
    results.sort((a, b)->{
        return Integer.compare(a.getOrder(), b.getOrder());
    });
    return results;
}

Denna sista metod som skrivs om med hjälp av Stream och metodreferenser är mycket mer läsbar och varje steg i processen förstås snabbt och lätt - det är inte bara kortare, det visar också på ett ögonblick vilka gränssnitt och klasser som är ansvariga för koden i varje steg:

public <V extends Ordered> List<V> myMethod(List<Thing<V>> things) {
    return things.stream()
        .filter(Thing::hasPropertyOne)
        .map(Thing::getValuedProperty)
        .filter(Objects::nonNull)
        .filter(Valued::hasPropertyTwo)
        .map(Valued::getValue)
        .filter(Objects::nonNull)
        .sorted(Comparator.comparing(Ordered::getOrder))
        .collect(Collectors.toList());
}    

Använda strömmar av kartor.Entry för att bevara initiala värden efter kartläggning

När du har en Stream måste du kartlägga men också vill bevara de initiala värdena, du kan kartlägga Stream till en Map.Entry<K,V> hjälp av en verktygsmetod som följande:

public static <K, V> Function<K, Map.Entry<K, V>> entryMapper(Function<K, V> mapper){
    return (k)->new AbstractMap.SimpleEntry<>(k, mapper.apply(k));
}

Sedan kan du använda din omvandlare för att bearbeta Stream har tillgång till både de ursprungliga och mappade värdena:

Set<K> mySet;
Function<K, V> transformer = SomeClass::transformerMethod;
Stream<Map.Entry<K, V>> entryStream = mySet.stream()
    .map(entryMapper(transformer));

Du kan sedan fortsätta att bearbeta den Stream som normalt. Detta undviker omkostnaderna för att skapa en mellansamling.

Strömförsörjningskategorier

Stream-operationer ingår i två huvudkategorier, mellan- och terminaloperationer, och två underkategorier, statslösa och statliga.


Mellanoperationer:

En mellanoperation är alltid lat , till exempel en enkel Stream.map . Det åberopas inte förrän strömmen faktiskt konsumeras. Detta kan verifieras enkelt:

Arrays.asList(1, 2 ,3).stream().map(i -> {
    throw new RuntimeException("not gonna happen");
    return i;
});

Mellanoperationer är de vanliga byggstenarna i en ström, kedjade efter källan och följs vanligtvis av en terminaloperation som utlöser strömkedjan.


Terminaloperationer

Terminaloperationer är det som utlöser förbrukningen av en ström. Några av de vanligare är Stream.forEach eller Stream.collect . De placeras vanligtvis efter en kedja av mellanoperationer och är nästan alltid ivriga .


Statslösa verksamheter

Statslöshet innebär att varje objekt behandlas utan sammanhanget med andra objekt. Statslösa operationer möjliggör minneseffektiv behandling av strömmar. Verksamheter som Stream.map och Stream.filter som inte kräver information om andra objekt i strömmen anses vara statslösa.


Statlig verksamhet

Statlighet innebär att operationen för varje objekt beror på (vissa) andra objekt i strömmen. Detta kräver att ett tillstånd bevaras. Åtgärder med tillstånd kan bryta med långa eller oändliga strömmar. Verksamheter som Stream.sorted kräver att hela strömmen ska bearbetas innan något objekt sänds ut som kommer att brytas i en tillräckligt lång ström av objekt. Detta kan demonstreras med en lång ström ( kör på egen risk ):

// works - stateless stream
long BIG_ENOUGH_NUMBER = 999999999;
IntStream.iterate(0, i -> i + 1).limit(BIG_ENOUGH_NUMBER).forEach(System.out::println);

Detta kommer att orsaka ett minne utanför Stream.sorted :

// Out of memory - stateful stream
IntStream.iterate(0, i -> i + 1).limit(BIG_ENOUGH_NUMBER).sorted().forEach(System.out::println);

Konvertera en iterator till en ström

Använd Spliterators.spliterator() eller Spliterators.spliteratorUnknownSize() att konvertera en iterator till en ström:

Iterator<String> iterator = Arrays.asList("A", "B", "C").iterator();    
Spliterator<String> spliterator = Spliterators.spliteratorUnknownSize(iterator, 0);
Stream<String> stream = StreamSupport.stream(spliterator, false);

Minskning med strömmar

Reduktion är processen att tillämpa en binär operatör på varje element i en ström för att resultera i ett värde.

sum() -metoden för en IntStream är ett exempel på en reduktion; det gäller tillägg till varje term i strömmen, vilket resulterar i ett slutligt värde: Summinskning

Detta motsvarar (((1+2)+3)+4)

En reduce för en ström låter en skapa en anpassad reduktion. Det är möjligt att använda reduce för att implementera sum() -metoden:

IntStream istr;
    
//Initialize istr
    
OptionalInt istr.reduce((a,b)->a+b);

Den Optional versionen returneras så att tomma strömmar kan hanteras på lämpligt sätt.

Ett annat exempel på reduktion är att kombinera en Stream<LinkedList<T>> till en enda LinkedList<T> :

Stream<LinkedList<T>> listStream;
    
//Create a Stream<LinkedList<T>>
    
Optional<LinkedList<T>> bigList = listStream.reduce((LinkedList<T> list1, LinkedList<T> list2)->{
    LinkedList<T> retList = new LinkedList<T>();
    retList.addAll(list1);
    retList.addAll(list2);
    return retList;
});

Du kan också tillhandahålla ett identitetselement . Exempelvis är identitetselementet för tillägg 0, som x+0==x . För multiplikation är identitetselementet 1, som x*1==x . I fallet ovan är identitetselementet en tom LinkedList<T> , för om du lägger till en tom lista till en annan lista ändras inte listan som du "lägger till" till:

Stream<LinkedList<T>> listStream;

//Create a Stream<LinkedList<T>>

LinkedList<T> bigList = listStream.reduce(new LinkedList<T>(), (LinkedList<T> list1, LinkedList<T> list2)->{
    LinkedList<T> retList = new LinkedList<T>();
    retList.addAll(list1);
    retList.addAll(list2);
    return retList;
});

Observera att när ett identitetselement tillhandahålls, är returvärdet inte lindat i ett Optional — om det kallas på en tom ström, reduce() kommer identitetselementet att returneras.

Den binära operatören måste också vara associerande , vilket betyder att (a+b)+c==a+(b+c) . Detta beror på att elementen kan reduceras i valfri ordning. Exempelvis kan ovanstående tilläggsreduktion utföras så här:

Annan summan

Denna minskning motsvarar skrivande ((1+2)+(3+4)) . Egenskaperna för associativitet gör det också möjligt för Java att reducera Stream parallellt - en del av strömmen kan minskas av varje processor, med en reduktion som kombinerar resultatet för varje processor i slutet.

Gå med i en ström till en enda sträng

Ett användningsfall som ofta stöter på är att skapa en String från en ström, där strömobjekten är separerade med en viss karaktär. Metoden Collectors.joining() kan användas för detta, som i följande exempel:

Stream<String> fruitStream = Stream.of("apple", "banana", "pear", "kiwi", "orange");

String result = fruitStream.filter(s -> s.contains("a"))
           .map(String::toUpperCase)
           .sorted()
           .collect(Collectors.joining(", "));
           
System.out.println(result);

Produktion:

APPEL, BANANA, ORANGE, Päron

Metoden Collectors.joining() kan också tillgodose för- och postfix:

String result = fruitStream.filter(s -> s.contains("e"))
           .map(String::toUpperCase)
           .sorted()
           .collect(Collectors.joining(", ", "Fruits: ", "."));
           
System.out.println(result);

Produktion:

Frukter: APPLE, ORANGE, Päron.

Live på Ideone



Modified text is an extract of the original Stack Overflow Documentation
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow