Hur man lär sig Java Stream API [+5 Resources]

By rik

Introduktion till Java Streams

I Java representerar en ström en följd av element som kan bearbetas genom sekventiella eller parallella operationer. Dessa operationer kan vara av mellanliggande eller terminal karaktär, och slutresultatet levereras efter att den terminala operationen har utförts.

Strömmar hanteras av Stream API, som introducerades i Java 8 och har blivit ett kraftfullt verktyg för databearbetning.

Vad är en Stream?

Tänk dig en ström som en fabrikspipeline där produkter genomgår olika steg, såsom tillverkning, sortering och paketering. I Java motsvarar dessa produkter objekt eller samlingar av objekt, medan operationerna är de transformationer som utförs och pipelinen är själva strömmen.

En ström består av följande komponenter:

  • En ingångskälla
  • Mellanliggande operationer som omvandlar data
  • En terminaloperation som producerar resultatet
  • Slutresultatet

Här är några viktiga egenskaper hos strömmar:

  • En ström är inte en datastruktur i minnet; det är en sekvens av data som bearbetas stegvis.
  • Strömmar är deklarativa, vilket innebär att man anger *vad* som ska göras snarare än *hur* det ska utföras.
  • Strömmar kan endast användas en gång eftersom de inte lagras.
  • Strömmar ändrar inte den underliggande datastrukturen utan skapar istället en ny struktur från den ursprungliga.
  • Terminaloperationen returnerar slutresultatet från bearbetningspipelinen.

Stream API kontra samlingsbearbetning

En samling är en datastruktur som används för att lagra och hantera data i minnet, med välkända typer som Set, Map och List. En ström är istället ett sätt att effektivt överföra data genom en process efter att den har bearbetats.

Här är ett exempel på hur en ArrayList används:

import java.util.ArrayList;

public class Main {
    public static void main(String[] args) {
        ArrayList list = new ArrayList();
        list.add(0, 3);
        System.out.println(list);
    }
}
    
Utdata:
[3]
    

Som exemplet visar kan man skapa en ArrayList, lägga till data och bearbeta den. Med strömmar kan man istället arbeta med en befintlig datastruktur och generera ett nytt modifierat resultat.

Exempel med filtrering av en lista med hjälp av en ström:

import java.util.ArrayList;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList();

        for (int i = 0; i < 20; i++) {
            list.add(i+1);
        }

        System.out.println(list);

        Stream<Integer> filtered = list.stream().filter(num -> num > 10);
        filtered.forEach(num -> System.out.println(num + " "));
    }
}
    
Utdata:
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
11 
12 
13 
14 
15 
16 
17 
18 
19 
20
    

I detta exempel filtreras en lista för att hitta tal större än 10 med hjälp av en ström. Notera att strömmen inte lagrar något; listan itereras endast för att generera resultatet. Om man försöker skriva ut strömmen erhålls en referens till strömmen, inte dess innehåll.

Arbeta med Java Stream API

Java Stream API tar en datakälla (till exempel en samling eller en sekvens av element) och bearbetar dessa element för att generera ett resultat. Strömmen fungerar som en pipeline där data transformeras.

Strömmar kan skapas från olika källor:

  • Från samlingar som List och Set
  • Från arrays
  • Från filer via buffrad inläsning

Det finns två huvudtyper av operationer i en ström:

  • Mellanliggande operationer
  • Terminaloperationer

Mellanliggande kontra Terminala Operationer

Mellanliggande operationer returnerar en ny ström efter att ha bearbetat datan med den angivna metoden. Inga data bearbetas aktivt i det steget utan istället skickas datan vidare till nästa operation. Det är först vid den terminala operationen som datan bearbetas och det slutliga resultatet produceras.

Till exempel, om du vill filtrera och sedan mappa en lista med nummer, så kommer inte alla element i listan bearbetas omedelbart. Istället kommer varje element kontrolleras och om villkoret för filtreringen är uppfyllt, kommer elementet att mappas. Varje operation producerar en ny ström.

Mapping-operationen utförs endast på de element som uppfyller filterkriteriet. Vid den terminala operationen kombineras alla dessa bearbetade element för att ge ett slutgiltigt resultat.

När en terminal operation utförs, förbrukas strömmen och kan inte återanvändas. En ny ström måste skapas om samma bearbetning ska utföras igen.

Källa: The Bored Dev

Med en grundläggande förståelse av hur strömmar fungerar ska vi nu titta på implementationen av strömmar i Java.

#1. En tom ström

En tom ström skapas genom att använda `empty()` metoden i Stream API.

import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        Stream emptyStream = Stream.empty();
        System.out.println(emptyStream.count());
    }
}
    
Utdata:
0
    

Antalet element i en tom ström är 0. Tomma strömmar är användbara för att undvika NullPointerExceptions.

#2. Strömmar från samlingar

Samlingar som Lists och Sets har en `stream()` metod som skapar en ström från samlingen. Denna ström kan sedan användas för att bearbeta datan.

ArrayList<Integer> list = new ArrayList();

for (int i = 0; i < 20; i++) {
    list.add(i+1);
}

System.out.println(list);

Stream<Integer> filtered = list.stream().filter(num -> num > 10);
filtered.forEach(num -> System.out.println(num + " "));

Utdata:

[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
11 
12 
13 
14 
15 
16 
17 
18 
19 
20
    

#3. Strömmar från Arrays

Metoden `Arrays.stream()` skapar en ström från en array.

import java.util.Arrays;

public class Main {
    public static void main(String[] args) {
        String[] stringArray = new String[]{"this", "is", "adminvista.com"};
        Arrays.stream(stringArray).forEach(item -> System.out.print(item + " "));
    }
}
    
Utdata:
this is adminvista.com
  

Man kan också ange start- och slutindex för att skapa en ström av en del av arrayen. Startindex är inkluderande och slutindex är exkluderande.

String[] stringArray = new String[]{"this", "is", "adminvista.com"};
Arrays.stream(stringArray, 1, 3).forEach(item -> System.out.print(item + " "));

Utdata:
is adminvista.com
    

#4. Hitta min och max värden med strömmar

För att hitta det minsta och största elementet i en samling kan man använda `min()` och `max()` metoderna med en komparator. Dessa metoder returnerar ett Optional-objekt.

Ett Optional objekt är en behållare som kan innehålla ett värde eller vara tom. Om värdet inte är null, returneras värdet genom anrop av metoden `get()` på Optional-objektet.

import java.util.Arrays;
import java.util.Optional;

public class MinMax {
    public static void main(String[] args) {
        Integer[] numbers = new Integer[]{21, 82, 41, 9, 62, 3, 11};

        Optional<Integer> maxValue = Arrays.stream(numbers).max(Integer::compare);
        System.out.println(maxValue.get());

        Optional<Integer> minValue = Arrays.stream(numbers).min(Integer::compare);
        System.out.println(minValue.get());
    }
}
    
Utdata:
82
3
    

Lärresurser

För att fördjupa din förståelse av Java Streams och Java 8, här är några rekommenderade resurser:

#1. Java 8 in Action

Denna bok är en guide som täcker alla nya funktioner i Java 8, inklusive strömmar, lambdas och funktionell programmering. Den innehåller också frågesporter för kunskapskontroll.

Boken finns både i tryckt och ljudboksformat på Amazon.

#2. Java 8 Lambdas: Functional Programming for the Masses

Denna bok riktar sig till Java utvecklare som vill förstå hur lambdas har förändrat Java-språket. Den innehåller enkla förklaringar, kodexempel och övningar.

Boken finns både i tryckt format och Kindle-utgåva på Amazon.

#3. Java SE 8 for the Really Impatient

Denna bok är för erfarna Java-utvecklare som snabbt vill lära sig förbättringarna i Java SE 8 som Stream API, lambda-uttryck, samtidighetsförbättringar och vissa Java 7-funktioner som många inte känner till.

Boken finns i tryckt format på Amazon.

#4. Lär dig Java funktionell programmering med Lambdas & Streams

Den här kursen på Udemy utforskar grunderna i funktionell programmering i Java 8 och 9. Den täcker lambda-uttryck, metodreferenser, strömmar och funktionella gränssnitt.

Kursen innehåller Java-pussel och övningar för funktionell programmering.

#5. Java Class Library

Java Class Library är en del av Core Java Specialization som erbjuds av Coursera. Kursen lär dig att skriva typsäker kod med Java Generics, att förstå klassbiblioteket som innehåller över 4000 klasser, hur man arbetar med filer och hanterar fel. Det finns dock vissa förkunskapskrav:

  • Introduktion till Java
  • Introduktion till objektorienterad programmering med Java
  • Objektorienterade hierarkier i Java

Slutord

Java Stream API och införandet av Lambda-funktioner i Java 8 har förenklat och förbättrat många aspekter av Java-programmering, såsom parallell bearbetning, funktionella gränssnitt, och kortare kod.

Strömmar har vissa begränsningar, den viktigaste är att de kan endast bearbetas en gång. För Java-utvecklare är de resurser som nämns ovan värdefulla för att förstå dessa koncept i detalj.

Du kan också vara intresserad av att lära dig om undantagshantering i Java.