Java 8 - Kraftig sammenligning med Lambdas

1. Oversigt

I denne tutorial tager vi et første kig på Lambda-understøttelse i Java 8 - specifikt til, hvordan man udnytter den til at skrive Komparator og sorter en samling.

Denne artikel er en del af "Java - Back to Basic" -serien her på Baeldung.

Lad os først definere en simpel enhedsklasse:

offentlig klasse Human {privat strengnavn; privat int alder // standard konstruktører, getters / settere, lig og hashcode} 

2. Grundlæggende sortering uden lambdas

Før Java 8 ville sortering af en samling involvere skabe en anonym indre klasse for Komparator brugt i den slags:

ny komparator () {@ Override offentlig int sammenligne (Human h1, Human h2) {return h1.getName (). CompareTo (h2.getName ()); }}

Dette ville simpelthen bruges til at sortere Liste af Human enheder:

@Test offentligt ugyldigt givetPreLambda_whenSortingEntitiesByName_thenCorrectlySorted () {Liste mennesker = Lists.newArrayList (nyt menneske ("Sarah", 10), nyt menneske ("Jack", 12)); Collections.sort (mennesker, ny komparator () {@ Override offentlig int sammenligne (Human h1, Human h2) {return h1.getName (). CompareTo (h2.getName ());}}); Assert.assertThat (mennesker.get (0), ligeTo (nyt menneske ("Jack", 12)); }

3. Grundlæggende sortering med Lambda-støtte

Med introduktionen af ​​Lambdas kan vi nu omgå den anonyme indre klasse og opnå det samme resultat med enkel, funktionel semantik:

(endelig Human h1, endelig Human h2) -> h1.getName (). sammenlignTo (h2.getName ());

Tilsvarende - vi kan nu teste adfærden som før:

@Test offentlig ugyldig nårSortingEntitiesByName_thenCorrectlySorted () {Liste mennesker = Lists.newArrayList (nyt menneske ("Sarah", 10), nyt menneske ("Jack", 12)); mennesker.sort ((Human h1, Human h2) -> h1.getName (). sammenlignTo (h2.getName ())); assertThat (mennesker.get (0), lige til (nyt menneske ("Jack", 12))); }

Bemærk, at vi også bruger den nye sortere API tilføjet til java.util.List i Java 8 - i stedet for det gamle Collections.sort API.

4. Grundlæggende sortering uden typedefinitioner

Vi kan yderligere forenkle udtrykket ved ikke at specificere typedefinitionerne - compileren er i stand til at udlede disse alene:

(h1, h2) -> h1.getName (). sammenlignTo (h2.getName ())

Og igen forbliver testen meget ens:

@Test offentligt ugyldigt givetLambdaShortForm_whenSortingEntitiesByName_thenCorrectlySorted () {Liste mennesker = Lists.newArrayList (nyt menneske ("Sarah", 10), nyt menneske ("Jack", 12)); mennesker.sort ((h1, h2) -> h1.getName (). sammenlignTo (h2.getName ())); assertThat (mennesker.get (0), lige til (nyt menneske ("Jack", 12)); }

5. Sorter ved hjælp af reference til statisk metode

Dernæst skal vi udføre sorteringen ved hjælp af et Lambda-udtryk med en henvisning til en statisk metode.

Først skal vi definere metoden CompareByNameThenAge - med nøjagtig samme underskrift som sammenligne metode i en Komparator objekt:

offentlig statisk int CompareByNameThenAge (Human lhs, Human rhs) {if (lhs.name.equals (rhs.name)) {return Integer.compare (lhs.age, rhs.age); } andet {returner lhs.name.compareTo (rhs.name); }}

Nu kalder vi mennesker. sort metode med denne reference:

mennesker.sort (Human :: sammenlignByNameThenAge);

Slutresultatet er en arbejdssortering af samlingen ved hjælp af den statiske metode som en Komparator:

@Test offentlig ugyldighed givenMethodDefinition_whenSortingEntitiesByNameThenAge_thenCorrectlySorted () {Liste mennesker = Lists.newArrayList (nyt menneske ("Sarah", 10), nyt menneske ("Jack", 12)); mennesker.sort (menneske :: sammenlignByNameThenAge); Assert.assertThat (mennesker.get (0), ligeTo (nyt menneske ("Jack", 12)); }

6. Sorter ekstraherede komparatorer

Vi kan også undgå at definere selv sammenligningslogikken ved hjælp af en eksempel metode reference og Comparator. Sammenligning metode - der udtrækker og opretter en Sammenlignelig baseret på denne funktion.

Vi skal bruge getter getName () at opbygge Lambda-udtrykket og sortere listen efter navn:

@Test offentlig ugyldighed givenInstanceMethod_whenSortingEntitiesByName_thenCorrectlySorted () {Liste mennesker = Lists.newArrayList (nyt menneske ("Sarah", 10), nyt menneske ("Jack", 12)); Collections.sort (mennesker, Comparator.comparing (Human :: getName)); assertThat (mennesker.get (0), lige til (nyt menneske ("Jack", 12))); }

7. Omvendt sortering

JDK 8 har også indført en hjælpemetode til vende komparatoren - det kan vi hurtigt bruge til at vende vores slags:

@Test offentlig ugyldig nårSortingEntitiesByNameReversed_thenCorrectlySorted () {Liste mennesker = Lists.newArrayList (nyt menneske ("Sarah", 10), nyt menneske ("Jack", 12)); Komparator komparator = (h1, h2) -> h1.getName (). SammenlignTo (h2.getName ()); mennesker.sort (komparator.omvendt ()); Assert.assertThat (mennesker.get (0), ligeTo (nyt menneske ("Sarah", 10))); }

8. Sorter efter flere forhold

Sammenligningen af ​​lambda-udtryk behøver ikke være så enkel - vi kan skrive også mere komplekse udtryk - for eksempel at sortere enhederne først efter navn og derefter efter alder:

@Test offentligt ugyldigt nårSortingEntitiesByNameThenAge_thenCorrectlySorted () {Liste mennesker = Lists.newArrayList (nyt menneske ("Sarah", 12), nyt menneske ("Sarah", 10), nyt menneske ("Zack", 12)); mennesker.sort ((lhs, rhs) -> {if (lhs.getName (). er lig med (rhs.getName ())) {return Integer.compare (lhs.getAge (), rhs.getAge ());} andet {returner lhs.getName (). sammenlignTo (rhs.getName ());}}); Assert.assertThat (mennesker.get (0), ligeTo (nyt menneske ("Sarah", 10))); }

9. Sorter efter flere forhold - sammensætning

Den samme sammenligningslogik - først sortering efter navn og derefter sekundært efter alder - kan også implementeres af den nye kompositionssupport til Komparator.

Fra og med JDK 8 kan vi nu sammenkæde flere komparatorer at opbygge mere kompleks sammenligningslogik:

@Test offentligt ugyldigt givetComposition_whenSortingEntitiesByNameThenAge_thenCorrectlySorted () {Liste mennesker = Lists.newArrayList (nyt menneske ("Sarah", 12), nyt menneske ("Sarah", 10), nyt menneske ("Zack", 12)); mennesker.sort (Comparator.comparing (Human :: getName). derefterComparing (Human :: getAge)); Assert.assertThat (mennesker.get (0), ligeTo (nyt menneske ("Sarah", 10))); }

10. Sortering af en liste med Stream.sorted ()

Vi kan også sortere en samling ved hjælp af Java 8'er Strømsorteret () API.

Vi kan sortere strømmen ved hjælp af naturlig bestilling såvel som bestilling leveret af en Komparator. Til dette har vi to overbelastede varianter af sorteret () API:

  • sortereed () sorterer elementerne i en Strøm ved hjælp af naturlig bestilling elementklassen skal implementere Sammenlignelig interface.
  • sorteret (Comparator super T> Comparator) - sorterer elementerne ud fra en Komparator eksempel

Lad os se et eksempel på, hvordan man gør det brug sorteret () metode med naturlig bestilling:

@Test offentlig endelig ugyldighed givenStreamNaturalOrdering_whenSortingEntitiesByName_thenCorrectlySorted () {List letters = Lists.newArrayList ("B", "A", "C"); Liste sortedLetters = letters.stream (). Sorteret (). Collect (Collectors.toList ()); assertThat (sortedLetters.get (0), equalTo ("A")); }

Lad os nu se, hvordan vi kan brug en brugerdefineret Komparator med sorteret () API:

@Test offentlig endelig ugyldighed givetStreamCustomOrdering_whenSortingEntitiesByName_thenCorrectlySorted () {Liste mennesker = Lists.newArrayList (nyt menneske ("Sarah", 10), nyt menneske ("Jack", 12)); Comparator nameComparator = (h1, h2) -> h1.getName (). SammenlignTo (h2.getName ()); Liste sortedHumans = mennesker.stream (). Sorteret (nameComparator) .collect (Collectors.toList ()); assertThat (sortedHumans.get (0), equalTo (nyt menneske ("Jack", 12))); }

Vi kan forenkle ovenstående eksempel endnu mere, hvis vi brug Comparator.comparing () metode:

@Test offentlig endelig ugyldighed givetStreamComparatorOrdering_whenSortingEntitiesByName_thenCorrectlySorted () {Liste mennesker = Lists.newArrayList (nyt menneske ("Sarah", 10), nyt menneske ("Jack", 12)); Liste sortedHumans = mennesker.stream () .sorteret (Comparator.comparing (Human :: getName)) .collect (Collectors.toList ()); assertThat (sortedHumans.get (0), equalTo (nyt menneske ("Jack", 12))); }

11. Sortering af en liste i omvendt rækkefølge Stream.sorted ()

Vi kan også bruge Stream.sorted () for at sortere en samling i omvendt rækkefølge.

Lad os først se et eksempel på, hvordan man gør det kombinere sorteret () metode med Comparator.reverseOrder () for at sortere en liste i den omvendte naturlige rækkefølge:

@Test offentlig endelig ugyldighed givetStreamNaturalOrdering_whenSortingEntitiesByNameReversed_thenCorrectlySorted () {List letters = Lists.newArrayList ("B", "A", "C"); Liste reverseSortedLetters = letters.stream () .sorted (Comparator.reverseOrder ()) .collect (Collectors.toList ()); assertThat (reverseSortedLetters.get (0), equalTo ("C")); }

Lad os nu se, hvordan vi kan brug sorteret () metode og en brugerdefineret Komparator:

@Test offentlig endelig ugyldighed givetStreamCustomOrdering_whenSortingEntitiesByNameReversed_thenCorrectlySorted () {Liste mennesker = Lists.newArrayList (nyt menneske ("Sarah", 10), nyt menneske ("Jack", 12)); Comparator reverseNameComparator = (h1, h2) -> h2.getName (). SammenlignTo (h1.getName ()); Liste reverseSortedHumans = mennesker.stream (). Sorteret (reverseNameComparator) .collect (Collectors.toList ()); assertThat (reverseSortedHumans.get (0), equalTo (nyt menneske ("Sarah", 10))); }

Bemærk, at påkaldelsen af sammenligne med vendes, hvilket er det, der gør reverseringen.

Lad os endelig forenkle ovenstående eksempel med bruger Comparator.comparing () metode:

@Test offentlig endelig ugyldighed givetStreamComparatorOrdering_whenSortingEntitiesByNameReversed_thenCorrectlySorted () {Liste mennesker = Lists.newArrayList (nyt menneske ("Sarah", 10), nyt menneske ("Jack", 12)); Liste reverseSortedHumans = mennesker.stream () .sorteret (Comparator.comparing (Human :: getName, Comparator.reverseOrder ())) .collect (Collectors.toList ()); assertThat (reverseSortedHumans.get (0), equalTo (nyt menneske ("Sarah", 10))); }

12. Nulværdier

Indtil videre har vi implementeret vores Komparators på en måde, så de ikke kan sortere samlinger, der indeholder nul værdier. Det vil sige, hvis samlingen indeholder mindst en nul element, derefter sortere metoden kaster en NullPointerException:

@Test (forventet = NullPointerException.class) offentlig ugyldighed givetANullElement_whenSortingEntitiesByName_thenThrowsNPE () {Liste mennesker = Lists.newArrayList (null, nyt menneske ("Jack", 12)); mennesker.sort ((h1, h2) -> h1.getName (). sammenlignTo (h2.getName ())); }

Den enkleste løsning er at håndtere nul værdier manuelt i vores Komparator implementering:

@Test offentligt ugyldigt givetANullElement_whenSortingEntitiesByNameManually_thenMovesTheNullToLast () {Liste mennesker = Lists.newArrayList (null, nyt menneske ("Jack", 12), null); mennesker.sort ((h1, h2) -> {if (h1 == null) {return h2 == null? 0: 1;} ellers hvis (h2 == null) {return -1;} return h1.getName ( ) .compareTo (h2.getName ());}); Assert.assertNotNull (mennesker.get (0)); Assert.assertNull (mennesker.get (1)); Assert.assertNull (mennesker.get (2)); }

Her skubber vi alle nul elementer mod slutningen af ​​samlingen. For at gøre det overvejer komparatoren nul at være større end værdier, der ikke er nul. Når begge er nul, betragtes de som lige.

Derudover vi kan passere enhver Komparator det er ikke nul-sikkert i Comparator.nullsLast () metode og opnå det samme resultat:

@Test offentligt ugyldigt givetANullElement_whenSortingEntitiesByName_thenMovesTheNullToLast () {Liste mennesker = Lists.newArrayList (null, nyt menneske ("Jack", 12), null); mennesker.sort (Comparator.nullsLast (Comparator.comparing (Human :: getName))); Assert.assertNotNull (mennesker.get (0)); Assert.assertNull (mennesker.get (1)); Assert.assertNull (mennesker.get (2)); }

På samme måde kan vi bruge Comparator.nullsFirst () at flytte nul elementer mod starten af ​​samlingen:

@Test offentligt ugyldigt givetANullElement_whenSortingEntitiesByName_thenMovesTheNullToStart () {Liste mennesker = Lists.newArrayList (null, nyt menneske ("Jack", 12), null); mennesker.sort (Comparator.nullsFirst (Comparator.comparing (Human :: getName))); Assert.assertNull (mennesker.get (0)); Assert.assertNull (mennesker.get (1)); Assert.assertNotNull (mennesker.get (2)); } 

Det anbefales stærkt at bruge nullsFirst () eller nullsLast () dekoratører, da de er mere fleksible og frem for alt mere læsbare.

13. Konklusion

Denne artikel illustrerede de forskellige og spændende måder, som a Listen kan sorteres ved hjælp af Java 8 Lambda Expressions - bevæger sig lige forbi syntaktisk sukker og ind i ægte og kraftfuld funktionel semantik.

Implementeringen af ​​alle disse eksempler og kodestykker findes på GitHub.