Java 8 - Krachtige vergelijking met Lambdas

1. Overzicht

In deze tutorial gaan we een eerste blik werpen op de Lambda-ondersteuning in Java 8 - specifiek over hoe u deze kunt gebruiken om het Comparator en sorteer een collectie.

Dit artikel maakt deel uit van de serie "Java - Back to Basic" hier op Baeldung.

Laten we eerst een eenvoudige entiteitsklasse definiëren:

openbare klasse Human {private String-naam; privé int leeftijd; // standard constructors, getters / setters, equals en hashcode} 

2. Basissortering zonder Lambdas

Vóór Java 8 zou het sorteren van een verzameling het creëren van een anonieme innerlijke klasse voor de Comparator gebruikt in de soort:

nieuwe Comparator () {@Override public int Compare (Human h1, Human h2) {return h1.getName (). CompareTo (h2.getName ()); }}

Dit zou gewoon worden gebruikt om de Lijst van Mens entiteiten:

@Test openbare ongeldig gegevenPreLambda_whenSortingEntitiesByName_thenCorrectlySorted () {List mensen = Lists.newArrayList (nieuwe mens ("Sarah", 10), nieuwe mens ("Jack", 12)); Collections.sort (mensen, nieuwe Comparator () {@Override public int Compare (Human h1, Human h2) {return h1.getName (). CompareTo (h2.getName ());}}); Assert.assertThat (people.get (0), equalTo (new Human ("Jack", 12))); }

3. Basissortering met Lambda-ondersteuning

Met de introductie van Lambdas kunnen we nu de anonieme innerlijke klasse omzeilen en hetzelfde resultaat bereiken met eenvoudige, functionele semantiek:

(final Human h1, final Human h2) -> h1.getName (). CompareTo (h2.getName ());

Evenzo - we kunnen het gedrag nu net als voorheen testen:

@Test public void whenSortingEntitiesByName_thenCorrectlySorted () {List mensen = Lists.newArrayList (nieuwe mens ("Sarah", 10), nieuwe mens ("Jack", 12)); people.sort ((Human h1, Human h2) -> h1.getName (). CompareTo (h2.getName ())); assertThat (people.get (0), equalTo (new Human ("Jack", 12))); }

Merk op dat we ook gebruik maken van de nieuwe soort API toegevoegd aan java.util.List in Java 8 - in plaats van het oude Collections.sort API.

4. Basissortering zonder typedefinities

We kunnen de uitdrukking verder vereenvoudigen door de typedefinities niet op te geven - de compiler kan deze afleiden op zichzelf:

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

En nogmaals, de test blijft erg vergelijkbaar:

@Test openbare leegte gegevenLambdaShortForm_whenSortingEntitiesByName_thenCorrectlySorted () {List mensen = Lists.newArrayList (nieuwe mens ("Sarah", 10), nieuwe mens ("Jack", 12)); people.sort ((h1, h2) -> h1.getName (). CompareTo (h2.getName ())); assertThat (people.get (0), equalTo (new Human ("Jack", 12))); }

5. Sorteer met verwijzing naar statische methode

Vervolgens gaan we de sortering uitvoeren met een Lambda-expressie met een verwijzing naar een statische methode.

Eerst gaan we de methode definiëren CompareByNameThenAge - met exact dezelfde handtekening als de vergelijken methode in een Comparator voorwerp:

public static int CompareByNameThenAge (Human lhs, Human rhs) {if (lhs.name.equals (rhs.name)) {return Integer.compare (lhs.age, rhs.age); } anders {retourneer lhs.name.compareTo (rhs.name); }}

Nu gaan we de mensen sorteren methode met deze referentie:

people.sort (Human :: CompareByNameThenAge);

Het eindresultaat is een werkende sortering van de collectie met behulp van de statische methode als een Comparator:

@Test openbare leegte gegevenMethodDefinition_whenSortingEntitiesByNameThenAge_thenCorrectlySorted () {List mensen = Lists.newArrayList (nieuwe mens ("Sarah", 10), nieuwe mens ("Jack", 12)); people.sort (Human :: CompareByNameThenAge); Assert.assertThat (people.get (0), equalTo (new Human ("Jack", 12))); }

6. Sorteer geëxtraheerde vergelijkers

We kunnen ook vermijden om zelfs de vergelijkingslogica zelf te definiëren door een instantie methode referentie en de Comparator.comparing methode - die extraheert en een Vergelijkbaar op basis van die functie.

We gaan de vangstof gebruiken getName () om de Lambda-expressie te bouwen en de lijst op naam te sorteren:

@Test openbare leegte gegevenInstanceMethod_whenSortingEntitiesByName_thenCorrectlySorted () {List mensen = Lists.newArrayList (nieuwe mens ("Sarah", 10), nieuwe mens ("Jack", 12)); Collections.sort (mensen, Comparator.comparing (Human :: getName)); assertThat (people.get (0), equalTo (new Human ("Jack", 12))); }

7. Omgekeerd sorteren

JDK 8 heeft ook een hulpmethode geïntroduceerd voor omkeren van de comparator - daar kunnen we snel gebruik van maken om onze soort om te keren:

@Test public void whenSortingEntitiesByNameReversed_thenCorrectlySorted () {List mensen = Lists.newArrayList (nieuwe mens ("Sarah", 10), nieuwe mens ("Jack", 12)); Comparator comparator = (h1, h2) -> h1.getName (). CompareTo (h2.getName ()); mensen.sorteren (comparator.reversed ()); Assert.assertThat (people.get (0), equalTo (new Human ("Sarah", 10))); }

8. Sorteer met meerdere voorwaarden

De lambda-uitdrukkingen voor vergelijking hoeven niet zo eenvoudig te zijn - we kunnen schrijven ook meer complexe uitdrukkingen - bijvoorbeeld de entiteiten eerst op naam en vervolgens op leeftijd sorteren:

@Test public void whenSortingEntitiesByNameThenAge_thenCorrectlySorted () {List mensen = Lists.newArrayList (nieuwe mens ("Sarah", 12), nieuwe mens ("Sarah", 10), nieuwe mens ("Zack", 12)); people.sort ((lhs, rhs) -> {if (lhs.getName (). equals (rhs.getName ())) {return Integer.compare (lhs.getAge (), rhs.getAge ());} anders {return lhs.getName (). CompareTo (rhs.getName ());}}); Assert.assertThat (people.get (0), equalTo (new Human ("Sarah", 10))); }

9. Sorteren met meerdere voorwaarden - Samenstelling

Dezelfde vergelijkingslogica - eerst sorteren op naam en vervolgens, secundair, op leeftijd - kan ook worden geïmplementeerd door de nieuwe compositieondersteuning voor Comparator.

Beginnend met JDK 8, kunnen we nu meerdere comparatoren aan elkaar koppelen om een ​​complexere vergelijkingslogica te bouwen:

@Test openbare leegte gegevenComposition_whenSortingEntitiesByNameThenAge_thenCorrectlySorted () {List mensen = Lists.newArrayList (nieuwe mens ("Sarah", 12), nieuwe mens ("Sarah", 10), nieuwe mens ("Zack", 12)); people.sort (Comparator.comparing (Human :: getName) .thenComparing (Human :: getAge)); Assert.assertThat (people.get (0), equalTo (new Human ("Sarah", 10))); }

10. Een lijst sorteren met Stream. Gesorteerd ()

We kunnen een collectie ook sorteren met behulp van Java 8's Stroomgesorteerd () API.

We kunnen de stream sorteren door middel van natuurlijke ordening en door een Comparator. Hiervoor hebben we twee overbelaste varianten van de gesorteerd () API:

  • soorted () sorteert de elementen van een Stroom gebruikmakend van natuurlijke ordening; de elementklasse moet de Vergelijkbaar koppel.
  • gesorteerd (Comparator super T> comparator) - sorteert de elementen op basis van een Comparator voorbeeld

Laten we eens kijken hoe dat moet gebruik de gesorteerd () methode met natuurlijke ordening:

@Test openbare definitieve leegte gegevenStreamNaturalOrdering_whenSortingEntitiesByName_thenCorrectlySorted () {List letters = Lists.newArrayList ("B", "A", "C"); Lijst gesorteerdeLetters = letters.stream (). Gesorteerde (). Collect (Collectors.toList ()); assertThat (gesorteerdeLetters.get (0), equalTo ("A")); }

Laten we nu eens kijken hoe we dat kunnen gebruik een gewoonte Comparator met de gesorteerd () API:

@Test openbare definitieve ongeldige gegevenStreamCustomOrdering_whenSortingEntitiesByName_thenCorrectlySorted () {List mensen = Lists.newArrayList (nieuwe mens ("Sarah", 10), nieuwe mens ("Jack", 12)); Comparator nameComparator = (h1, h2) -> h1.getName (). CompareTo (h2.getName ()); Lijst gesorteerdHumans = mensen.stream (). Gesorteerd (naamComparator) .collect (Collectors.toList ()); assertThat (gesorteerdHumans.get (0), equalTo (new Human ("Jack", 12))); }

We kunnen het bovenstaande voorbeeld nog verder vereenvoudigen als we dat doen gebruik de Comparator.comparing () methode:

@Test openbare definitieve ongeldige gegevenStreamComparatorOrdering_whenSortingEntitiesByName_thenCorrectlySorted () {List mensen = Lists.newArrayList (nieuwe mens ("Sarah", 10), nieuwe mens ("Jack", 12)); Lijst gesorteerdHumans = mensen.stream (). Gesorteerd (Comparator.comparing (Human :: getName)) .collect (Collectors.toList ()); assertThat (gesorteerdHumans.get (0), equalTo (new Human ("Jack", 12))); }

11. Een lijst omgekeerd sorteren met Stream. Gesorteerd ()

We kunnen ook gebruik maken van Stream. Gesorteerd () om een ​​verzameling in omgekeerde volgorde te sorteren.

Laten we eerst een voorbeeld bekijken van hoe combineer de gesorteerd () methode met Comparator.reverseOrder () om een ​​lijst in omgekeerde natuurlijke volgorde te sorteren:

@Test openbare definitieve leegte gegevenStreamNaturalOrdering_whenSortingEntitiesByNameReversed_thenCorrectlySorted () {List letters = Lists.newArrayList ("B", "A", "C"); Lijst reverseSortedLetters = letters.stream () .sorted (Comparator.reverseOrder ()) .collect (Collectors.toList ()); assertThat (reverseSortedLetters.get (0), equalTo ("C")); }

Laten we nu eens kijken hoe we dat kunnen gebruik de gesorteerd () methode en een gewoonte Comparator:

@Test openbare definitieve ongeldige gegevenStreamCustomOrdering_whenSortingEntitiesByNameReversed_thenCorrectlySorted () {List mensen = Lists.newArrayList (nieuwe mens ("Sarah", 10), nieuwe mens ("Jack", 12)); Comparator reverseNameComparator = (h1, h2) -> h2.getName (). CompareTo (h1.getName ()); Lijst reverseSortedHumans = mensen.stream (). Gesorteerd (reverseNameComparator) .collect (Collectors.toList ()); assertThat (reverseSortedHumans.get (0), equalTo (new Human ("Sarah", 10))); }

Merk op dat het aanroepen van vergelijk met wordt omgedraaid, wat het omkeren doet.

Laten we tot slot het bovenstaande voorbeeld vereenvoudigen door de ... gebruiken Comparator.comparing () methode:

@Test openbare definitieve leegte gegevenStreamComparatorOrdering_whenSortingEntitiesByNameReversed_thenCorrectlySorted () {List mensen = Lists.newArrayList (nieuwe mens ("Sarah", 10), nieuwe mens ("Jack", 12)); Lijst reverseSortedHumans = people.stream () .sorted (Comparator.comparing (Human :: getName, Comparator.reverseOrder ())) .collect (Collectors.toList ()); assertThat (reverseSortedHumans.get (0), equalTo (new Human ("Sarah", 10))); }

12. Null-waarden

Tot nu toe hebben we onze Comparators op een manier dat ze collecties met nul waarden. Dat wil zeggen, als de collectie er minstens één bevat nul element, dan is het soort methode gooit een NullPointerException:

@Test (verwacht = NullPointerException.class) openbare ongeldige gegevenANullElement_whenSortingEntitiesByName_thenThrowsNPE () {List mensen = Lists.newArrayList (null, nieuwe Human ("Jack", 12)); people.sort ((h1, h2) -> h1.getName (). CompareTo (h2.getName ())); }

De eenvoudigste oplossing is om de nul waarden handmatig in onze Comparator implementatie:

@Test openbare leegte gegevenANullElement_whenSortingEntitiesByNameManually_thenMovesTheNullToLast () {List mensen = Lists.newArrayList (null, nieuwe Human ("Jack", 12), null); people.sort ((h1, h2) -> {if (h1 == null) {return h2 == null? 0: 1;} else if (h2 == null) {return -1;} return h1.getName ( ) .compareTo (h2.getName ());}); Assert.assertNotNull (mensen.get (0)); Assert.assertNull (mensen.get (1)); Assert.assertNull (mensen.get (2)); }

Hier pushen we alles nul elementen tegen het einde van de collectie. Om dat te doen, overweegt de vergelijker nul groter zijn dan niet-null-waarden. Als beide dat zijn nul, worden ze als gelijk beschouwd.

Bovendien, we kunnen ze passeren Comparator dat is niet nulveilig in het Comparator.nullsLast () methode en bereik hetzelfde resultaat:

@Test openbare leegte gegevenANullElement_whenSortingEntitiesByName_thenMovesTheNullToLast () {List mensen = Lists.newArrayList (null, nieuwe Human ("Jack", 12), null); mensen.sort (Comparator.nullsLast (Comparator.comparing (Human :: getName))); Assert.assertNotNull (mensen.get (0)); Assert.assertNull (mensen.get (1)); Assert.assertNull (mensen.get (2)); }

Evenzo kunnen we gebruiken Comparator.nullsFirst () om het nul elementen richting het begin van de collectie:

@Test openbare leegte gegevenANullElement_whenSortingEntitiesByName_thenMovesTheNullToStart () {List mensen = Lists.newArrayList (null, nieuwe Human ("Jack", 12), null); mensen.sort (Comparator.nullsFirst (Comparator.comparing (Human :: getName))); Assert.assertNull (mensen.get (0)); Assert.assertNull (mensen.get (1)); Assert.assertNotNull (mensen.get (2)); } 

Het wordt ten zeerste aanbevolen om het nullsFirst () of nullsLast () decorateurs, omdat ze flexibeler en vooral beter leesbaar zijn.

13. Conclusie

Dit artikel illustreert de verschillende en opwindende manieren waarop a Lijst kan worden gesorteerd met Java 8 Lambda Expressions - voorbij syntactische suiker gaan en naar echte en krachtige functionele semantiek gaan.

De implementatie van al deze voorbeelden en codefragmenten is te vinden op GitHub.