CodeGym /Java 博客 /随机的 /Java 中的泛型
John Squirrels
第 41 级
San Francisco

Java 中的泛型

已在 随机的 群组中发布
你好!我们将讨论 Java 泛型。我必须说你会学到很多东西!不仅这节课,而且接下来的几节课,都将专门讨论泛型。所以,如果您对泛型感兴趣,那么今天是您的幸运日:您将学到很多关于泛型特性的知识。如果没有,请辞职并放松!:) 这是一个非常重要的主题,您需要了解它。让我们从简单的开始:“什么”和“为什么”。

什么是 Java 泛型?

泛型是具有参数的类型。创建泛型类型时,您不仅要指定类型,还要指定它将使用的数据类型。我猜你已经想到了最明显的例子:ArrayList!这就是我们通常在程序中创建一个的方式:

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 的历史并尝试回答“为什么?”这个问题。为此,我们将编写自己的 ArrayList 类的简化版本。我们的列表只知道如何向内部数组添加数据和从中检索数据:

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。我们没有使用通用类型。我们不想在add()方法中包含显式的“instanceof Integer ”检查。如果这样做,我们的整个类将只适用于Integer,我们将不得不为世界上所有其他数据类型编写一个类似的类!我们将依赖我们的程序员,并在代码中留下注释以确保他们不会添加我们不想要的任何内容:

// 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)
这种情况最糟糕的部分是什么?当然不是程序员的粗心。最糟糕的是,不正确的代码最终出现在我们程序的重要位置并成功编译。现在我们不会在编写代码时遇到错误,而只会在测试期间遇到错误(这是最好的情况!)。在开发的后期阶段修复错误的成本要高得多——无论是金钱还是时间。这正是泛型给我们带来好处的地方:泛型类可以让不幸的程序员立即检测到错误。该程序根本无法编译!

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类来查看此类错误。只需从普通 ArrayList 中 删除尖括号和类型 ( <Integer> )!

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 的“本机”机制,我们也可能会犯这种错误并创建不安全的集合。但是,如果我们将此代码粘贴到 IDE 中,我们会收到警告:“未经检查的调用 add(E) 作为 java.util.List 原始类型的成员”我们被告知添加项目时可能会出错到缺少通用类型的集合。但是“原始类型”这个短语是什么意思呢?原始类型是其类型已被删除的泛型类。换句话说,List myList1原始类型与原始类型相反的是泛型类型——带有参数化类型指示的泛型类。例如,List<String> myList1. 您可能会问为什么该语言允许使用原始类型?原因很简单。Java 的创建者在语言中保留了对原始类型的支持,以避免产生兼容性问题。到 Java 5.0 发布时(泛型首次出现在这个版本中),已经有很多代码是使用原始类型编写的。因此,该机制至今仍受支持。我们在课程中多次提到了Joshua Bloch的经典著作《Effective Java》。作为该语言的创造者之一,他在书中 并没有跳过原始类型泛型类型。Java 中的泛型是什么? - 2该书的第 23 章有一个非常雄辩的标题:“不要在新代码中使用原始类型”,这是您需要记住的。使用泛型类时,切勿将泛型类型转换为原始类型

通用方法

Java 允许您通过创建所谓的泛型方法来参数化各个方法。这些方法有何帮助?最重要的是,它们的帮助在于它们让您可以使用不同类型的方法参数。如果可以将相同的逻辑安全地应用于不同的类型,那么泛型方法可能是一个很好的解决方案。考虑这是一个非常简单的例子:假设我们有一个名为myList1的列表。我们想从列表中删除所有值并用新值填充所有空白区域。这是我们的类使用泛型方法的样子:

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 ()方法说明了如何使用fill()方法轻松处理不同类型的数据。首先,我们使用带有字符串列表和字符串作为输入的方法,然后使用整数列表和整数。控制台输出:

[New String, New String, New String] [888, 888, 888]
想象一下,如果我们没有泛型方法并且需要30 个不同类的fill()方法的逻辑。我们将不得不为不同的数据类型编写相同的方法 30 次!但是多亏了泛型方法,我们可以重用我们的代码!:)

通用类

您不限于标准 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<>();
在最后一行代码中,当我们尝试将数字 12345 放入框中时,出现编译错误!就这么简单!我们已经创建了自己的通用类!:) 至此,今天的课程就结束了。但我们并没有与泛型说再见!在接下来的课程中,我们将讨论更多高级功能,所以不要走开!) 为了巩固您所学的知识,我们建议您观看我们的 Java 课程中的视频课程
祝你学业成功!:)
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION