Java-gemaksfabrieksmethoden voor verzamelingen

1. Overzicht

Java 9 brengt de langverwachte syntactische suiker voor het maken van kleine niet-wijzigbare Verzameling instanties met behulp van een beknopte code one-liner. Volgens JEP 269 zullen nieuwe gemaksfabrieksmethoden worden opgenomen in JDK 9.

In dit artikel bespreken we het gebruik ervan samen met de implementatiedetails.

2. Geschiedenis en motivatie

Creëer een klein onveranderlijk Verzameling in Java is erg uitgebreid op de traditionele manier.

Laten we een voorbeeld nemen van een Set:

Set set = new HashSet (); set.add ("foo"); set.add ("balk"); set.add ("baz"); set = Collections.unmodifiableSet (set);

Dat is veel te veel code voor een simpele taak en het zou mogelijk moeten zijn om in een enkele uitdrukking te doen.

Het bovenstaande geldt ook voor een Kaart.

Echter, voor Lijst, er is een fabrieksmethode:

List list = Arrays.asList ("foo", "bar", "baz");

Hoewel dit Lijst maken is beter dan de constructorinitialisatie, dit is minder voor de hand liggend omdat de algemene intuïtie niet zou zijn om naar te kijken Arrays class voor methoden om een Lijst:

Er zijn andere manieren om breedsprakigheid te verminderen, zoals de initialisatie met dubbele accolade techniek:

Set set = Collections.unmodifiableSet (nieuwe HashSet () {{add ("foo"); add ("bar"); add ("baz");}});

of door Java 8 te gebruiken Streams:

Stream.of ("foo", "bar", "baz") .collect (collectionAndThen (toSet (), Collections :: unmodifiableSet));

De dubbele beugeltechniek is slechts iets minder uitgebreid, maar vermindert de leesbaarheid aanzienlijk (en wordt als een antipatroon beschouwd).

De Java 8-versie is echter een uitdrukking in één regel en er zijn ook enkele problemen. Ten eerste is het niet duidelijk en intuïtief. Ten tweede is het nog steeds uitgebreid. Ten derde gaat het om het creëren van onnodige objecten. En ten vierde kan deze methode niet worden gebruikt voor het maken van een Kaart.

Om de tekortkomingen samen te vatten: geen van de bovenstaande benaderingen behandelt de specifieke use-case en creëert een kleine niet-aanpasbare Verzameling eersteklas probleem.

3. Beschrijving en gebruik

Er is voorzien in statische methoden Lijst, Set, en Kaart interfaces die de elementen als argumenten nemen en een instantie van Lijst, Set, en Kaart, respectievelijk.

Deze methode wordt genoemd van(…) voor alle drie de interfaces.

3.1. Lijst en Set

De handtekening en kenmerken van Lijst en Set fabrieksmethoden zijn hetzelfde:

statisch Lijst van (E e1, E e2, E e3) statische Set van (E e1, E e2, E e3)

gebruik van de methoden:

List list = List.of ("foo", "bar", "baz"); Set set = Set.of ("foo", "bar", "baz");

Zoals we kunnen zien, is het heel eenvoudig, kort en beknopt.

In het voorbeeld hebben we de methode gebruikt met exact drie elementen als parameters en retourneert een Lijst / Set van maat 3.

Maar er zijn 12 overbelaste versies van deze methode - elf met 0 tot 10 parameters en één met var-args:

static Lijst van () statische Lijst van (E e1) statische Lijst van (E e1, E e2) // .... enzovoort statische Lijst van (E ... elems)

Voor de meeste praktische doeleinden zouden 10 elementen voldoende zijn, maar als er meer nodig zijn, kan de var-args-versie worden gebruikt.

Nu kunnen we ons afvragen, wat heeft het voor zin om 11 extra methoden te hebben als er een var-args-versie is die voor een willekeurig aantal elementen kan werken.

Het antwoord daarop is prestatie. Elke aanroep van de methode var-args maakt impliciet een array aan. Door de overbelaste methoden te gebruiken, worden onnodige objectcreatie en de overhead van garbagecollection vermeden. Integendeel, Arrays.asList creëert altijd die impliciete array en is bijgevolg minder efficiënt als het aantal elementen laag is.

Tijdens het maken van een Set gebruikmakend van een fabrieksmethode, als dubbele elementen worden doorgegeven als parameters, dan IllegalArgumentException wordt tijdens runtime gegooid:

@Test (verwacht = IllegalArgumentException.class) public void onDuplicateElem_IfIllegalArgExp_thenSuccess () {Set.of ("foo", "bar", "baz", "foo"); }

Een belangrijk punt om op te merken is dat, aangezien de fabrieksmethoden generieke geneesmiddelen gebruiken, primitieve typen automatisch worden gebundeld.

Als een array van primitieve typen wordt doorgegeven, wordt een Lijst van array van dat primitieve type wordt geretourneerd.

Bijvoorbeeld:

int [] arr = {1, 2, 3, 4, 5}; List list = List.of (arr);

In dit geval een Lijst van grootte 1 wordt geretourneerd en het element op index 0 bevat de array.

3.2. Kaart

De handtekening van Kaart fabrieksmethode is:

statische kaart van (K k1, V v1, K k2, V v2, K k3, V v3)

en het gebruik:

Map map = Map.of ("foo", "a", "bar", "b", "baz", "c");

gelijk aan Lijst en Set, de van(…) methode is overbelast om 0 tot 10 sleutelwaardeparen te hebben.

In het geval van Kaart, is er een andere methode voor meer dan 10 sleutel / waarde-paren:

statische kaart van ingangen (Map.Entry ... vermeldingen)

en het is gebruik:

Map map = Map.ofEntries (nieuwe AbstractMap.SimpleEntry ("foo", "a"), nieuwe AbstractMap.SimpleEntry ("bar", "b"), nieuwe AbstractMap.SimpleEntry ("baz", "c"));

Het doorgeven van dubbele waarden voor Key zou een IllegalArgumentException:

@Test (verwacht = IllegalArgumentException.class) public void givenDuplicateKeys_ifIllegalArgExp_thenSuccess () {Map.of ("foo", "a", "foo", "b"); }

Nogmaals, in het geval van Kaart ook zijn de primitieve typen autoboxed.

4. Implementatie-opmerkingen

De verzamelingen die zijn gemaakt met behulp van de fabrieksmethoden, zijn geen veelgebruikte implementaties.

Bijvoorbeeld de Lijst is geen ArrayList en de Kaart is geen Hash kaart. Dat zijn verschillende implementaties die in Java 9 worden geïntroduceerd. Deze implementaties zijn intern en hun constructors hebben beperkte toegang.

In deze sectie zullen we enkele belangrijke implementatieverschillen zien die gemeenschappelijk zijn voor alle drie soorten collecties.

4.1. Onveranderlijk

De verzamelingen die zijn gemaakt met behulp van fabrieksmethoden zijn onveranderlijk en het wijzigen van een element, het toevoegen van nieuwe elementen of het verwijderen van een element gooit UnsupportedOperationException:

@Test (verwacht = UnsupportedOperationException.class) openbare ongeldige onElemAdd_ifUnSupportedOpExpnThrown_thenSuccess () {Set set = Set.of ("foo", "bar"); set.add ("baz"); }
@Test (verwacht = UnsupportedOperationException.class) openbare ongeldige onElemModify_ifUnSupportedOpExpnThrown_thenSuccess () {List list = List.of ("foo", "bar"); list.set (0, "baz"); } 

Aan de andere kant keerde de collectie terug van Arrays.asListis veranderlijk. Daarom is het mogelijk om de bestaande elementen te wijzigen of te verwijderen. Gelijkwaardig aan Lijst van, kunnen we geen nieuwe elementen toevoegen aan een lijst die wordt geretourneerd van Arrays.asList.

@Test (verwacht = UnsupportedOperationException.class) openbare ongeldige onElemRemove_ifUnSupportedOpExpnThrown_thenSuccess () {Map map = Map.of ("foo", "a", "bar", "b"); map.remove ("foo"); }

4.2. Nee nul Element toegestaan

In het geval van Lijst en Setkunnen er geen elementen zijn nul. In het geval van een Kaart, kunnen sleutels noch waarden zijn nul. Passeren nul argument gooit een NullPointerException:

@Test (verwacht = NullPointerException.class) public void onNullElem_ifNullPtrExpnThrown_thenSuccess () {List.of ("foo", "bar", null); }

In tegenstelling tot Lijst van, de Arrays.asList methode accepteert nul waarden.

4.3. Op waarde gebaseerde instanties

De instanties die door fabrieksmethoden zijn gemaakt, zijn op waarden gebaseerd. Dit betekent dat fabrieken vrij zijn om een ​​nieuw exemplaar aan te maken of een bestaand exemplaar te retourneren.

Als we daarom lijsten met dezelfde waarden maken, kunnen ze al dan niet naar hetzelfde object op de heap verwijzen:

List list1 = List.of ("foo", "bar"); List list2 = List.of ("foo", "bar");

In dit geval, list1 == lijst2 kan wel of niet evalueren naar waar afhankelijk van de JVM.

4.4. Serialisatie

Collecties die zijn gemaakt op basis van fabrieksmethoden zijn Serialiseerbaar als de elementen van de collectie zijn Serialiseerbaar.

5. Conclusie

In dit artikel hebben we de nieuwe fabrieksmethoden voor verzamelingen geïntroduceerd die in Java 9 zijn geïntroduceerd.

We concludeerden waarom deze functie een welkome verandering is door enkele eerdere methoden te bespreken voor het maken van niet-wijzigbare collecties. We hebben het gebruik ervan besproken en de belangrijkste punten benadrukt waarmee tijdens het gebruik ervan rekening moet worden gehouden.

Ten slotte hebben we verduidelijkt dat deze collecties verschillen van de veelgebruikte implementaties en wezen we op de belangrijkste verschillen.

De volledige broncode en unit-tests voor dit artikel zijn beschikbaar op GitHub.