Java 8 - silné porovnanie s Lambdas

1. Prehľad

V tomto výučbe sa najskôr pozrieme na Podpora lambda v prostredí Java 8 - konkrétne o tom, ako ju využiť na napísanie súboru Komparátor a triediť zbierku.

Tento článok je súčasťou série „Java - Späť na základné“ tu na Baeldungu.

Najprv definujeme jednoduchú triedu entít:

public class Human {private String name; súkromný int vek; // štandardné konštruktory, getre / setre, rovné a hashcode} 

2. Základné triedenie bez lambd

Pred programom Java 8 by triedenie zbierky zahŕňalo vytvorenie anonymnej vnútornej triedy pre Komparátor použité v druhu:

new Comparator () {@Override public int compare (Human h1, Human h2) {return h1.getName (). compareTo (h2.getName ()); }}

Toto by sa jednoducho použilo na triedenie Zoznam z Človek subjekty:

@Test public void givenPreLambda_whenSortingEntitiesByName_thenCorrectlySorted () {List human = Lists.newArrayList (new Human ("Sarah", 10), new Human ("Jack", 12)); Collections.sort (ľudia, nový komparátor () {@Override public int compare (človek h1, človek h2) {návrat h1.getName (). CompareTo (h2.getName ());}}); Assert.assertThat (human.get (0), equalTo (nový človek ("Jack", 12))); }

3. Základné triedenie s podporou lambda

Zavedením Lambdasa môžeme teraz obísť anonymnú vnútornú triedu a dosiahnuť rovnaký výsledok s jednoduchá, funkčná sémantika:

(konečný človek h1, konečný človek h2) -> h1.getName (). compareTo (h2.getName ());

Podobne - teraz môžeme testovať správanie rovnako ako predtým:

@Test public void whenSortingEntitiesByName_thenCorrectlySorted () {List human = Lists.newArrayList (new Human ("Sarah", 10), new Human ("Jack", 12)); human.sort ((Human h1, Human h2) -> h1.getName (). compareTo (h2.getName ())); assertThat (human.get (0), equalTo (nový človek ("Jack", 12))); }

Všimnite si, že tiež používame nové triediť API pridané do java.util.List v prostredí Java 8 - namiesto starého Zbierky.triediť API.

4. Základné triedenie bez definícií typu

Ďalej môžeme výraz zjednodušiť neurčením definícií typov - kompilátor je schopný ich odvodiť sama o sebe:

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

Test opäť zostáva veľmi podobný:

@Test public void givenLambdaShortForm_whenSortingEntitiesByName_thenCorrectlySorted () {List human = Lists.newArrayList (new Human ("Sarah", 10), new Human ("Jack", 12)); human.sort ((h1, h2) -> h1.getName (). compareTo (h2.getName ())); assertThat (human.get (0), equalTo (nový človek ("Jack", 12))); }

5. Zoraďte pomocou odkazu na statickú metódu

Ďalej uskutočníme triedenie pomocou Lambda výrazu s odkazom na statickú metódu.

Najskôr definujeme metódu compareByNameThenAge - s presne rovnakým podpisom ako porovnaj metóda v a Komparátor 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); }}

Teraz zavoláme ľudia.triediť metóda s týmto odkazom:

people.sort (Human :: compareByNameThenAge);

Konečným výsledkom je pracovné triedenie zbierky pomocou statickej metódy ako a Komparátor:

@Test public void givenMethodDefinition_whenSortingEntitiesByNameThenAge_thenCorrectlySorted () {List human = Lists.newArrayList (new Human ("Sarah", 10), new Human ("Jack", 12)); people.sort (Human :: compareByNameThenAge); Assert.assertThat (human.get (0), equalTo (nový človek ("Jack", 12))); }

6. Zoraďte extrahované komparátory

Tiež sa môžeme vyhnúť definovaniu samotnej porovnávacej logiky pomocou znaku odkaz na inštančnú metódu a Comparator.comparing metóda - ktorá extrahuje a vytvára a Porovnateľné na základe tejto funkcie.

Použijeme getr getName () vytvoriť výraz Lambda a zoradiť zoznam podľa názvu:

@Test public void givenInstanceMethod_whenSortingEntitiesByName_thenCorrectlySorted () {List human = Lists.newArrayList (new Human ("Sarah", 10), new Human ("Jack", 12)); Collections.sort (ľudia, Comparator.comparing (Human :: getName)); assertThat (human.get (0), equalTo (nový človek ("Jack", 12))); }

7. Spätné triedenie

JDK 8 tiež zaviedol pomocnú metódu pre obrátenie komparátora - môžeme to rýchlo využiť na zvrátenie nášho druhu:

@Test public void whenSortingEntitiesByNameReversed_thenCorrectlySorted () {List human = Lists.newArrayList (new Human ("Sarah", 10), new Human ("Jack", 12)); Komparátor komparátor = (h1, h2) -> h1.getName (). CompareTo (h2.getName ()); human.sort (comparator.reversed ()); Assert.assertThat (human.get (0), equalTo (nový človek ("Sarah", 10))); }

8. Zoradiť podľa viacerých podmienok

Porovnávacie výrazy lambda nemusia byť také jednoduché - môžeme písať aj zložitejšie výrazy - napríklad zoradenie entít najskôr podľa názvu a potom podľa veku:

@Test public void whenSortingEntitiesByNameThenAge_thenCorrectlySorted () {List human = 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 (human.get (0), equalTo (nový človek ("Sarah", 10))); }

9. Zoradiť podľa viacerých podmienok - zloženie

Rovnakú logiku porovnávania - najskôr triedenie podľa mena a potom, sekundárne, podľa veku - možno implementovať aj pomocou podpory nového zloženia pre Komparátor.

Počnúc JDK 8 môžeme teraz spojiť dohromady viac komparátorov vytvoriť zložitejšiu logiku porovnávania:

@Test public void givenComposition_whenSortingEntitiesByNameThenAge_thenCorrectlySorted () {List human = Lists.newArrayList (new Human ("Sarah", 12), new Human ("Sarah", 10), new Human ("Zack", 12)); human.sort (Comparator.comparing (Human :: getName) .thenComparing (Human :: getAge)); Assert.assertThat (human.get (0), equalTo (nový človek ("Sarah", 10))); }

10. Triedenie zoznamu pomocou Stream.sorted ()

Môžeme tiež triediť zbierku pomocou Java 8 Prúdzoradené () API.

Stream môžeme triediť pomocou prirodzeného poradia aj podľa poradia poskytnutého a Komparátor. Na tento účel máme dve preťažené varianty zoradené () API:

  • triediťed () triedi prvky a Prúd pomocou prirodzeného usporiadania; trieda prvkov musí implementovať Porovnateľné rozhranie.
  • zoradené (komparátor Super T> komparator) - triedi prvky na základe a Komparátor inštancia

Pozrime sa na príklad, ako na to Použi zoradené () metóda s prirodzeným usporiadaním:

@Test public final void givenStreamNaturalOrdering_whenSortingEntitiesByName_thenCorrectlySorted () {List letters = Lists.newArrayList ("B", "A", "C"); Zoznam triedené písmená = letters.stream (). Triedené (). Collect (Collectors.toList ()); assertThat (sortLetters.get (0), equalTo ("A")); }

Teraz sa pozrime, ako môžeme použite zvyk Komparátor s zoradené () API:

@Test public final void givenStreamCustomOrdering_whenSortingEntitiesByName_thenCorrectlySorted () {List human = Lists.newArrayList (new Human ("Sarah", 10), new Human ("Jack", 12)); Názov komparátoraComparator = (h1, h2) -> h1.getName (). CompareTo (h2.getName ()); Zoznam triedenýchHumans = human.stream (). Triedených (nameComparator) .collect (Collectors.toList ()); assertThat (seřazenéHumans.get (0), rovnakeTo (nový Človek ("Jack", 12))); }

Vyššie uvedený príklad môžeme ešte viac zjednodušiť, ak Použi Comparator.comparing () metóda:

@Test public final void givenStreamComparatorOrdering_whenSortingEntitiesByName_thenCorrectlySorted () {List human = Lists.newArrayList (new Human ("Sarah", 10), new Human ("Jack", 12)); Zoznam seřazenéHumans = human.stream () .sorted (Comparator.comparing (Human :: getName)) .collect (Collectors.toList ()); assertThat (seřazenéHumans.get (0), rovnakeTo (nový Človek ("Jack", 12))); }

11. Zoradenie zoznamu v opačnom poradí Stream.sorted ()

Môžeme tiež použiť Stream.sorted () triediť zbierku opačne.

Najprv sa pozrime na príklad, ako na to kombinovať zoradené () metóda s Comparator.reverseOrder () zoradiť zoznam v opačnom prirodzenom poradí:

@Test public final void givenStreamNaturalOrdering_whenSortingEntitiesByNameReversed_thenCorrectlySorted () {List letters = Lists.newArrayList ("B", "A", "C"); Zoznam reverseSortedLetters = letters.stream () .sorted (Comparator.reverseOrder ()) .collect (Collectors.toList ()); assertThat (reverseSortedLetters.get (0), equalTo ("C")); }

Teraz sa pozrime, ako môžeme Použi zoradené () metóda a zvyk Komparátor:

@Test public final void givenStreamCustomOrdering_whenSortingEntitiesByNameReversed_thenCorrectlySorted () {List human = Lists.newArrayList (new Human ("Sarah", 10), new Human ("Jack", 12)); Komparátor reverseNameComparator = (h1, h2) -> h2.getName (). CompareTo (h1.getName ()); Zoznam reverseSortedHumans = people.stream (). Triedené (reverseNameComparator) .collect (Collectors.toList ()); assertThat (reverseSortedHumans.get (0), equalTo (nový človek ("Sarah", 10))); }

Upozorňujeme, že vyvolanie porovnať s je otočený, čo robí cúvanie.

Na záver si vyššie uvedený príklad zjednodušíme o pomocou Comparator.comparing () metóda:

@Test public final void givenStreamComparatorOrdering_whenSortingEntitiesByNameReversed_thenCorrectlySorted () {List human = Lists.newArrayList (new Human ("Sarah", 10), new Human ("Jack", 12)); Zoznam reverseSortedHumans = human.stream () .sorted (Comparator.comparing (Human :: getName, Comparator.reverseOrder ())) .collect (Collectors.toList ()); assertThat (reverseSortedHumans.get (0), equalTo (nový človek ("Sarah", 10))); }

12. Nulové hodnoty

Zatiaľ sme implementovali naše Komparátorspôsobom, že nemôžu triediť zbierky obsahujúce nulový hodnoty. Teda v prípade, že zbierka obsahuje aspoň jednu nulový prvok, potom triediť metóda hodí a NullPointerException:

@Test (očakáva sa = NullPointerException.class) public void givenANullElement_whenSortingEntitiesByName_thenThrowsNPE () {List human = Lists.newArrayList (null, new Human ("Jack", 12)); human.sort ((h1, h2) -> h1.getName (). compareTo (h2.getName ())); }

Najjednoduchším riešením je zvládnuť nulový hodnoty ručne v našom Komparátor implementácia:

@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)); }

Tu tlačíme všetkých nulový prvky na konci zbierky. Za týmto účelom porovnateľ zváži nulový byť väčšie ako nenulové hodnoty. Keď sú obaja nulový, považujú sa za rovnocenné.

Navyše, môžeme prejsť ľubovoľným Komparátor to nie je bezpečné z hľadiska nuly do Comparator.nullsLast () metódou a dosiahnuť rovnaký výsledok:

@Test public void givenANullElement_whenSortingEntitiesByName_thenMovesTheNullToLast () {List human = 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)); }

Podobne môžeme použiť Comparator.nullsFirst () presunúť nulový prvky na začiatku zbierky:

@Test public void givenANullElement_whenSortingEntitiesByName_thenMovesTheNullToStart () {List human = 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)); } 

Dôrazne sa odporúča používať nullsFirst () alebo nullsLast () dekoratéry, pretože sú pružnejšie a hlavne čitateľnejšie.

13. Záver

Tento článok ilustroval rôzne a zaujímavé spôsoby, ktoré a Zoznam je možné triediť pomocou výrazov Java 8 Lambda Expressions - prechod priamo okolo syntaktického cukru a do skutočnej a výkonnej funkčnej sémantiky.

Implementáciu všetkých týchto príkladov a útržkov kódu nájdete na GitHub.


$config[zx-auto] not found$config[zx-overlay] not found