CodeGym /Java 博客 /随机的 /使用泛型时使用可变参数
John Squirrels
第 41 级
San Francisco

使用泛型时使用可变参数

已在 随机的 群组中发布
你好!在今天的课程中,我们将继续学习泛型。碰巧,这是一个很大的话题,但无法回避——它是语言中极其重要的一部分 :) 当您研究关于泛型的 Oracle 文档或阅读在线教程时,您会遇到术语不可具体化的类型具体化类型。具体化类型是一种信息在运行时完全可用的类型。在 Java 中,此类类型包括原始类型、原始类型和非泛型类型。相反,不可具体化的类型是其信息在运行时被擦除并且变得不可访问的类型。碰巧的是,这些是泛型—— List<String>,List<Integer>等等。

顺便说一句,你还记得可变参数是什么吗?

万一您忘记了,这是一个变长参数。它们在我们不知道有多少参数可能传递给我们的方法的情况下很有用。例如,如果我们有一个具有sum方法的计算器类。该sum()方法可以接收 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
这向我们展示了将可变参数与泛型结合使用时有一些重要的特性。让我们看看下面的代码:

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")
       );
   }
}
该方法将 a和任意数量的对象addAll()作为输入,然后将所有这些对象添加到列表中。在方法中,我们两次调用我们的方法。在第一种情况下,我们将两个普通字符串添加到. 这里一切都井井有条。在第二种情况下,我们将两个对象添加到. 但是这里我们意外收到警告: List<E>Emain()addAll()ListPair<String, String>List

Unchecked generics array creation for varargs parameter
这意味着什么?为什么我们会收到警告,为什么会提到 an array?毕竟,我们的代码没有array!让我们从第二种情况开始。警告中提到了数组,因为编译器将可变长度参数 (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) {

   // Compilation error Generic array creation
  Pair<String, String>[] array = new Pair<String, String>[10];
}
原因很明显:类型安全。您会记得,在创建数组时,您肯定需要指定数组将存储哪些对象(或基元)。

int array[] = new int[10];
在我们之前的一节课中,我们详细研究了类型擦除。Pair在这种情况下,类型擦除会导致我们丢失对象存储对的信息<String, String>。创建数组是不安全的。 当使用涉及可变参数和泛型的方法时,一定要记住类型擦除及其工作原理。如果您完全确定您编写的代码并且知道它不会导致任何问题,您可以使用注释关闭与 varargs 相关的警告@SafeVarargs

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

   for (E element : array) {
       list.add(element);
   }
}
如果将此注释添加到您的方法中,我们之前遇到的警告将不会出现。将可变参数与泛型一起使用时可能发生的另一个问题是堆污染。 使用泛型时使用可变参数 - 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;
在这一行,编译器试图通过发出“未经检查的赋值... ”警告来警告我们可能存在的错误,但我们忽略了它。我们最终得到一个 type 的泛型变量List<String>,它指向一个 type 的泛型集合ArrayList<Number>。显然,这种情况会导致麻烦!确实如此。使用我们的新变量,我们将一个字符串添加到集合中。我们现在有堆污染——我们在参数化集合中添加了一个数字,然后是一个字符串。编译器警告我们,但我们忽略了它的警告。结果,我们ClassCastException只在程序运行时得到一个。那么这与可变参数有什么关系呢? 将可变参数与泛型一起使用很容易导致堆污染。 这是一个简单的例子:

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[]未知类型对象的数组(不要忘记可变参数在编译期间变成常规数组)。正因为如此,我们可以轻松地将它分配给Object[] array方法第一行中的变量——我们列表中对象的类型已被删除!现在我们有了一个Object[]变量,我们可以向它添加任何东西,因为 Java 中的所有对象都继承Object! 起初,我们只有一个字符串列表数组。但是多亏了类型擦除和我们对可变参数的使用,我们可以轻松地添加一个数字列表,我们确实这样做了。结果,我们通过混合不同类型的对象来污染堆。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