JavaRush /Java 博客 /Random-ZH /使用泛型时使用可变参数

使用泛型时使用可变参数

已在 Random-ZH 群组中发布
你好!在今天的课程中,我们将继续学习泛型。碰巧这是一个很大的主题,但无处可去 - 这是该语言极其重要的部分:) 当您研究有关泛型的 Oracle 文档或阅读 Internet 上的指南时,您会遇到这些术语不可具体化类型可具体化类型。“可具体化”是一个什么样的词?即使英语一切都很好,你也不太可能遇到它。我们来尝试翻译一下吧! 使用泛型时使用可变参数 - 2
*谢谢谷歌,你帮了很多忙-_-*
可具体化类型是一种其信息在运行时完全可用的类型。在 Java 语言中,这些类型包括基元、原始类型和非泛型类型。相反,不可具体化类型是其信息在运行时被删除且不可用的类型。这些只是泛型 - List<String>List<Integer>等。

顺便问一下,您还记得可变参数是什么吗?

如果您忘记了,这些是可变长度参数。当我们不知道到底有多少参数可以传递给我们的方法时,它们会派上用场。例如,如果我们有一个计算器类并且它有一个方法sumsum()您可以将 2 个数字、3 个、5 个或任意数量的数字传递给该方法。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与泛型结合使用时,有一些重要的特征。我们看一下这段代码:
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>(),  //  здесь все нормально
               "Leonardo da Vinci",
               "Vasco de Gama"
       );

       // а здесь мы получаем предупреждение
       addAll(new ArrayList<Pair<String, String>>(),
               new Pair<String, String>("Leonardo", "da Vinci"),
               new Pair<String, String>("Vasco", "de Gama")
       );
   }
}
该方法采用一个列表和任意数量的对象addAll()作为输入,然后将所有这些对象添加到列表中。在该方法中,我们调用我们的方法两次。第一次我们添加了两条常规行。这里一切都很好。第二次我们添加两个对象。在这里我们突然收到一个警告: List<E>Emain()addAll()ListListPair<String, String>

Unchecked generics array creation for varargs parameter
这是什么意思?为什么我们会收到警告?这与警告有何关系arrayArray- 这是一个数组,而我们的代码中没有数组!让我们从第二个开始。该警告提到了数组,因为编译器将可变长度参数 (varargs) 转换为数组。换句话说,我们方法的签名是addAll()
public static <E> void addAll(List<E> list, E... array)
它实际上看起来像这样:
public static <E> void addAll(List<E> list, E[] array)
也就是说,在方法中main(),编译器会将我们的代码转换为:
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) {

   //  ошибка компиляции! Generic array creation
  Pair<String, String>[] array = new Pair<String, String>[10];
}
原因很明显——类型安全。您还记得,创建数组时,必须指示该数组将存储哪些对象(或基元)。
int array[] = new int[10];
在之前的一课中,我们详细研究了类型擦除机制。因此,在这种情况下,由于擦除类型,我们丢失了Pair存储在对象中的信息对<String, String>。创建数组是不安全的。将方法与varargs泛型一起使用时,请务必记住类型擦除及其工作原理。如果您对所编写的代码绝对有信心,并且知道它不会导致任何问题,则可以varargs使用注释禁用与其关联的警告@SafeVarargs
@SafeVarargs
public static <E> void addAll(List<E> list, E... array) {

   for (E element : array) {
       list.add(element);
   }
}
如果您将此注释添加到您的方法中,我们之前遇到的警告将不会出现。一起使用泛型时另一个可能的问题varargs是堆污染。 使用泛型时使用可变参数 - 4在以下情况下可能会发生污染:
import java.util.ArrayList;
import java.util.List;

public class Main {

   static List<String> makeHeapPollution() {
       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 = makeHeapPollution();

       System.out.println(stringsWithHeapPollution.get(0));
   }
}
控制台输出:

Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String
简单来说,堆污染是指类型 1 的对象应该位于堆上,但由于类型安全错误,А类型 1 的对象最终出现在堆上的情况。B在我们的示例中,这就是发生的情况。首先,我们创建了一个 Raw 变量numbers并为其分配了一个通用集合ArrayList<Number>。之后我们在那里添加了号码1
List<String> strings = numbers;
在这一行中,编译器试图通过发出警告“ Unchecked assignment... ”来警告我们可能出现的错误,但我们忽略了它。结果,我们有一个类型为 的泛型变量List<String>,它指向类型为 的泛型集合ArrayList<Number>。这种情况显然会带来麻烦!这就是发生的事情。使用新变量,我们将一个字符串添加到集合中。堆被污染了 - 我们首先向类型化集合中添加了一个数字,然后添加了一个字符串。编译器向我们发出警告,但我们忽略了它的警告,ClassCastException仅在程序运行时接收结果。跟它有什么关系呢varargs与泛型一起使用varargs很容易导致堆污染。 这是一个简单的例子:
import java.util.Arrays;
import java.util.List;

public class Main {

   static void makeHeapPollution(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");

       makeHeapPollution(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:)我们今天的讲座就到此结束。不要忘记解决几个问题,如果您还有时间和精力,请研究更多文献。“ Effective Java ”不会自动读取!:) 再见!
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION