CodeGym /Java блог /Случаен /Generics в Java
John Squirrels
Ниво
San Francisco

Generics в Java

Публикувано в групата
здрасти Ще говорим за Java Generics. Трябва да кажа, че ще научите много! Не само този урок, но и следващите няколко урока ще бъдат посветени на генеричните продукти. Така че, ако се интересувате от генерични лекарства, днес е вашият щастлив ден: ще научите много за характеристиките на генеричните лекарства. И ако не, примирете се и се отпуснете! :) Това е много важна тема и трябва да я знаете. Нека започнем с простото: „Howво“ и „защо“.

Какво представляват Java Generics?

Генериците са типове, които имат параметър. Когато създавате общ тип, вие указвате не само тип, но и тип данни, с който ще работи. Предполагам, че най-очевидният пример вече ви е хрумнал: ArrayList! Ето How обикновено създаваме такъв в програма:

import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       List<String> myList1 = new ArrayList<>();
       myList1.add("Test String 1");
       myList1.add("Test String 2");
   }
}
Както можете да предположите, характеристика на този списък е, че не можем да напъхаме всичко в него: той работи изключително с String обекти. Сега нека направим малко отклонение в историята на Java и се опитаме да отговорим на въпроса "защо?" За да направим това, ще напишем наша собствена опростена version на класа ArrayList. Нашият списък знае само How да добавя данни към и извлича данни от вътрешен масив:

public class MyListClass {

   private Object[] data;
   private int count;

   public MyListClass() {
       this.data = new Object[10];
       this.count = 0;
   }

   public void add(Object o) {
       this.data[count] = o;
       count++;
   }

   public Object[] getData() {
       return data;
   }
}
Да предположим, че искаме нашият списък да съхранява само Integer s. Ние не използваме общ тип. Не искаме да включваме изрична проверка „instanceof Integer “ в метода add() . Ако го направихме, целият ни клас би бил подходящ само за Integer и ще трябва да напишем подобен клас за всеки друг тип данни в света! Ще разчитаме на нашите програмисти и просто ще оставим коментар в codeа, за да сме сигурни, че няма да добавят нищо, което не искаме:

// Use this class ONLY with the Integer data type
public void add(Object o) {
   this.data[count] = o;
   count++;
}
Един от програмистите пропусна този коментар и по невнимание постави няколко низа в списък с числа и след това изчисли сумата им:

public class Main {

   public static void main(String[] args) {

       MyListClass list = new MyListClass();
       list.add(100);
       list.add(200);
       list.add("Lolkek");
       list.add("Shalala");

       Integer sum1 = (Integer) list.getData()[0] + (Integer) list.getData()[1];
       System.out.println(sum1);

       Integer sum2 = (Integer) list.getData()[2] + (Integer) list.getData()[3];
       System.out.println(sum2);
   }
}
Конзолен изход:

300 
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer 
      at Main.main (Main.java:14)
Коя е най-лошата част от тази ситуация? Със сигурност не е небрежността на програмиста. Най-лошото е, че неправилният code се озова на важно място в нашата програма и се компorра успешно. Сега ще срещнем грешката не докато пишем code, а само по време на тестване (и това е най-добрият сценарий!). Поправянето на грешки в по-късните етапи на разработката струва много повече — Howто по отношение на пари, така и на време. Това е точно мястото, където генериците са от полза: генеричният клас позволява на нещастния програмист да открие грешката веднага. Програмата просто няма да се компorра!

import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

       List<Integer> myList1 = new ArrayList<>();
      
       myList1.add(100);
       myList1.add(100);
       myList1.add ("Lolkek"); // Error!
       myList1.add("Shalala"); // Error!
   }
}
Програмистът веднага осъзнава грешката си и моментално се оправя. Между другото, не трябваше да създаваме наш собствен клас List , за да видим този вид грешка. Просто премахнете ъгловите скоби и въведете ( <Integer> ) от обикновен ArrayList!

import java.util.ArrayList;
import java.util.List;

public class Main {

   public static void main(String[] args) {

      List list = new ArrayList();

      list.add(100);
      list.add(200);
      list.add("Lolkek");
      list.add("Shalala");

       System.out.println((Integer) list.get(0) + (Integer) list.get(1));
       System.out.println((Integer) list.get(2) + (Integer) list.get(3));
   }
}
Конзолен изход:

300 
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer 
     at Main.main(Main.java:16)
С други думи, дори използвайки „родните“ механизми на Java, можем да направим този вид грешка и да създадем опасна колекция. Ако обаче поставим този code в IDE, получаваме предупреждение: „Непроверено извикване за добавяне (E) като член на необработен тип на java.util.List“ Казват ни, че нещо може да се обърка при добавяне на елемент към колекция, на която липсва общ тип. Но Howво означава фразата "суров тип"? Суровият тип е общ клас, чийто тип е премахнат. С други думи, List myList1 е необработен тип . Обратното на необработения тип е генеричен тип — генеричен клас с указание за параметризирания тип(ове) . Например List<String> myList1. Може да попитате защо езикът позволява използването на необработени типове ? Причината е проста. Създателите на Java оставиха поддръжка за необработени типове в езика, за да избегнат създаването на проблеми със съвместимостта. По времето, когато беше пусната Java 5.0 (генериците се появиха за първи път в тази version), много code вече беше написан с помощта на необработени типове . В резултат на това този механизъм се поддържа и днес. В уроците многократно сме споменавали класическата книга на Джошуа Блок „Ефективна Java“. Като един от създателите на езика, той не е пропуснал необработените типове и генеричните типове в книгата си. Какво представляват генериците в Java?  - 2Глава 23 от книгата има много красноречиво заглавие: „Не използвайте необработени типове в нов code“ Това е, което трябва да запомните. Когато използвате генерични класове, никога не превръщайте генеричен тип в необработен тип .

Генерични методи

Java ви позволява да параметризирате отделни методи чрез създаване на така наречените общи методи. Как са полезни такива методи? Преди всичко те са полезни с това, че ви позволяват да работите с различни типове параметри на метода. Ако една и съща логика може безопасно да се приложи към различни типове, общият метод може да бъде чудесно решение. Считайте това за много прост пример: Да предположим, че имаме няHowъв списък, наречен myList1 . Искаме да премахнем всички стойности от списъка и да запълним всички празни пространства с нови стойности. Ето How изглежда нашият клас с общ метод:

public class TestClass {

   public static <T> void fill(List<T> list, T val) {
       for (int i = 0; i < list.size(); i++)
           list.set(i, val);
   }

   public static void main(String[] args) {

       List<String> strings = new ArrayList<>();
       strings.add("Old String 1");
       strings.add("Old String 2");
       strings.add("Old String 3");

       fill(strings, "New String");

       System.out.println(strings);

       List<Integer> numbers = new ArrayList<>();
       numbers.add(1);
       numbers.add(2);
       numbers.add(3);

       fill(numbers, 888);
       System.out.println(numbers);
   }
}
Обърнете внимание на синтаксиса. Изглежда малко необичайно:

public static <T> void fill(List<T> list, T val)
Пишем <T> преди връщания тип. Това показва, че имаме работа с общ метод. В този случай методът приема 2 параметъра като вход: списък от T обекти и друг отделен T обект. Като използваме <T>, ние параметризираме типовете параметри на метода: не можем да подадем списък от низове и цяло число. Списък с низове и низ, списък с цели числа и цяло число, списък с нашите собствени обекти Cat и още един обект Cat — това е, което трябва да направим. Методът main() илюстрира How методът fill() може лесно да се използва за работа с различни типове данни. Първо използваме метода със списък от низове и низ като вход, а след това със списък от цели числа и цяло число. Конзолен изход:

[New String, New String, New String] [888, 888, 888]
Представете си, ако нямахме общи методи и се нуждаехме от логиката на метода fill() за 30 различни класа. Ще трябва да напишем един и същ метод 30 пъти за различни типове данни! Но благодарение на общите методи, можем да използваме повторно нашия code! :)

Генерични класове

Не сте ограничени до генеричните класове, предоставени в стандартните Java библиотеки — можете да създадете свои собствени! Ето един прост пример:

public class Box<T> {

   private T t;

   public void set(T t) {
       this.t = t;
   }

   public T get() {
       return t;
   }

   public static void main(String[] args) {

       Box<String> stringBox = new Box<>();

       stringBox.set("Old String");
       System.out.println(stringBox.get());
       stringBox.set("New String");

       System.out.println(stringBox.get());
      
       stringBox.set(12345); // Compilation error!
   }
}
Нашият клас Box<T> е общ клас. След като присвоим тип данни ( <T> ) по време на създаването, вече не можем да поставяме обекти от други типове в него. Това може да се види в примера. Когато създавахме нашия обект, посочихме, че той ще работи с низове:

Box<String> stringBox = new Box<>();
И в последния ред от codeа, когато се опитаме да поставим числото 12345 в полето, получаваме грешка при компилация! Толкова е лесно! Създадохме наш собствен общ клас! :) С това днешният урок приключва. Но ние не се сбогуваме с генеричните лекарства! В следващите уроци ще говорим за по-разширени функции, така че не си тръгвайте! ) За да затвърдите наученото, ви предлагаме да гледате видео урок от нашия курс по Java
Желая успех в обучението! :)
Коментари
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION