Java 泛型类、泛型方法和泛型接口
一、课程信息
课程主题
泛型类、泛型方法和泛型接口
课程目标
- 理解泛型的概念和引入泛型的必要性。
- 掌握泛型类、泛型方法和泛型接口的定义与使用。
- 能够运用泛型解决实际编程中的类型安全问题。
课程重点
- 泛型类、泛型方法和泛型接口的语法。
- 泛型在实际编程中的应用。
课程难点
- 泛型的类型擦除机制。
- 灵活运用泛型解决复杂问题。
二、课程导入
传统编程的问题
在 Java 中,我们经常需要编写可以处理不同类型数据的代码。在没有泛型之前,通常使用 Object
类型来实现代码的复用,但这会带来一些问题。
示例代码
class Box {
private Object content;
public void setContent(Object content) {
this.content = content;
}
public Object getContent() {
return content;
}
}
public class TraditionalBoxExample {
public static void main(String[] args) {
Box box = new Box();
box.setContent(10);
// 需要进行强制类型转换
Integer num = (Integer) box.getContent();
System.out.println(num);
// 可能会出现运行时错误
box.setContent("Hello");
// 以下代码会抛出 ClassCastException
// Integer wrongNum = (Integer) box.getContent();
}
}
在这个例子中,Box
类使用 Object
类型来存储内容,这意味着可以存储任意类型的数据。但在获取内容时,需要进行强制类型转换,并且如果不小心存储了错误类型的数据,运行时会抛出 ClassCastException
。
泛型的引入
为了解决上述问题,Java 引入了泛型。泛型允许我们在定义类、方法或接口时使用类型参数,从而实现代码的复用和类型安全。
三、泛型类
泛型类的定义
泛型类是指在定义类时使用类型参数的类。类型参数可以在类的内部作为一种类型来使用。
语法
class 类名<类型参数列表> {
// 类的成员
}
示例代码
// 定义一个泛型类
class Box<T> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
public class GenericBoxExample {
public static void main(String[] args) {
// 创建一个存储整数的 Box 对象
Box<Integer> intBox = new Box<>();
intBox.setContent(10);
Integer num = intBox.getContent();
System.out.println(num);
// 创建一个存储字符串的 Box 对象
Box<String> stringBox = new Box<>();
stringBox.setContent("Hello");
String str = stringBox.getContent();
System.out.println(str);
// 编译时会检查类型,不允许添加错误类型的元素
// intBox.setContent("World");
}
}
在这个例子中,Box
类是一个泛型类,T
是类型参数。在创建 Box
对象时,我们可以指定具体的类型,如 Integer
或 String
。这样,编译器会在编译时检查类型,避免了运行时的 ClassCastException
。
泛型类的多个类型参数
泛型类可以有多个类型参数。
示例代码
// 定义一个具有两个类型参数的泛型类
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;
}
}
public class MultipleTypeParamsExample {
public static void main(String[] args) {
// 创建一个 Pair 对象,键为 String 类型,值为 Integer 类型
Pair<String, Integer> pair = new Pair<>("Apple", 10);
System.out.println(pair.getKey() + ": " + pair.getValue());
}
}
四、泛型方法
泛型方法的定义
泛型方法是指在定义方法时使用类型参数的方法。泛型方法可以在普通类中定义,也可以在泛型类中定义。
语法
修饰符 <类型参数列表> 返回类型 方法名(参数列表) {
// 方法体
}
示例代码
public class GenericMethodExample {
// 定义一个泛型方法
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.print(element + " ");
}
System.out.println();
}
public static void main(String[] args) {
Integer[] intArray = {1, 2, 3, 4, 5};
String[] stringArray = {"Hello", "World"};
printArray(intArray);
printArray(stringArray);
}
}
在这个例子中,printArray
方法是一个泛型方法,<T>
表示类型参数。它可以处理不同类型的数组。
泛型方法与泛型类的区别
泛型方法可以独立于泛型类存在,即使在非泛型类中也可以定义泛型方法。而泛型类是在类的层面上使用类型参数。
示例代码
class NonGenericClass {
// 泛型方法
public <T> T getFirstElement(T[] array) {
if (array != null && array.length > 0) {
return array[0];
}
return null;
}
}
public class GenericMethodInNonGenericClass {
public static void main(String[] args) {
NonGenericClass obj = new NonGenericClass();
Integer[] intArray = {1, 2, 3};
Integer firstInt = obj.getFirstElement(intArray);
System.out.println(firstInt);
String[] stringArray = {"Hello", "World"};
String firstString = obj.getFirstElement(stringArray);
System.out.println(firstString);
}
}
五、泛型接口
泛型接口的定义
泛型接口是指在定义接口时使用类型参数的接口。实现泛型接口的类可以指定具体的类型,也可以继续使用类型参数。
语法
interface 接口名<类型参数列表> {
// 接口的抽象方法
}
示例代码
// 定义一个泛型接口
interface Generator<T> {
T generate();
}
// 实现泛型接口并指定具体类型
class IntegerGenerator implements Generator<Integer> {
@Override
public Integer generate() {
return (int) (Math.random() * 100);
}
}
// 实现泛型接口并继续使用类型参数
class GenericGenerator<T> implements Generator<T> {
private T value;
public GenericGenerator(T value) {
this.value = value;
}
@Override
public T generate() {
return value;
}
}
public class GenericInterfaceExample {
public static void main(String[] args) {
// 使用实现了具体类型的泛型接口
Generator<Integer> intGenerator = new IntegerGenerator();
Integer num = intGenerator.generate();
System.out.println(num);
// 使用继续使用类型参数的泛型接口
Generator<String> stringGenerator = new GenericGenerator<>("Hello");
String str = stringGenerator.generate();
System.out.println(str);
}
}
六、类型擦除
类型擦除的概念
Java 中的泛型是通过类型擦除实现的。在编译时,泛型类型信息会被擦除,替换为原始类型。例如,对于泛型类 Box<T>
,编译后会变成 Box
,类型参数 T
会被替换为 Object
。
示例代码
import java.util.ArrayList;
import java.util.List;
public class TypeErasureExample {
public static void main(String[] args) {
List<Integer> intList = new ArrayList<>();
List<String> stringList = new ArrayList<>();
// 运行时类型相同
System.out.println(intList.getClass() == stringList.getClass());
}
}
这里 intList
和 stringList
在运行时的类型都是 ArrayList
,泛型类型信息被擦除了。
类型擦除的影响
类型擦除可能会导致一些泛型相关的特性在运行时不可用,例如不能使用 instanceof
来检查泛型类型。
import java.util.ArrayList;
import java.util.List;
public class TypeErasureLimitExample {
public static void main(String[] args) {
List<Integer> intList = new ArrayList<>();
// 以下代码会编译错误
// if (intList instanceof List<Integer>) { }
}
}
七、课堂练习
练习 1
定义一个泛型类 Container
,包含一个泛型成员变量和相应的 get
、set
方法,并创建不同类型的 Container
对象进行测试。
练习 2
定义一个泛型方法 swap
,用于交换数组中两个指定位置的元素,并使用该方法交换整数数组和字符串数组中的元素。
练习 3
定义一个泛型接口 Processor
,包含一个抽象方法 process(T t)
,然后实现该接口,分别处理整数和字符串。
八、课程总结
重点回顾
- 泛型的概念和引入泛型的必要性。
- 泛型类、泛型方法和泛型接口的定义与使用。
- 类型擦除的概念和影响。
注意事项
- 在使用泛型时,要注意类型参数的使用范围和限制。
- 理解类型擦除机制,避免在运行时依赖泛型类型信息。
九、课后作业
作业 1
实现一个泛型的 Stack
类,包含入栈、出栈和获取栈顶元素的方法,并使用该类存储不同类型的数据。
作业 2
定义一个泛型接口 Converter
,包含一个抽象方法 convert(T t)
,用于将一种类型的数据转换为另一种类型。然后实现该接口,将整数转换为字符串,将字符串转换为整数。