Java的泛型概念
Java的泛型(Generics是一种参数化类型的机制。它允许在定义类、接口和方法时使用类型参数,这些类型参数可以在使用该类、接口或方法时被具体的类型所替换。
示例:
// 一个简单的泛型类
public class Box<T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
}
在上述代码中泛型类Box中T就是一个类型参数。这个类可以用来存储不同类型的对象,如Box可以用来存储整数,Box可以用来存储字符串。
Java的泛型提供了强大的功能,能够在类、接口和方法中使用类型参数。
1. 泛型类
泛型类是指在类定义时使用类型参数,这使得类可以处理不同类型的对象,而不需要为每个类型定义不同的类。
示例代码:
// 定义一个泛型类 Box
public class Box<T> {
private T value;
// 设置值
public void setValue(T value) {
this.value = value;
}
// 获取值
public T getValue() {
return value;
}
public static void main(String[] args) {
// 使用泛型类
Box<Integer> intBox = new Box<>();
intBox.setValue(10);
System.out.println("Integer value: " + intBox.getValue());
Box<String> strBox = new Box<>();
strBox.setValue("Hello, World!");
System.out.println("String value: " + strBox.getValue());
}
}
解释:
在这个例子中,Box是一个泛型类,T代表类型参数,表示Box类可以容纳任何类型的对象。T的类型可以在创建实例时指定。
Box指定T为Integer类型,而Box则指定T为String类型。
setValue和getValue方法通过泛型参数T确保类型安全,可以避免类型转换错误。
总结:
泛型类使得类可以适应不同类型的数据,而不需要重复定义多个类。
通过使用泛型,可以在编译时确保类型安全,避免运行时出现类型转换错误。
2. 泛型接口
泛型接口是指接口声明中包含类型参数,可以使得实现该接口的类在使用时指定类型。泛型接口允许接口方法操作不同类型的对象。
示例代码:
// 定义一个泛型接口
public interface Pair<K, V> {
K getKey();
V getValue();
}
// 实现泛型接口
public class SimplePair<K, V> implements Pair<K, V> {
private K key;
private V value;
public SimplePair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() {
return key;
}
public V getValue() {
return value;
}
public static void main(String[] args) {
// 使用泛型接口
Pair<String, Integer> pair = new SimplePair<>("age", 30);
System.out.println("Key: " + pair.getKey() + ", Value: " + pair.getValue());
}
}
解释:
Pair<K, V>是一个泛型接口,具有两个类型参数K和V,分别表示键和值的类型。
SimplePair<K, V>是Pair接口的实现类,它实现了getKey和getValue方法。
在使用时,创建了一个Pair<String, Integer>的实例,这表明键的类型是String,值的类型是Integer。
总结:
泛型接口使得接口可以定义与类型相关的操作,从而提高代码的灵活性。
泛型接口的使用允许为不同的类型定义通用接口,而不需要为每个类型编写不同的接口实现。
3. 泛型方法
泛型方法是在方法定义时使用类型参数,使得该方法能够处理不同类型的参数。与泛型类和接口不同,泛型方法只在方法签名中声明类型参数。
// 定义一个泛型方法
public class GenericMethodExample {
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.println(element);
}
}
public static void main(String[] args) {
// 调用泛型方法
Integer[] integers = {1, 2, 3};
String[] strings = {"Hello", "World"};
printArray(integers); // 输出整数数组
printArray(strings); // 输出字符串数组
}
}
解释:
printArray是一个泛型方法,它使用了类型参数T,使得该方法能够接受任何类型的数组。
在调用时,Integer[]和String[]数组被作为参数传递给方法,T分别被替换为Integer和String类型。
该方法遍历数组并打印每个元素。
总结:
泛型方法使得一个方法能够处理不同类型的参数,而不需要为每种类型编写不同的方法。
它通常与泛型类或接口一起使用,提供更大的灵活性和代码复用性。
总之
泛型类允许类定义时使用类型参数,这使得类可以操作多种类型的对象,避免了类型转换和重复代码的编写。
泛型接口让接口方法能支持不同类型的实现,使得代码更加灵活且可扩展。
泛型方法使得方法能在不依赖于类或接口的类型情况下,接受不同类型的参数,提高了代码的通用性。
4. 通配符(Wildcard)
在泛型中,通配符用于表示不特定的类型。通配符允许编写更加通用的代码,尤其是在处理泛型集合时,它能够提高灵活性,同时保持类型安全。常见的通配符有以下三种:? extends T
(上界通配符),? super T
(下界通配符),和?
(无界通配符)。
4.1 ? extends T
:上界通配符
? extends T
表示类型必须是 T
或 T
的子类。通过上界通配符,可以确保在传递参数时,元素类型为 T
或其子类,但无法往集合中添加元素,因为无法确定具体的类型(可能是 T
或其子类)。
示例代码:
public void printNumbers(List<? extends Number> numbers) {
for (Number number : numbers) {
System.out.println(number); // 可以读取,但不能修改
}
}
解释:
List<? extends Number>
表示一个列表,它的元素类型可以是Number
或者Number
的任何子类,比如Integer
、Double
、Float
等。- 在方法内部,您可以安全地读取
Number
类型的元素(for
循环中),但是无法往列表中添加元素,因为不知道具体的类型(可能是Integer
、Double
等)。例如,不能执行numbers.add(1)
,因为numbers
可能是List<Integer>
或List<Double>
,Integer
和Double
是不同的类型。
4.2 ? super T
:下界通配符
? super T
是一种通配符类型,被称为下界通配符。它代表的是一个未知类型,这个未知类型是T
或者是T
的超类型(父类型)。例如,如果T
是Integer
,那么? super Integer
可以是Number
(Integer
的父类型)或者Object
(所有类的父类型)等。
使用? super T
通配符时,从这个集合中读取元素会受到限制。因为编译器不确定集合中的元素具体是T
的哪种超类型,读取出来的元素在编译时会被当作Object
类型来处理。
示例代码:
public static <T> void addElements(List<? super T> list, T element) {
list.add(element);
}
- 在这个方法中,
List<? super T>
表示这个列表的元素类型是T
或者T
的超类型。这样就可以保证T
类型的element
能够安全地添加到这个列表中。
4.3 无限制通配符 ?
:可以是任何类型
?
是最通用的通配符,它表示可以接受任何类型的元素。使用这种通配符时,通常只能进行读取操作,不能进行修改操作。
示例代码:
public void printList(List<?> list) {
for (Object obj : list) {
System.out.println(obj); // 只能读取元素作为 Object 类型
}
}
解释:
List<?>
表示一个未知类型的列表,元素类型可以是任何类型。它允许处理任何类型的列表,但由于不知道具体的类型,因此只能读取元素(它们会被视为Object
类型),而不能往列表中添加元素。- 例如,无法执行
list.add("String")
,因为不知道该列表的具体类型。
5. 泛型的边界(Bounds)
在泛型中,边界用于限制类型参数的范围。通过设置上界(extends
)和下界(super
),可以让泛型更加灵活和安全。泛型的边界可以限制一个泛型类型只能是某个类或接口的子类或父类,从而更精确地控制类型。
上界限制(extends
)
上界限制通过 extends
关键字指定,表示类型参数必须是某个类或接口的子类。通常用于读取数据时,只允许该类型及其子类作为有效类型。
示例代码:
public <T extends Number> void printNumber(T number) {
System.out.println(number);
}
解释:
T extends Number
表示T
必须是Number
类型或其子类(如Integer
、Double
等)。- 在该方法中,能够安全地读取并打印
T
类型的值,因为T
确保了它是Number
或其子类的实例。 - 上界限制用于方法中需要读取某些特定类型时非常有用,避免了直接使用
Object
类型的做法。
下界限制(super
)
下界限制通过 super
关键字指定,表示类型参数必须是某个类或接口的父类。通常用于写入数据时,只允许该类型及其父类作为有效类型。
示例代码:
public <T> void addNumbers(List<? super T> list) {
list.add(1); // 可以安全地添加 T 或其子类的元素
}
解释:
? super T
表示可以向该集合中添加T
类型或其父类的类型(例如Object
)。- 下界限制常用于需要向集合中添加元素时,保证可以添加指定类型及其父类的元素。
6. 泛型的类型擦除
Java的泛型使用类型擦除技术,在编译时将所有泛型类型转换为原始类型(通常是 Object
或指定的上界)。
示例代码:
List<String> list = new ArrayList<>();
list.add("Hello");
// 在运行时,编译后的代码不会区分 List<String> 和 List<Integer>
解释:
- 在编译时,
List<String>
被擦除为List
,这意味着编译后不再保留类型参数String
。 - 由于类型擦除,Java 会在编译时移除泛型信息,并将
List<String>
和List<Integer>
都当作普通的List
类型处理。这也解释了为什么在运行时无法直接访问泛型类型。 - 由于擦除,Java 不能直接获取泛型类型,因此像
List<String>
和List<Integer>
在运行时都表现为List
,因此不能直接进行类型判断或类型转换。
7. 泛型的应用场景
常见的应用场景包括:
- List:表示一个元素为
T
类型的列表。 - Map<K, V>:表示键值对集合,其中键的类型是
K
,值的类型是V
。 - Set:表示一个元素为
T
类型的集合,集合中的元素是唯一的。 - Queue:表示一个元素为
T
类型的队列。 - Optional:用于表示一个可能为空的值,
Optional
存储类型为T
的元素。
常见问题
1. Java中泛型是什么?使用泛型的好处是什么?
Java 泛型 是一种通过类型参数化的机制,允许在类、接口和方法中定义类型参数。通过泛型,Java 可以在编译时确保类型安全,避免出现类型转换错误。
2. 常用泛型标记有哪些?
-
T
:表示任意类型(通常用于类或方法中的类型参数)。例如,T
代表 “Type”。- 示例:
public <T> void print(T value) {...}
- 示例:
-
E
:表示元素类型,常用于集合类中。- 示例:
public interface List<E> {...}
- 示例:
-
K
:表示键类型,常用于Map
类中。- 示例:
public interface Map<K, V> {...}
- 示例:
-
V
:表示值类型,常用于Map
类中。- 示例:
public interface Map<K, V> {...}
- 示例:
-
N
:表示数字类型,通常用于表示数值范围。- 示例:
public <N extends Number> void sum(N num) {...}
- 示例:
-
?
: 表示不确定的Java类型。
3. 什么是类型擦除?
类型擦除是指,在编译时Java 将泛型类型转换成原始类型(通常是 Object
或指定的上界),并移除类型参数。这意味着泛型信息在运行时不可用。
示例:
List<String> list1 = new ArrayList<>();
List<Integer> list2 = new ArrayList<>();
System.out.println(list1.getClass() == list2.getClass()); // 输出 true
解释:
- 在编译时,
List<String>
和List<Integer>
会都被转换成List
。这就意味着在运行时,List<String>
和List<Integer>
都会被视为相同类型List
,因此它们的getClass()
方法返回的是相同的类对象。
4. 如何在类上使用泛型?
示例:泛型类
// 定义一个泛型类 Box
public class Box<T> {
private T value;
public void setValue(T value) {
this.value = value;
}
public T getValue() {
return value;
}
public static void main(String[] args) {
Box<Integer> intBox = new Box<>();
intBox.setValue(10);
System.out.println("Integer value: " + intBox.getValue());
Box<String> strBox = new Box<>();
strBox.setValue("Hello, World!");
System.out.println("String value: " + strBox.getValue());
}
}
标签:Java,List,value,夯实,接口,类型,泛型,public
From: https://blog.csdn.net/m0_60235638/article/details/143753328