什么是泛型?
泛型(Generics)是一种在编程中提供类型安全和代码复用的机制。
泛型允许在定义类、接口、方法时不指定具体的类型,而是在使用时再明确。这样做的好处有很多:
- 提高了代码的复用性:通过使用泛型,可以编写一个通用的逻辑,适用于多种不同的类型,而无需为每种类型都单独编写类似的代码。
- 增强了类型安全性:可以避免在运行时出现类型转换错误。例如,如果一个方法期望接收整数类型,使用泛型可以确保不会错误地传入其他类型的数据。
比如说,在 Java 中,我们可以定义一个泛型类 Box<T>
来表示一个可以存储任意类型对象的盒子:
class Box<T> {
private T item;
public void setItem(T item) {
this.item = item;
}
public T getItem() {
return item;
}
}
在这个例子中,Box
类是一个泛型类,其中T
是类型参数。T
可以在Box
类的定义中用作变量类型。可以创建 Box
的不同类型实例,如 Box<Integer>
用于存储整数,Box<String>
用于存储字符串。
为什么需要泛型?
在没有泛型的情况下,我们可能需要为每个数据类型都编写相应的类或方法,这样会导致代码冗余,并且难以维护。而有了泛型,我们可以将类型作为参数传递给类或方法,使其具有通用性,可以处理多种类型的数据。
-
适用于多种数据类型执行相同的代码
private static int add(int a, int b) { System.out.println(a + "+" + b + "=" + (a + b)); return a + b; } private static float add(float a, float b) { System.out.println(a + "+" + b + "=" + (a + b)); return a + b; } private static double add(double a, double b) { System.out.println(a + "+" + b + "=" + (a + b)); return a + b; }
如果没有泛型,要实现不同类型的加法,每种类型都需要重载一个add方法;通过泛型,我们可以复用为一个方法:
private static <T extends Number> double add(T a, T b) { System.out.println(a + "+" + b + "=" + (a.doubleValue() + b.doubleValue())); return a.doubleValue() + b.doubleValue(); }
基本语法
泛型的基本语法包括泛型类、泛型接口和泛型方法。
泛型类
public class Box<T> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
泛型接口
public interface Container<T> {
void add(T item);
T get(int index);
}
泛型方法
public class Util {
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.print(element + " ");
}
System.out.println();
}
}
使用示例
泛型类使用
Box<String> stringBox = new Box<>();
stringBox.setContent("Hello");
String content = stringBox.getContent();
System.out.println(content);
泛型方法使用
Integer[] intArray = {1, 2, 3, 4, 5};
Util.printArray(intArray);
String[] strArray = {"A", "B", "C"};
Util.printArray(strArray);
好啦,到这就可以啦,下面的是扩展,可以了解
泛型的实现原理
Java 的泛型实现采用了类型擦除的方式。这意味着在编译阶段,泛型类型的信息会被擦除,替换为其上限类型(如果没有指定上限类型,则默认是 Object
类型)。
例如,对于 List<String>
这样的泛型类型,在编译后的字节码中,它会被视为 List
。在运行时,实际上并不存在具体的泛型类型信息。
这样做的好处是保持了 Java 字节码的向后兼容性,但也带来了一些限制,比如不能在运行时获取泛型的具体类型参数。
如何编写一个泛型类?
首先,按照某种类型,例如:String
,来编写类:
public class Pair {
private String first;
private String last;
public Pair(String first, String last) {
this.first = first;
this.last = last;
}
public String getFirst() {
return first;
}
public String getLast() {
return last;
}
}
然后,把特定类型String
替换为T
,并申明<T>
:
public class Pair<T> {
private T first;
private T last;
public Pair(T first, T last) {
this.first = first;
this.last = last;
}
public T getFirst() {
return first;
}
public T getLast() {
return last;
}
}
类型参数 <T>
类型参数是用于定义泛型类、接口和方法时的占位符,表示某种类型。
在泛型中,<T>
中的 T
确实是自定义的,常见的类型参数有:
T
:表示类型(Type)。E
:表示集合元素(Element)。K``和
V`:分别表示键(Key)和值(Value)。N
:表示数字(Number)。S
,U
,V
等:用于多个类型参数的情况。
但是,这些都是约定俗成的,并不是强制的。你可以使用任何合法的字母或标识符作为泛型参数。
使用多个类型参数
public class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
}
在这个例子中,Pair
类使用了两个类型参数K
和V
,分别表示键和值。
虽然可以使用任意字母作为类型参数,但使用有意义的字母(如T
、E
、K
、V
等)更有助于代码的可读性和理解。如果你的代码涉及多个类型参数,建议选择有意义的字母来表示不同的类型。
类型参数使用示例
定义一个泛型类 Box
:
public class Box<T> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
使用这个泛型类:
Box<String> stringBox = new Box<>();
stringBox.setContent("Hello");
String content = stringBox.getContent();
通配符 <?>
通配符是用于实例化泛型类型时表示未知类型的占位符,用来表示不确定的类型,常见的有三种:
- 无界通配符:
<?>
,表示任意类型。 - 有界通配符(上界):
<? extends T>
,表示类型必须是T
或者T
的子类。 - 有界通配符(下界):
<? super T>
,表示类型必须是T
或者T
的父类。
通配符使用示例
定义一个方法来处理任何类型的Box
:
public static void printBox(Box<?> box) {
System.out.println(box.getContent());
}
调用这个方法:
Box<String> stringBox = new Box<>();
stringBox.setContent("Hello");
printBox(stringBox);
Box<Integer> intBox = new Box<>();
intBox.setContent(123);
printBox(intBox);
在这个例子中,<?>
是一个无界通配符,表示方法printBox
可以接受任何类型的Box
。
限定类型参数示例
有时候需要对类型参数进行限制,例如只允许某个类型及其子类:
public class NumberBox<T extends Number> {
private T number;
public void setNumber(T number) {
this.number = number;
}
public T getNumber() {
return number;
}
}
在这个例子中,T
被限定为Number
类型及其子类。
总结:泛型是Java中强大的特性,通过参数化类型提高了代码的复用性和类型安全性。理解和合理使用泛型可以显著提升代码的质量和可维护性。