Създаване на общ масив в Java

1. Въведение

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

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

Ще разгледаме и къде Java API е решил подобен проблем.

2. Съображения при използване на общи масиви

Важна разлика между масивите и генериците е как те налагат проверка на типа. По-конкретно, масивите съхраняват и проверяват информация за типа по време на изпълнение. Generics обаче проверяват за грешки в типа по време на компилация и нямат информация за типа по време на изпълнение.

Синтаксисът на Java предполага, че бихме могли да създадем нов родов масив:

T[] elements = new T[size];

Но ако се опитаме да направим това, ще получим грешка при компилиране.

За да разберем защо, нека разгледаме следното:

public  T[] getArray(int size) { T[] genericArray = new T[size]; // suppose this is allowed return genericArray; }

Тъй като несвързаният родов тип T се разрешава към Object, нашият метод по време на изпълнение ще бъде:

public Object[] getArray(int size) { Object[] genericArray = new Object[size]; return genericArray; }

След това, ако извикаме метода си и съхраним резултата в масив String :

String[] myArray = getArray(5);

Кодът ще се компилира добре, но ще се провали по време на изпълнение с ClassCastException . Това е така, защото току-що сме присвоили обект [] на референция String [] . По-конкретно, имплицитно гласуване от компилатора няма да успее да преобразува Object [] в нашия задължителен тип String [] .

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

3. Създаване на общ масив

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

Първо, нека създадем поле за съхранение на елементите на нашия стек, което е общ масив от тип E :

private E[] elements;

Второ, нека добавим конструктор:

public MyStack(Class clazz, int capacity) { elements = (E[]) Array.newInstance(clazz, capacity); }

Забележете как използваме java.lang.reflect.Array # newInstance за инициализиране на нашия общ масив , който изисква два параметъра. Първият параметър указва типа обект в новия масив. Вторият параметър указва колко пространство да се създаде за масива. Тъй като резултатът от Array # newInstance е от тип Object , трябва да го предадем на E [], за да създадем нашия общ масив.

Трябва също да отбележим конвенцията за именуване на параметър тип clazz, а не клас, който е запазена дума в Java.

4. Разглеждане на ArrayList

4.1. Използване на ArrayList вместо масив

Често е по-лесно да се използва общ ArrayList вместо общ масив. Нека да видим как можем да променим MyStack, за да използваме ArrayList .

Първо, нека създадем поле за съхранение на нашите елементи:

private List elements;

На второ място, в нашия конструктор на стека можем да инициализираме ArrayList с първоначален капацитет:

elements = new ArrayList(capacity);

Това прави класа ни по-опростен, тъй като не се налага да използваме размисъл. Също така, не се изисква да предаваме литерал на клас, когато създаваме нашия стек. И накрая, тъй като можем да зададем първоначалния капацитет на ArrayList , можем да получим същите предимства като масива.

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

4.2. Изпълнение на ArrayList

Интересното е, че самият ArrayList се реализира с помощта на общи масиви. Нека надникнем в ArrayList, за да видим как.

Първо, нека видим полето елементи на списъка:

transient Object[] elementData;

Забележка ArrayList използва Object като тип елемент. Тъй като нашият родов тип не е известен до времето на изпълнение, Object се използва като суперклас от всякакъв тип.

Заслужава да се отбележи, че почти всички операции в ArrayList могат да използват този общ масив, тъй като не е необходимо да предоставят силно въведен масив на външния свят, с изключение на един метод - toArray !

5. Изграждане на масив от колекция

5.1. Пример за LinkedList

Нека разгледаме използването на общи масиви в API на Java Collections, където ще изградим нов масив от колекция.

Първо, нека създадем нов LinkedList с аргумент тип String и добавим елементи към него:

List items = new LinkedList(); items.add("first item"); items.add("second item"); 

Второ, нека изградим масив от елементите, които току-що добавихме:

String[] itemsAsArray = items.toArray(new String[0]);

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

В нашия пример по-горе използвахме нов String [0] като наш входен масив за изграждане на получения масив String .

5.2. Внедряване на LinkedList.toArray

Нека надникнем в LinkedList.toArray , за да видим как е внедрен в Java JDK.

Първо, нека разгледаме сигнатурата на метода:

public  T[] toArray(T[] a)

Второ, нека видим как се създава нов масив, когато се изисква:

a = (T[])java.lang.reflect.Array.newInstance(a.getClass().getComponentType(), size);

Забележете как използва Array # newInstance за изграждане на нов масив, както в нашия пример за стека по-рано. Също така забележете как параметър a се използва за предоставяне на тип на Array # newInstance. И накрая, резултатът от Array # newInstance се прехвърля към T [] създава общ масив.

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

В тази статия първо разгледахме разликите между масивите и генеричните средства, последвани от пример за създаване на общ масив. След това показахме как използването на ArrayList може да е по-лесно от използването на общ масив. И накрая, разгледахме и използването на общ масив в API за колекции.

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