Java удобни фабрични методи за колекции

1. Общ преглед

Java 9 носи дългоочакваната синтактична захар за създаване на малки немодифицируеми екземпляри на колекция с помощта на кратък код еднолинейна. Според JEP 269 в JDK 9 ще бъдат включени нови фабрични методи за удобство.

В тази статия ще разгледаме използването му заедно с подробностите за изпълнението.

2. История и мотивация

Създаването на малка неизменяема колекция в Java е много подробно, използвайки традиционния начин.

Да вземем пример за набор :

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

Това е твърде много код за проста задача и трябва да е възможно да се направи с един израз.

Горното е вярно и за Карта.

Въпреки това, за Списък има фабричен метод:

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

Въпреки че това създаване на Списък е по-добро от инициализацията на конструктора, това е по-малко очевидно, тъй като общата интуиция не би била да се търси клас Arrays за методи за създаване на Списък :

Има и други начини за намаляване на многословието като техниката за инициализиране на двойни скоби :

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

или с помощта на Java 8 Streams :

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

Техниката на двойна скоба е само малко по-малко подробна, но значително намалява четливостта (и се счита за анти-модел).

Версията на Java 8 обаче е едноредов израз и също има някои проблеми. Първо, това не е очевидно и интуитивно. Второ, все още е многословно. Трето, включва създаването на ненужни обекти. И четвърто, този метод не може да се използва за създаване на карта .

За да обобщим недостатъците, нито един от горните подходи не третира конкретния случай на употреба, създавайки малък немодифицируем проблем от първокласен клас на колекцията .

3. Описание и употреба

Предоставени са статични методи за интерфейси List , Set и Map, които вземат елементите като аргументи и връщат екземпляр съответно на List , Set и Map .

Този метод е наречен на (...) за всичките три интерфейса.

3.1. Списък и набор

Подписът и характеристиките на фабричните методи List и Set са еднакви:

static  List of(E e1, E e2, E e3) static  Set of(E e1, E e2, E e3)

използване на методите:

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

Както виждаме, това е много просто, кратко и кратко.

В примера използвахме метода с взема точно три елемента като параметри и връща Списък / Набор с размер 3.

Но има 12 претоварени версии на този метод - единадесет с 0 до 10 параметъра и една с var-args:

static  List of() static  List of(E e1) static  List of(E e1, E e2) // ....and so on static  List of(E... elems)

За повечето практически цели са достатъчни 10 елемента, но ако се изискват повече, може да се използва версията var-args.

Сега можем да попитаме какъв е смисълът да имаме 11 допълнителни метода, ако има версия на var-args, която може да работи за произволен брой елементи.

Отговорът на това е представянето. Всяко извикване на метод var-args имплицитно създава масив. Претоварените методи избягват ненужното създаване на обекти и натрупването на боклук. Напротив, Arrays.asList винаги създава този неявен масив и следователно е по-малко ефективен, когато броят на елементите е малък.

По време на създаването на набор с фабричен метод, ако дублиращи се елементи се предават като параметри, тогава IllegalArgumentException се хвърля по време на изпълнение:

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

Важен момент, който трябва да се отбележи, е, че тъй като фабричните методи използват генерични продукти, примитивните типове се получават автоматично.

Ако е предаден масив от примитивен тип, се връща Списък на масив от този примитивен тип.

Например:

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

В този случай се връща Списък с размер 1 и елементът с индекс 0 съдържа масива.

3.2. Карта

Подписът на карта фабрика метод е:

static  Map of(K k1, V v1, K k2, V v2, K k3, V v3)

и използването:

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

Подобно на List and Set , методът на (...) е претоварен, за да има 0 до 10 двойки ключ-стойност.

В случая на Map има различен метод за повече от 10 двойки ключ-стойност:

static  Map ofEntries(Map.Entry... entries)

и това е употреба:

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

Предаването в дублирани стойности за Key би хвърлило IllegalArgumentException :

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

Again, in the case of Map too, the primitive types are autoboxed.

4. Implementation Notes

The collections created using the factory methods are not commonly used implementations.

For example, the List is not an ArrayList and the Map is not a HashMap. Those are different implementations that are introduced in Java 9. These implementations are internal and their constructors have restricted access.

In this section, we'll see some important implementation differences which are common to all the three types of collections.

4.1. Immutable

The collections created using factory methods are immutable, and changing an element, adding new elements, or removing an element throws UnsupportedOperationException:

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

On the other hand, the collection returned from Arrays.asListis mutable. Therefore, it's possible to change or remove the existing elements. Similar to List.of, we can't add new elements to a list returned from Arrays.asList.

@Test(expected = UnsupportedOperationException.class) public void onElemRemove_ifUnSupportedOpExpnThrown_thenSuccess() { Map map = Map.of("foo", "a", "bar", "b"); map.remove("foo"); }

4.2. No null Element Allowed

In the case of List and Set, no elements can be null. In the case of a Map, neither keys nor values can be null. Passing null argument throws a NullPointerException:

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

As opposed to List.of, the Arrays.asList method accepts null values.

4.3. Value-Based Instances

The instances created by factory methods are value-based. This means that factories are free to create a new instance or return an existing instance.

Hence, if we create Lists with same values, they may or may not refer to the same object on the heap:

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

В този случай list1 == list2 може или не може да оцени като истина в зависимост от JVM.

4.4. Сериализация

Колекциите, създадени от фабрични методи, са сериализуеми, ако елементите на колекцията са сериализуеми.

5. Заключение

В тази статия представихме новите фабрични методи за колекции, въведени в Java 9.

Заключихме защо тази функция е добре дошла промяна, като разгледахме някои минали методи за създаване на немодифицируеми колекции. Ние разгледахме използването му и подчертахме ключовите моменти, които трябва да се имат предвид, докато ги използваме.

Накрая изяснихме, че тези колекции са различни от често използваните внедрения и посочихме ключови разлики.

Пълният изходен код и модулни тестове за тази статия са достъпни в GitHub.