Java 8 - Moćna usporedba s Lambdas

1. Pregled

U ovom uputstvu prvo ćemo pogledati Lambda podrška u Javi 8 - posebno o tome kako je iskoristiti za pisanje Usporednik i sortirajte Zbirku.

Ovaj je članak dio serije "Java - Povratak na osnovno" ovdje na Baeldungu.

Prvo, definirajmo jednostavnu klasu entiteta:

javna klasa Human {ime privatnog niza; privatno int doba; // standardni konstruktori, getteri / postavljači, jednakosti i hashcode} 

2. Osnovna sorta bez lambda

Prije Java 8, sortiranje kolekcije uključivalo bi stvaranje anonimne unutarnje klase za Usporednik koristi se u sortiranju:

novi Usporeditelj () {@Preuzmi javno int uspoređivanje (Human h1, Human h2) {return h1.getName (). compareTo (h2.getName ()); }}

Ovo bi se jednostavno koristilo za sortiranje Popis od Ljudski entiteti:

@Test javna praznina givenPreLambda_whenSortingEntitiesByName_thenCorrectSorted () {Popis ljudi = Lists.newArrayList (new Human ("Sarah", 10), new Human ("Jack", 12)); Collections.sort (ljudi, novi Komparator () {@Preuzmi javno int uspoređivanje (Human h1, Human h2) {return h1.getName (). CompareTo (h2.getName ());}}); Assert.assertThat (people.get (0), jednakTo (novi Human ("Jack", 12))); }

3. Osnovno sortiranje s Lambda podrškom

Uvođenjem Lambdas, sada možemo zaobići anonimnu unutarnju klasu i postići isti rezultat s jednostavna, funkcionalna semantika:

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

Slično - sada možemo testirati ponašanje baš kao i prije:

@Test public void whenSortingEntitiesByName_thenCorrectSorted () {Popis ljudi = Lists.newArrayList (new Human ("Sarah", 10), new Human ("Jack", 12)); people.sort ((Human h1, Human h2) -> h1.getName (). compareTo (h2.getName ())); assertThat (people.get (0), jednakTo (novi Human ("Jack", 12))); }

Primijetite da također koristimo Novi vrsta API dodan u java.util.Popis u Javi 8 - umjesto starog Zbirke.sort API.

4. Osnovno sortiranje bez definicija tipa

Izraz možemo dodatno pojednostaviti nenavođenjem definicija tipova - kompajler je sposoban zaključiti o njima samostalno:

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

I opet, test ostaje vrlo sličan:

@Test javna praznina givenLambdaShortForm_whenSortingEntitiesByName_thenCorrectSorted () {Popis ljudi = Lists.newArrayList (new Human ("Sarah", 10), new Human ("Jack", 12)); people.sort ((h1, h2) -> h1.getName (). compareTo (h2.getName ())); assertThat (people.get (0), jednakTo (novi Human ("Jack", 12))); }

5. Poredaj koristeći referencu na statičku metodu

Dalje ćemo izvršiti sortiranje pomoću Lambda izraza s referencom na statičku metodu.

Prvo ćemo definirati metodu compareByNameThenAge - s potpuno istim potpisom kao i usporedi metoda u a Usporednik objekt:

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

Sada ćemo nazvati ljudi.sort metoda s ovom referencom:

people.sort (Human :: compareByNameThenAge);

Krajnji rezultat je radno sortiranje zbirke korištenjem statičke metode kao a Usporednik:

@Test javna praznina givenMethodDefinition_whenSortingEntitiesByNameThenAge_thenCorrectSorted () {Popis ljudi = Lists.newArrayList (new Human ("Sarah", 10), new Human ("Jack", 12)); people.sort (Human :: compareByNameThenAge); Assert.assertThat (people.get (0), jednakTo (novi Human ("Jack", 12))); }

6. Poredaj izdvojene usporedbe

Također možemo izbjeći definiranje čak i same logike usporedbe korištenjem referenca metode instance i Usporednik.usporedba metoda - koja izdvaja i stvara a Usporedive na temelju te funkcije.

Koristit ćemo geter getName () za izgradnju Lambda izraza i sortiranje popisa po imenu:

@Test javna praznina givenInstanceMethod_whenSortingEntitiesByName_thenCorrectSorted () {Popis ljudi = Lists.newArrayList (new Human ("Sarah", 10), new Human ("Jack", 12)); Collections.sort (ljudi, Comparator.comparing (Human :: getName)); assertThat (people.get (0), jednakTo (novi Human ("Jack", 12))); }

7. Obrnuto sortiranje

JDK 8 je također uveo pomoćnu metodu za obrtanje usporednika - to možemo brzo iskoristiti da preokrenemo svoju sortu:

@Test public void whenSortingEntitiesByNameReversed_thenCorrectSorted () {Popis ljudi = Lists.newArrayList (new Human ("Sarah", 10), new Human ("Jack", 12)); Usporednik za usporedbu = (h1, h2) -> h1.getName (). CompareTo (h2.getName ()); people.sort (comparator.reversed ()); Assert.assertThat (people.get (0), jednakTo (novi Čovjek ("Sarah", 10))); }

8. Poredaj s više uvjeta

Usporedba lambda izraza ne mora biti tako jednostavna - možemo pisati složeniji izrazi - na primjer sortiranje entiteta prvo po imenu, a zatim prema dobi:

@Test public void whenSortingEntitiesByNameThenAge_thenCorrectSorted () {Popis ljudi = Lists.newArrayList (new Human ("Sarah", 12), new Human ("Sarah", 10), new Human ("Zack", 12)); people.sort ((lhs, rhs) -> {if (lhs.getName (). equals (rhs.getName ())) {return Integer.compare (lhs.getAge (), rhs.getAge ());} else {return lhs.getName (). compareTo (rhs.getName ());}}); Assert.assertThat (people.get (0), jednakTo (novi Čovjek ("Sarah", 10))); }

9. Poredaj s više uvjeta - Sastav

Istu logiku usporedbe - prvo razvrstavanje po imenu, a zatim, drugo, prema dobi - također može implementirati nova podrška za sastav za Usporednik.

Počevši od JDK 8, sada možemo povezati više usporednika za izgradnju složenije logike usporedbe:

@Test javna praznina givenComposition_whenSortingEntitiesByNameThenAge_thenCorrectSorted () {Popis ljudi = Lists.newArrayList (new Human ("Sarah", 12), new Human ("Sarah", 10), new Human ("Zack", 12)); people.sort (Comparator.comparing (Human :: getName) .thenComparing (Human :: getAge)); Assert.assertThat (people.get (0), jednakTo (novi Human ("Sarah", 10))); }

10. Sortiranje popisa sa Stream. sortirano ()

Zbirku također možemo sortirati pomoću Jave 8 Streamsortirano () API.

Potok možemo sortirati prirodnim poredkom, kao i redoslijedom koji pruža a Usporednik. Za to imamo dvije preopterećene inačice sortirano () API:

  • vrstaizd. () razvrstava elemente a Stream koristeći prirodni poredak; klasa elemenata mora implementirati Usporedive sučelje.
  • sortirano (Usporednik super T> komparator) - sortira elemente na temelju a Usporednik primjer

Pogledajmo primjer kako koristiti sortirano () metoda s prirodnim redoslijedom:

@Test javna konačna praznina givenStreamNaturalOrdering_whenSortingEntitiesByName_thenCorrectSorted () {Popis slova = Lists.newArrayList ("B", "A", "C"); Popis sortedLetters = letters.stream (). Sorted (). Collect (Collectors.toList ()); assertThat (sortedLetters.get (0), jednakTo ("A")); }

Sada da vidimo kako možemo koristiti običaj Usporednik s sortirano () API:

@Test javna konačna praznina givenStreamCustomOrdering_whenSortingEntitiesByName_thenCorrectSorted () {Popis ljudi = Lists.newArrayList (new Human ("Sarah", 10), new Human ("Jack", 12)); Ime usporedbeComparator = (h1, h2) -> h1.getName (). CompareTo (h2.getName ()); Popis sortedHumans = people.stream (). Sorted (nameComparator) .collect (Collectors.toList ()); assertThat (sortedHumans.get (0), jednakTo (novi Čovjek ("Jack", 12))); }

Gornji primjer možemo još više pojednostaviti ako to učinimo koristiti Usporednik.comupoređivanje () metoda:

@Test javna konačna praznina givenStreamComparatorOrdering_whenSortingEntitiesByName_thenCorrectSorted () {List people = Lists.newArrayList (new Human ("Sarah", 10), new Human ("Jack", 12)); Popis sortedHumans = people.stream (). Sortirano (Comparator.comparing (Human :: getName)) .collect (Collectors.toList ()); assertThat (sortedHumans.get (0), jednakTo (novi Čovjek ("Jack", 12))); }

11. Sortiranje popisa obrnuto Stream. sortirano ()

Možemo i koristiti Stream. sortirano () za sortiranje zbirke obrnuto.

Prvo, pogledajmo primjer kako kombinirati sortirano () metoda sa Usporednik.reverseOrder () za sortiranje popisa obrnutim prirodnim redoslijedom:

@Test javna konačna praznina givenStreamNaturalOrdering_whenSortingEntitiesByNameReversed_thenCorrectSorted () {Slova s ​​popisa = Lists.newArrayList ("B", "A", "C"); Popis reverseSortedLetters = letters.stream (). Sortirano (Comparator.reverseOrder ()) .collect (Collectors.toList ()); assertThat (reverseSortedLetters.get (0), jednakTo ("C")); }

Sada, da vidimo kako možemo koristiti sortirano () metoda i običaj Usporednik:

@Test javna konačna praznina givenStreamCustomOrdering_whenSortingEntitiesByNameReversed_thenCorrectSorted () {List people = Lists.newArrayList (new Human ("Sarah", 10), new Human ("Jack", 12)); Usporednik reverseNameComparator = (h1, h2) -> h2.getName (). CompareTo (h1.getName ()); Popis reverseSortedHumans = people.stream (). Sorted (reverseNameComparator) .collect (Collectors.toList ()); assertThat (reverseSortedHumans.get (0), jednakTo (novi Čovjek ("Sarah", 10))); }

Imajte na umu da je pozivanje usporediTo se okreće, što je ono što radi unatrag.

Napokon, pojednostavnimo gornji primjer pomoću koristiti Usporednik.comupoređivanje () metoda:

@Test javna konačna praznina givenStreamComparatorOrdering_whenSortingEntitiesByNameReversed_thenCorrectSorted () {List people = Lists.newArrayList (new Human ("Sarah", 10), new Human ("Jack", 12)); Popis reverseSortedHumans = people.stream (). Sortirano (Comparator.comparing (Human :: getName, Comparator.reverseOrder ())) .collect (Collectors.toList ()); assertThat (reverseSortedHumans.get (0), jednakTo (novi Čovjek ("Sarah", 10))); }

12. Nulti vrijednosti

Do sada smo implementirali svoje Usporedniks na način da ne mogu sortirati zbirke koje sadrže null vrijednosti. Odnosno, ako zbirka sadrži barem jedan null element, zatim vrsta metoda baca a NullPointerException:

@Test (očekuje se = NullPointerException.class) javna praznina givenANullElement_whenSortingEntitiesByName_thenThrowsNPE () {Popis ljudi = Lists.newArrayList (null, new Human ("Jack", 12)); people.sort ((h1, h2) -> h1.getName (). compareTo (h2.getName ())); }

Najjednostavnije rješenje je rukovanje null vrijednosti ručno u našem Usporednik provedba:

@Test public void givenANullElement_whenSortingEntitiesByNameManually_thenMovesTheNullToLast () {List people = Lists.newArrayList (null, new 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 (people.get (0)); Assert.assertNull (people.get (1)); Assert.assertNull (people.get (2)); }

Ovdje guramo sve null elementi pred kraj zbirke. Da bi to učinio, uspoređivač razmatra null biti veće od ne-null vrijednosti. Kad su oboje null, smatraju se jednakima.

Dodatno, možemo proći bilo koji Usporednik koji nije nula-siguran u Usporednik.nullsLast () metodom i postići isti rezultat:

@Test public void givenANullElement_whenSortingEntitiesByName_thenMovesTheNullToLast () {List people = Lists.newArrayList (null, new Human ("Jack", 12), null); people.sort (Comparator.nullsLast (Comparator.comparing (Human :: getName))); Assert.assertNotNull (people.get (0)); Assert.assertNull (people.get (1)); Assert.assertNull (people.get (2)); }

Slično tome, možemo koristiti Usporednik.nullsFirst () za pomicanje null elementi prema početku zbirke:

@Test javna praznina givenANullElement_whenSortingEntitiesByName_thenMovesTheNullToStart () {List people = Lists.newArrayList (null, new Human ("Jack", 12), null); people.sort (Comparator.nullsFirst (Comparator.comparing (Human :: getName))); Assert.assertNull (people.get (0)); Assert.assertNull (people.get (1)); Assert.assertNotNull (people.get (2)); } 

Preporučuje se korištenje nullsFirst () ili nullsLast () dekoratori, jer su fleksibilniji i nadasve čitljiviji.

13. Zaključak

Ovaj je članak ilustrirao razne i uzbudljive načine na koje a Popis se može sortirati pomoću Java 8 Lambda Expressions - prelazak iz sintaksičkog šećera u pravu i snažnu funkcionalnu semantiku.

Provedbu svih ovih primjera i isječaka koda možete pronaći na GitHubu.