Java Generics - адна з найважнейшых асаблівасцей мовы Java. Ідэя агульных прэпаратаў досыць простая, аднак часам выходзіць складанай з-за зруху ад звычайнага сінтаксісу, звязанага з ёй.
Мэта гэтага падручніка - пазнаёміць вас з гэтай карыснай канцэпцыяй джынэрыкаў простым для разумення спосабам.
Але перш чым пагрузіцца ў саму дженерыку, давайце разбярэмся, навошта спачатку патрэбныя дженерыкі Java.
Да ўвядзення дженерыкаў у 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, як паказана ў наступным прыкладзе.
List stringList = new ArrayList();
Цяпер вы не можаце памылкова захоўваць цэлае лік у спісе тыпаў String, не выдаючы памылкі падчас кампіляцыі. Гэта гарантуе, што ваша праграма не будзе мець памылак падчас выканання.
stringList.add(new Integer(4)); //Compile time Error
Асноўнай мэтай увядзення дженерыкаў у Java было пазбегнуць ClassCastExceptions
падчас выканання.
Вы можаце выкарыстоўваць агульныя сродкі для стварэння класаў і метадаў 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){
} }
Тып дадзеных, які прымае, павінен быць падкласам класаў жывёл і млекакормячых. Калі адна з гэтых межаў з'яўляецца класам, яна павінна быць першай у звязанай дэкларацыі.
У прыведзеным вышэй прыкладзе, калі млекакормячыя - гэта клас, а жывёла - інтэрфейс, млекакормячыя павінны быць на першым месцы, як паказана вышэй. У адваротным выпадку код выдае памылку падчас кампіляцыі.
Падстаноўныя знакі выкарыстоўваюцца для перадачы параметраў агульных тыпаў метадам. У адрозненне ад агульнага метаду, тут агульны параметр перадаецца параметрам, прынятым метадам, які адрозніваецца ад параметра тыпу дадзеных, пра які мы гаварылі вышэй. Падстаноўны знак прадстаўлены знакам? сімвал.
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 стала функцыяй, без якой праграмісты не могуць жыць пасля яе ўвядзення.
Гэтая папулярнасць звязана з уплывам на палягчэнне жыцця праграмістаў. Акрамя прадухілення памылак кадавання, выкарыстанне агульных сродкаў робіць код менш паўтаральным. Вы заўважылі, як ён абагульняе класы і метады, каб пазбегнуць неабходнасці паўтараць код для розных тыпаў дадзеных?
Добра валодаць агульнымі прэпаратамі, важна стаць экспертам у гэтай мове. Такім чынам, прымяненне таго, што вы даведаліся ў гэтым уроку, у практычным кодзе - гэта спосаб ісці наперад.