你好!今天我们来谈谈泛型。我必须说你会学到很多新东西!不仅如此,接下来的几堂课也将专门讨论泛型。 因此,如果您对这个主题感兴趣,那么您很幸运:今天您将了解很多有关泛型的特性。好吧,如果没有,冷静下来,放松一下!:) 这是一个非常重要的话题,你需要了解它。让我们从一个简单的开始:“什么”和“为什么”。什么是仿制药? 泛型是带有参数的类型。 创建泛型时,您不仅指定其类型,还指定它应使用的数据类型。我想最明显的例子已经浮现在你的脑海里了——这就是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
。我们没有仿制药。Integer
我们无法在 中显式指定 check 的 o 实例add()
。那么我们的整个类将只适用于Integer
,并且我们将不得不为世界上存在的所有数据类型编写相同的类!我们决定依靠我们的程序员,只需在代码中留下注释,以便他们不会在其中添加任何不必要的内容:
//use it ONLY with 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 线程“main”中的异常 java.lang.ClassCastException:java.lang.String 无法在 Main.main(Main.java:14) 处转换为 java.lang.Integer 在这种情况下最糟糕的是什么?远不是程序员的不专心。最糟糕的是,错误的代码最终出现在我们程序的重要位置并成功编译。现在我们不会在编码阶段看到错误,而只会在测试阶段看到错误(这是最好的情况!)。 在开发后期修复错误会花费更多的钱和时间。 这正是泛型的优点:泛型类可以让不幸的程序员立即检测到错误。该代码根本无法编译!
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无法在Main.main(Main.java:16)处转换为java.lang.Integer 也就是说,即使使用“本机”工具Java,你可能会犯这个错误并创建一个不安全的集合。但是,如果我们将此代码粘贴到 IDEa 中,我们会看到一条警告:“ Unchecked call to add(E) as a member of raw type of java.util.List ”它告诉我们,向 a 中添加元素时可能会出现问题。没有泛型的集合不是这样的。但是“原始类型”这个词是什么意思呢?直译是相当准确的——“原始类型”或“脏类型”。 Raw type
是一个泛型类,其类型已从中删除。 换句话说,List myList1
这是Raw type
。相反的raw type
是使用类型规范正确创建的generic type
泛型类(也称为类)。parameterized type
例如,List<String> myList1
。您可能有一个问题:为什么允许使用它raw types
?原因很简单。Java 的创建者留下了对该语言的支持raw types
,以免造成兼容性问题。当 Java 5.0 发布时(泛型在这个版本中首次出现),很多代码已经使用raw types
. 因此,这种可能性在今天仍然存在。我们在讲座中已经不止一次提到过Joshua Bloch的经典著作《Effective Java》。raw types
作为该语言的创造者之一,他并没有忽视书中使用and的话题generic types
。 本书第 23 章有一个非常雄辩的标题:“不要在新代码中使用原始类型。” 这是你需要记住的事情。使用泛型类时,切勿将它们转换generic type
为raw type
.
类型化方法
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("Старая строка 1");
strings.add("Старая строка 2");
strings.add("Старая строка 3");
fill(strings, "Новая строка");
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()
可以轻松地处理不同类型的数据。首先,它接受一个字符串列表和一个字符串作为输入,然后是一个数字列表和一个数字。 控制台输出: [Newline, Newline, Newline] [888, 888, 888] 想象一下,如果fill()
我们需要 30 个不同类的方法逻辑,并且我们没有通用方法。我们将被迫为不同的数据类型编写相同的方法 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("Старая строка");
System.out.println(stringBox.get());
stringBox.set("Новая строка");
System.out.println(stringBox.get());
stringBox.set(12345);//ошибка компиляции!
}
}
我们的类Box<T>
(“盒子”)已键入。在创建过程中为其指定了数据类型 () 后<T>
,我们将无法再将其他类型的对象放入其中。这可以在示例中看到。创建时,我们指定我们的对象将使用字符串:
Box<String> stringBox = new Box<>();
当我们在最后一行代码中尝试将数字 12345 放入框中时,我们收到编译错误!就像这样,我们创建了自己的泛型类!:) 今天的讲座到此结束。但我们并没有告别仿制药!在接下来的讲座中,我们将讨论更高级的功能,所以不要说再见!) 祝你学习顺利!:)
GO TO FULL VERSION