Падручнік па Java Generics - Што такое Generics і як імі карыстацца?

Java Generics - адна з найважнейшых асаблівасцей мовы Java. Ідэя агульных прэпаратаў досыць простая, аднак часам выходзіць складанай з-за зруху ад звычайнага сінтаксісу, звязанага з ёй.

Мэта гэтага падручніка - пазнаёміць вас з гэтай карыснай канцэпцыяй джынэрыкаў простым для разумення спосабам.

Але перш чым пагрузіцца ў саму дженерыку, давайце разбярэмся, навошта спачатку патрэбныя дженерыкі Java.




Прызначэнне Java Generics

Да ўвядзення дженерыкаў у Java 5 вы маглі напісаць і скампіляваць фрагмент кода, як гэты, не выдаючы памылкі і папярэджання:

List list = new ArrayList(); list.add('hey'); list.add(new Object());

Вы можаце дадаць значэнні любога тыпу ў спіс альбо іншую калекцыю Java, не паведамляючы, які тып дадзеных ён захоўвае. Але калі вы атрымліваеце значэнні са спісу, вы павінны відавочна перадаць яго пэўнаму тыпу.


Паспрабуйце перагледзець прыведзены вышэй спіс.



for (int i=0; i< list.size(); i++) {
String value = (String) list.get(i); //CastClassException when i=1 }

Дазвол на стварэнне спісу без папярэдняга аб'яўлення захаванага тыпу дадзеных, як мы зрабілі, можа прывесці да таго, што праграмісты здзяйсняюць памылкі, падобныя вышэй, якія выкідваюць ClassCastExceptions падчас выканання.

Джэнерыкі былі ўведзены, каб прадухіліць праграмістаў рабіць такія памылкі.

З агульнымі прэпаратамі вы можаце відавочна аб'явіць тып дадзеных, які будзе захоўвацца пры стварэнні калекцыі Java, як паказана ў наступным прыкладзе.


нататка:Вы ўсё яшчэ можаце стварыць аб'ект калекцыі Java без указання захаванага тыпу дадзеных, але гэта не рэкамендуецца. List stringList = new ArrayList();

Цяпер вы не можаце памылкова захоўваць цэлае лік у спісе тыпаў String, не выдаючы памылкі падчас кампіляцыі. Гэта гарантуе, што ваша праграма не будзе мець памылак падчас выканання.

stringList.add(new Integer(4)); //Compile time Error

Асноўнай мэтай увядзення дженерыкаў у Java было пазбегнуць ClassCastExceptions падчас выканання.



Стварэнне Java Generics

Вы можаце выкарыстоўваць агульныя сродкі для стварэння класаў і метадаў Java. Давайце разгледзім прыклады таго, як ствараць джынэрыкі кожнага тыпу.

Агульны клас

Пры стварэнні агульнага класа параметр тыпу для класа дадаецца ў канцы назвы класа ў межах кута дужкі.


public class GenericClass {
private T item;
public void setItem(T item) {
this.item = item;
}
public T getItem() {
return this.item;
} }

Тут, T - параметр тыпу дадзеных. T, N і E некаторыя літары, якія выкарыстоўваюцца для параметраў тыпу дадзеных у адпаведнасці з канвенцыямі Java.

У прыведзеным вышэй прыкладзе вы можаце перадаць яму пэўны тып дадзеных пры стварэнні аб'екта GenericClass.

public static void main(String[] args) {
GenericClass gc1 = new GenericClass();
gc1.setItem('hello');
String item1 = gc1.getItem(); // 'hello'
gc1.setItem(new Object()); //Error
GenericClass gc2 = new GenericClass();
gc2.setItem(new Integer(1));
Integer item2 = gc2.getItem(); // 1
gc2.setItem('hello'); //Error }

Вы не можаце перадаць прымітыўны тып дадзеных у параметр тыпу дадзеных пры стварэнні агульнага аб'екта класа. У якасці параметраў тыпу могуць быць перададзены толькі тыпы дадзеных, якія пашыраюць тып аб'екта.

Напрыклад:


GenericClass gc3 = new GenericClass(); //Error

Агульныя метады

Стварэнне агульных метадаў ідзе па аналагічнай схеме стварэння агульных класаў. Вы можаце рэалізаваць агульны метад як у агульным класе, так і не ў агульным.

public class GenericMethodClass {
public static void printItems(T[] arr){
for (int i=0; i< arr.length; i++) {

System.out.println(arr[i]);
}
}
public static void main(String[] args) {
String[] arr1 = {'Cat', 'Dog', 'Mouse'};
Integer[] arr2 = {1, 2, 3};

GenericMethodClass.printItems(arr1); // 'Cat', 'Dog', 'Mouse'
GenericMethodClass.printItems(arr2); // 1, 2, 3
} }

Тут вы можаце перадаць масіў пэўнага тыпу для параметрызацыі метаду. Агульны метад PrintItems() перабірае перададзены масіў і друкуе элементы, якія захоўваюцца, як звычайны метад Java.



Параметры абмежаванага тыпу

Пакуль што агульныя класы і метады, якія мы стварылі вышэй, могуць быць параметрызаваны на любы тып дадзеных, акрамя прымітыўных тыпаў. Але што, калі мы хацелі абмежаваць тыпы дадзеных, якія можна перадаць агульным сродкам? Тут паступаюць абмежаваныя параметры тыпу.

Вы можаце абмежаваць тыпы дадзеных, прынятыя агульным класам ці метадам, указаўшы, што гэта павінен быць падклас іншага тыпу дадзеных.


Напрыклад:

//accepts only subclasses of List public class UpperBoundedClass{
//accepts only subclasses of List
public void UpperBoundedMethod(T[] arr) {
} }

Тут, UpperBoundedClass і UpperBoundedMethod можна параметрызаваць толькі з выкарыстаннем падтыпаў List тып дадзеных.

List тып дадзеных дзейнічае як верхняя мяжа да параметра тыпу. Калі вы паспрабуеце выкарыстаць тып дадзеных, які не з'яўляецца падтыпам List, ён выдасць памылку падчас кампіляцыі.

Межы не абмяжоўваюцца толькі заняткамі. Вы таксама можаце перадаваць інтэрфейсы. Пашырэнне інтэрфейсу азначае, у дадзеным выпадку, рэалізацыю інтэрфейсу.

Параметр таксама можа мець некалькі межаў, як паказана ў гэтым прыкладзе.

//accepts only subclasses of both Mammal and Animal public class MultipleBoundedClass{
//accepts only subclasses of both Mammal and Animal
public void MultipleBoundedMethod(T[] arr){
} }

Тып дадзеных, які прымае, павінен быць падкласам класаў жывёл і млекакормячых. Калі адна з гэтых межаў з'яўляецца класам, яна павінна быць першай у звязанай дэкларацыі.

У прыведзеным вышэй прыкладзе, калі млекакормячыя - гэта клас, а жывёла - інтэрфейс, млекакормячыя павінны быць на першым месцы, як паказана вышэй. У адваротным выпадку код выдае памылку падчас кампіляцыі.



Падстаноўныя знакі Java Generics

Падстаноўныя знакі выкарыстоўваюцца для перадачы параметраў агульных тыпаў метадам. У адрозненне ад агульнага метаду, тут агульны параметр перадаецца параметрам, прынятым метадам, які адрозніваецца ад параметра тыпу дадзеных, пра які мы гаварылі вышэй. Падстаноўны знак прадстаўлены знакам? сімвал.

public void printItems(List list) {
for (int i=0; i< list.size(); i++) {
System.out.println(list.get(i));
} }

Вышэй printItems() метад прымае ў якасці параметра спісы любога тыпу дадзеных. Гэта перашкаджае праграмістам паўтараць коды для спісаў розных тыпаў дадзеных, што было б у выпадку без агульных прэпаратаў.

Змены ўверсе

Калі мы хочам абмежаваць тыпы дадзеных, якія захоўваюцца ў спісе, прынятым метадам, мы можам выкарыстоўваць абмежаваныя падстаноўныя знакі.

Прыклад:

public void printSubTypes(List list) {
for (int i=0; i< list.size(); i++) {
System.out.println(list.get(i));
} }

printSubTypes() метад прымае толькі спісы, якія захоўваюць падтыпы колеру. Ён прымае спіс аб'ектаў RedColor або BlueColor, але не прымае спіс аб'ектаў Animal. Гэта таму, што жывёла не з'яўляецца падтыпам колеру. Гэта прыклад верхняй мяжы падстаноўнага знака.

Універсальныя знакі ніжняй мяжы

Аналагічна, калі б у нас было:

public void printSuperTypes(List list) {
for (int i=0; i< list.size(); i++) {
System.out.println(list.get(i));
} }

тады | | + _ | метад прымае толькі спісы, якія захоўваюць супертыпы класа Dog. Ён прыняў бы спіс аб'ектаў млекакормячых або жывёл, але не спіс аб'ектаў LabDog, таму што LabDog - гэта не суперклас Сабакі, а падклас. Гэта прыклад ніжняй мяжы падстаноўнага знака.



Выснова

Java Generics стала функцыяй, без якой праграмісты не могуць жыць пасля яе ўвядзення.

Гэтая папулярнасць звязана з уплывам на палягчэнне жыцця праграмістаў. Акрамя прадухілення памылак кадавання, выкарыстанне агульных сродкаў робіць код менш паўтаральным. Вы заўважылі, як ён абагульняе класы і метады, каб пазбегнуць неабходнасці паўтараць код для розных тыпаў дадзеных?

Добра валодаць агульнымі прэпаратамі, важна стаць экспертам у гэтай мове. Такім чынам, прымяненне таго, што вы даведаліся ў гэтым уроку, у практычным кодзе - гэта спосаб ісці наперад.