CodeGym /Java блог /Случаен /Използване на varargs при работа с генерични продукти
John Squirrels
Ниво
San Francisco

Използване на varargs при работа с генерични продукти

Публикувано в групата
здрасти В днешния урок ще продължим да изучаваме генеричните лекарства. Както се случва, това е голяма тема, но няма How да я избегнете — това е изключително важна част от езика :) Когато изучавате documentацията на Oracle за генерични продукти or четете онлайн уроци, ще срещнете термините non-reifiable types и реифицируеми типове . Повторяемият тип е тип, за който информацията е напълно налична по време на изпълнение. В Java такива типове включват примитиви, необработени типове и негенерични типове. Обратно, типовете, които не могат да се преобразуват, са типове, чиято информация се изтрива и става недостъпна по време на изпълнение. Както се случва, това са генерични лекарства — List<String>, List<Integer>и т.н.

Между другото, помните ли Howво е varargs?

В случай, че сте забравor, това е аргумент с променлива дължина. Те са полезни в ситуации, в които не знаем колко аргумента могат да бъдат предадени на нашия метод. Например, ако имаме клас калкулатор, който има sumметод. Методът sum()може да получи 2 числа, or 3, or 5, or колкото желаете. Би било много странно да претоварвате sum()метода за всеки възможен брой аргументи. Вместо това можем да направим следното:

public class SimpleCalculator {

   public static int sum(int...numbers) {

       int result = 0;

       for(int i : numbers) {

           result += i;
       }

       return result;
   }

   public static void main(String[] args) {

       System.out.println(sum(1,2,3,4,5));
       System.out.println(sum(2,9));
   }
}
Конзолен изход:

15
11
Това ни показва, че има някои важни функции при използване на varargs в комбинация с генерични. Нека да разгледаме следния code:

import javafx.util.Pair;
import java.util.ArrayList;
import java.util.List;

public class Main {

   public static <E> void addAll(List<E> list, E... array) {

       for (E element : array) {
           list.add(element);
       }
   }

   public static void main(String[] args) {
       addAll(new ArrayList<String>(), // This is okay
               "Leonardo da Vinci",
               "Vasco de Gama"
       );

       // but here we get a warning
       addAll(new ArrayList<Pair<String, String>>(),
               new Pair<String, String>("Leonardo", "da Vinci"),
               new Pair<String, String>("Vasco", "de Gama")
       );
   }
}
Методът addAll()приема като вход a List<E>и произволен брой Eобекти и след това добавя всички тези обекти към списъка. В main()метода извикваме нашия addAll()метод два пъти. В първия случай добавяме два обикновени низа към List. Тук всичко е наред. Във втория случай добавяме два Pair<String, String>обекта към List. Но тук неочаквано получаваме предупреждение:

Unchecked generics array creation for varargs parameter
Какво означава това? Защо получаваме предупреждение и защо се споменава array? В крайна сметка нашият code няма array! Да започнем с втория случай. Предупреждението споменава масив, защото компилаторът преобразува аргумента с променлива дължина (varargs) в масив. С други думи, подписът на нашия addAll()метод е:

public static <E> void addAll(List<E> list, E... array)
Всъщност изглежда така:

public static <E> void addAll(List<E> list, E[] array)
Тоест в main()метода компилаторът преобразува нашия code в това:

public static void main(String[] args) { 
   addAll(new ArrayList<String>(), 
      new String[] { 
        "Leonardo da Vinci", 
        "Vasco de Gama" 
      } 
   ); 
   addAll(new ArrayList<Pair<String,String>>(),
        new Pair<String,String>[] { 
            new Pair<String,String>("Leonardo","da Vinci"), 
            new Pair<String,String>("Vasco","de Gama") 
        } 
   ); 
}
Масивът Stringе добре. Но Pair<String, String>масивът не е. Проблемът е, че Pair<String, String>е тип, който не може да се преобразува. По време на компилация цялата информация за аргументите на типа (<String, String>) се изтрива. Създаването на масиви от нерефифицируем тип не е разрешено в Java . Можете да видите това, ако се опитате ръчно да създадете масив Pair<String, String>

public static void main(String[] args) {

   // Compilation error Generic array creation
  Pair<String, String>[] array = new Pair<String, String>[10];
}
Причината е очевидна: безопасност на типа. Както си спомняте, когато създавате масив, определено трябва да посочите кои обекти (or примитиви) ще съхранява масивът.

int array[] = new int[10];
В един от предишните ни уроци разгледахме подробно изтриването на типа. В този случай изтриването на типа ни кара да загубим информацията, която Pairобектите съхраняват <String, String>двойки. Създаването на масива не би било безопасно. Когато използвате методи, които включват varargs и генерични, не забравяйте да запомните за изтриването на типове и How работи. Ако сте абсолютно сигурни за codeа, който сте написали, и знаете, че той няма да причини проблеми, можете да изключите предупрежденията, свързани с varargs, като използвате анотациите @SafeVarargs.

@SafeVarargs
public static <E> void addAll(List<E> list, E... array) {

   for (E element : array) {
       list.add(element);
   }
}
Ако добавите тази анотация към вашия метод, предупреждението, което срещнахме по-рано, няма да се появи. Друг проблем, който може да възникне при използване на varargs с генерични, е замърсяването на купчина. Използване на varargs при работа с генерици - 3Замърсяването на купчина може да се случи в следната ситуация:

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

public class Main {

   static List<String> polluteHeap() {
       List numbers = new ArrayList<Number>();
       numbers.add(1);
       List<String> strings = numbers;
       strings.add("");
       return strings;
   }

   public static void main(String[] args) {

       List<String> stringsWithHeapPollution = polluteHeap();

       System.out.println(stringsWithHeapPollution.get(0));
   }
}
Конзолен изход:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
С прости думи, замърсяването на купчина е, когато обекти от тип Aтрябва да са в купчината, но обекти от тип Bсе озовават там поради грешки, свързани с безопасността на типа. В нашия пример се случва точно това. Първо създадохме необработената променлива и й numbersприсвоихме обща колекция ( ). ArrayList<Number>След това добавихме номера 1към колекцията.

List<String> strings = numbers;
На този ред компилаторът се опита да ни предупреди за възможни грешки, като издаде предупреждението „ Непроверено присвояване... “, но ние го игнорирахме. Завършваме с генерична променлива от тип List<String>, която сочи към генерична колекция от тип ArrayList<Number>. Ясно е, че тази ситуация може да доведе до проблеми! И така става. Използвайки нашата нова променлива, добавяме низ към колекцията. Сега имаме замърсяване на купчина — добавихме число и след това низ към параметризираната колекция. Компилаторът ни предупреди, но ние пренебрегнахме предупреждението му. В резултат на това получаваме ClassCastExceptionсамо докато програмата работи. И така, Howво общо има това с varargs? Използването на varargs с генерични лекарства може лесно да доведе до замърсяване на купчина. Ето един прост пример:

import java.util.Arrays;
import java.util.List;

public class Main {

   static void polluteHeap(List<String>... stringsLists) {
       Object[] array = stringsLists;
       List<Integer> numbersList = Arrays.asList(66,22,44,12);

       array[0] = numbersList;
       String str = stringsLists[0].get(0);
   }

   public static void main(String[] args) {

       List<String> cars1 = Arrays.asList("Ford", "Fiat", "Kia");
       List<String> cars2 = Arrays.asList("Ferrari", "Bugatti", "Zaporozhets");

       polluteHeap(cars1, cars2);
   }
}
Какво става тук? Поради изтриването на типа, нашият аргумент с променлива дължина

List<String>...stringsLists
се превръща в масив от списъци, т.е. List[]от обекти от неизвестен тип (не забравяйте, че varargs се превръща в нормален масив по време на компилация). Поради това можем лесно да го присвоим на Object[] arrayпроменливата в първия ред на метода — типът на обектите в нашите списъци е изтрит! И сега имаме Object[]променлива, към която можем да добавим всичко, тъй като всички обекти в Java наследяват Object! Първоначално имаме само масив от списъци с низове. Но благодарение на изтриването на типове и използването на varargs, можем лесно да добавим списък с числа, което и правим. В резултат на това замърсяваме купчината чрез смесване на обекти от различен тип. Резултатът ще бъде друг ClassCastException, когато се опитаме да прочетем низ от масива. Конзолен изход:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
Такива неочаквани последствия могат да бъдат причинени от използването на varargs, на пръв поглед прост механизъм :) И с това днешният урок приключва. Не забравяйте да решите няколко задачи, а ако имате време и сor, проучете допълнително четиво. „ Ефективната Java “ не се чете сама! :) До следващия път!
Коментари
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION