文章目录
- 泛型
- 一、概述
- 二、泛型的使用
- 三、泛型通配符
-
- 1、<?>
- [2、<? extends T>](#2、<? extends T>)
- [3、<? super T>](#3、<? super T>)
- 四、泛型的擦除
泛型
一、概述
泛型(Generic)是一种机制,允许你编写与数据类型无关的代码,增加代码的灵活性和可重用性。
- 泛型在定义的时候不具体,使用的时候才变得具体。在使用的时候确定泛型的具体数据类型。
泛型的作用:
- 安全性 :编译时检查类型,将运行时期的
ClassCastException
,转移到编译时期的编译失败。 - 灵活性 :使类型参数化,可以预先地使用未知的类型,让设计的代码更通用灵活。
- 重用性:一个泛型类或方法可以处理多种数据类型,减少代码重复。
- 维护性:泛型代码通常更清晰,容易理解和维护。
注意事项:
- 泛型只能在编译阶段起作用 ,到了运行阶段就会被擦除。
- 泛型只能是引用数据类型,不能是 基本数据类型。
- 泛型在使用时指定实际的类型,如果不指定默认为Object类型。
二、泛型的使用
1、类
定义:类名之后
java
// 泛型一般用大写的单个字母表示,可以定义多个泛型,使用逗号分隔。
修饰符 class 类名<A, B, C> {}
使用:在类中使用,可以作为 实例方法 的 参数 和 返回值(静态方法不支持)
java
class Example<T> {
// 作为实例方法的参数类型
public void show(T t) {
System.out.println(t);
}
// 作为实例方法的返回值类型
public T get(int index) {
return null;
}
// 静态方法 不能使用 类上定义的泛型
// public static void test1(T t) {...}
// public static T test2(int index) {...}
}
指定类型:创建对象时
java
public class Test {
public static void main(String[] args) {
// 在创建对象时,根据需要指定泛型的类型
DataShow<String> ds = new DataShow<>();
ds.show("Hello");
}
}
2、方法
定义:返回值之前
java
修饰符 <T> 返回值类型 方法名(T t) {}
使用:在方法内部使用
java
class Example {
public static <T> void show(T t) {
// 在方法内部使用
System.out.println("t = " + t);
}
// 不同方法的泛型名称可以一致
public static <T> void show2(T t) {}
}
指定类型:调用方法时,根据传参的类型
java
public class Test {
public static void main(String[] args) {
show(100); // T -> Integer
show("Hello"); // T -> String
}
}
3、接口
定义:接口名之后
java
public interface 接口名<E> {}
使用:作为 接口方法 的 参数 或 返回值
java
public interface Example<T> {
void method1(T t);
T method2();
}
指定类型:
1、定义接口的实现类时,确定泛型的类型
java
public class ExampleChild1 implements Example<String>{
@Override
public void method1(String s) {
System.out.println(s);
}
@Override
public String method2() {
return null;
}
}
2、定义接口的实现类时,继续沿用泛型
java
public class ExampleChild2<T> implements Example<T> {
@Override
public void method1(T t) {
System.out.println(t);
}
@Override
public T method2() {
return null;
}
}
在创建接口的实现类对象时,确定泛型的类型
java
public class Test {
public static void main(String[] args) {
ExampleChild2<String> exampleChild2 = new ExampleChild2<>();
exampleChild2.method1("hello");
String str = exampleChild2.method2();
}
}
三、泛型通配符
泛型通配符
<?>
,常用于泛型方法和类中,帮助实现更加灵活和通用的类型操作。
1、<?>
<?>
:表示任意类型,适用于我们不关心具体类型的场景。(只能使用Object类中的共性方法)
java
public void printList(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
使用举例
java
public class Test {
public static void main(String[] args) {
List<Integer> integerList = new ArrayList<>();
integerList.add(1);
integerList.add(2);
// 使用泛型方法
printList(integerList);
}
public static void printList(List<?> list) {
for (Object obj : list) {
System.out.println(obj);
}
}
}
2、<? extends T>
<? extends T>
:表示 T
或 T
的子类型,适用于读取操作。(可以使用父类T
中的公共方法)
java
public void processNumbers(List<? extends Number> numbers) {
for (Number number : numbers) {
System.out.println(number.intValue());
}
Long a = 1L; // Long extends Number
numbers.add(a); // error,Java 的泛型系统为了类型安全,不允许向这样的列表中添加特定类型的元素(除 null 外)。
}
java
public <T extends Number> void processNumbers(List<T> numbers) {
for (Number number : numbers) {
System.out.println(number.intValue());
}
Long a = 1L; // Long extends Number
numbers.add(a); // error
}
使用举例
java
public class Test {
public static void main(String[] args) {
List<Integer> integerList = new ArrayList<>();
integerList.add(1);
integerList.add(2);
// 使用泛型方法
printNumbers(integerList);
}
public static <T extends Number> void printNumbers(List<T> list) {
for (T number : list) {
System.out.println(number);
}
}
}
3、<? super T>
<? super T>
:表示 T
或 T
的父类型,适用于写入操作。
java
public void addNumbers(List<? super Integer> list) {
list.add(1); // 可以安全地添加 Integer 类型的元素
list.add(2); // 也可以添加其他 Integer 类型的元素
}
java
// 编译错误! `super` 只能用在 泛型方法 或 类中的方法参数 中来指定一个类型范围。
public <T super Integer> void addNumbers(List<T> list) {...}
使用举例
java
public class Test {
public static void main(String[] args) {
List<Number> numbers = new ArrayList<>();
// 使用泛型方法
addNumbers(numbers);
System.out.println(numbers);
}
public static void addNumbers(List<? super Integer> list) {
list.add(1); // 可以添加 Integer 类型的对象
list.add(2); // 可以添加 Integer 类型的对象
}
}
四、泛型的擦除
泛型的擦除是指,在编译期间,Java 编译器会将泛型信息擦除掉,泛型类型参数会被替换为其限定类型。
- 默认情况下,泛型类型参数会被替换为
Object
- 如果泛型有上限,如
T extends Number
,擦除后会使用上限类型(Number
)。
例如:
List<String>
和List<Integer>
在编译后都会被擦除为List
。List<T extends Number>
会擦除为List<Number>
。
泛型擦除的主要目的是为了 兼容 Java 的早期版本 和 简化虚拟机的实现。
- 泛型擦除将泛型类型转换为
Object
或其超类,从而使得 泛型代码 可以与 旧版本的 Java 代码 相互操作。 - 泛型擦除意味着 泛型的具体类型(
T
)在字节码中不可见,保持向后兼容性的同时,避免对字节码格式进行重大修改。
但是,这也会导致一些泛型相关的信息在运行时不可用,需要在编写泛型代码时注意擦除造成的影响。
1、泛型的擦除
java
public class GenericMethodExample {
// 泛型方法 在擦除后会变成 `print(Object data)`
public <T> void print(T data) {
System.out.println(data);
}
// 原始类型的方法 -> 编译错误!因为与擦除后的泛型方法冲突了
public void print(Object data) {
System.out.println(data);
}
}
java
public class GenericMethodExample {
// 泛型方法 在擦除后会变成 `print(Object data)`
public <T> void print(T data) {
System.out.println(data);
}
// 原始类型的方法 -> 编译通过!因为泛型默认擦除为 Object,这里是 String,重载
public void print(String data) {
System.out.println(data);
}
}
2、泛型边界的擦除
java
public void processNumbers(List<? extends Number> numbers) {
for (Number number : numbers) {
System.out.println(number.intValue());
}
Long a = 1L; // Long extends Number
numbers.add(a); // error
}
List<T extends Number>
在编译后会被擦除为List<Number>
。
java
public <T extends Number> void processNumbers(List<T> numbers) {
for (Number number : numbers) {
System.out.println(number.intValue());
}
Long a = 1L; // Long extends Number
numbers.add(a); // error
}
- 泛型参数
T
在运行时被擦除为Number
类型。
3、无法实例化泛型类型
java
public class GenericInstantiationExample<T> {
private T instance;
public GenericInstantiationExample() {
// 编译错误:无法直接实例化泛型类型
// instance = new T(); // 错误
}
public GenericInstantiationExample(Class<T> clazz) {
try {
instance = clazz.getDeclaredConstructor().newInstance();
} catch (Exception e) {
e.printStackTrace();
}
}
}
new T()
在编译时会引发错误,因为T
的实际类型在运行时未知。- 可以使用反射(通过传递
Class<T>
对象)来实例化对象。