Java中的泛型是什么,简单说来?
泛型是指参数化类型。泛型让你可以创建类、接口和方法,这些类型和方法可以在参数中指定具体类型,并且使用泛型,你可以编写处理不同类型并在编译时保证类型安全的代码。
不含泛型的示例:
List list = new ArrayList();
list.add("Hello");,
String s = (String) list.get(0); // 需要转换为字符串
使用泛型:
List<String> list = new ArrayList<>();
list.add("Hello");
String s = list.get(0); // 无需类型转换,类型安全有保障
为什么使用泛型(Generic)编程?
- 类型安全特性: 泛型允许你在编译时捕获类型错误,从而减少
ClassCastException
的出现。
// 没有泛型,可以存储任何类型的对象。
List list = new ArrayList();
list.add(10);
list.add("10");
// 有了泛型,需要指定要存储的对象类型。
List<Integer> 列表 = new ArrayList<Integer>();
list.add(10);
list.add("10"), // 编译时错误
2. 省去类型声明: 在获取元素时无需进行类型声明,因为编译器已经知道元素的类型。
// 在泛型出现之前,我们需要进行强制类型转换。
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0); // 强制类型转换
// 使用泛型之后,我们不需要进行对象的类型转换。
List<String> list = new ArrayList<String>();
list.add("hello");
String s = list.get(0);
3. 代码复用: 你可以编写一个类或方法,使其能处理各种数据类型,从而让代码更灵活且可复用。
import java.util.Arrays;
public class GenericSorting {
public static void main(String[] args) {
Integer[] a = {76, 55, 58, 23, 6, 50};
Character[] c = {'v', 'g', 'a', 'c', 'x', 'd', 't'};
String[] s = {"Vali", "Ali", "Ahmed", "Aysu", "Leman", "Orkhan", "Lale"};
System.out.println("排序后的整数数组 : ");
bubbleSort(a);
System.out.println("排序后的字符数组 : ");
bubbleSort(c);
System.out.println("排序后的字符串数组 : ");
bubbleSort(s);
}
// 使用冒泡排序算法对数组进行排序
public static <T extends Comparable<T>> void bubbleSort(T[] array) {
for (int i = 0; i < array.length - 1; i++) {
for (int j = 0; j < array.length - i - 1; j++) {
if (array[j].compareTo(array[j + 1]) > 0) {
swap(j, j + 1, array);
}
}
}
System.out.println(Arrays.toString(array));
}
// 交换数组中的两个元素
public static <T> void swap(int i, int j, T[] a) {
T t = a[i];
a[i] = a[j];
a[j] = t;
}
}
// 排序结果:
排序后的整数数组 : [6, 23, 50, 55, 58, 76]
排序后的字符数组 : [a, c, d, g, t, v, x]
排序后的字符串数组 : [Ahmed, Ali, Aysu, Lale, Leman, Orkhan, Vali]
泛型类
泛型类允许定义可以与任何数据类型配合的类。
语法:
// 这是一个泛型类,用于存储任意类型的物品.
class Box<T> {
private T item;
public void 设置项目(T item) {
this.item = item;
}
public T 获取项目() {
return item;
}
}
在此示例中,T 是一个类型参数,可以在实例化 Box 时将其替换为任何类型。
用法:
// 创建一个字符串盒子,并设置其内容为 "Hello",然后输出内容
Box<String> stringBox = new Box<>();
stringBox.setItem("Hello");
System.out.println(stringBox.getItem());
泛型方法
泛型方法允许你定义带有类型参数的方法,这样它们就可以在运行时处理任何类型的输入了。
语法结构:
public class GenericsExample {
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.println(element);
}
}
}
用法如下:
Integer[] intArray = {1, 2, 3};
String[] strArray = {"Hello", "World"};
GenericsExample.printArray(intArray);
GenericsExample.printArray(strArray);
泛型示例(示例仅供参考)
- 互为递归的类型参数约束
interface ConvertibleTo<T> {
T convert();
}
// ReprChange 类用于表示类型的转换关系
class ReprChange<T extends ConvertibleTo<S>,
S extends ConvertibleTo<T>> {
T t;
// 将 s 转换后的结果赋值给 t
void set(S s) { t = s.convert(); }
// 返回 t 转换后的结果
S get() { return t.convert(); }
// T 和 S 都需要是 ConvertibleTo 类型
}
2. 内部嵌套的泛型类
class Seq<T> {
T head;
Seq<T> tail;
Seq() {
this(null, null);
}
Seq(T head, Seq<T> tail) {
this.head = head;
this.tail = tail;
}
boolean isEmpty() {
return tail == null;
}
class Zipper<S> {
Seq<Pair<T, S>> zip(Seq<S> that) {
if (isEmpty() || that.isEmpty()) {
return new Seq<>();
}
Seq<T>.Zipper<S> tailZipper = tail.new Zipper<>();
return new Seq<>(new Pair<>(head, that.head), tailZipper.zip(that.tail));
}
}
}
class Pair<T, S> {
T fst;
S snd;
Pair(T f, S s) {
fst = f;
snd = s;
}
}
class Test {
public static void main(String[] args) {
Seq<String> strs = new Seq<>("a", new Seq<>("b", new Seq<>()));
Seq<Number> nums = new Seq<>(1, new Seq<>(1.5, new Seq<>()));
Seq<String>.Zipper<Number> zipper = strs.new Zipper<Number>();
Seq<Pair<String, Number>> combined = zipper.zip(nums);
}
}
3. 多重边界
interface IoeThrowingSupplier<S> {
S get();
}
public class Generics {
public static <S extends Readable & Closeable,
T extends Appendable & Closeable>
void copy(IoeThrowingSupplier<S> src,
IoeThrowingSupplier<T> tgt,
int size) throws IOException { // 可能抛出IO异常
try (S s = src.get(); T t = tgt.get()) {
CharBuffer buf = CharBuffer.allocate(size);
int i = s.read(buf); // 读取字符到缓冲区,结果保存在i中
while (i >= 0) { // 在还有字符可读时循环
buf.flip(); // 准备写
t.append(buf);
buf.clear(); // 准备读
i = s.read(buf); // 读取字符到缓冲区,结果保存在i中
}
}
}
}
泛型中的类型参数命名约定
学习泛型时,了解类型参数命名约定非常重要。常见的类型参数如下:
- T — 类型 (T)
- E — 项目
- K — 键
- N — 数值
- V — 值
限定类型参数
泛型也可以限制可用的类型,也被称为“有界类型”。
语法:
定义一个泛型方法 printDouble
,它接受一个 Number
类型的参数,并打印该参数的 double
值。
解释: 这里的 <T extends Number>
意味着 T 必须是 Number 的子类,因此只允许像 Integer、Double、Float 等这样的类型。
如何使用:
printDouble(5); // 可以处理整数(Integer)
printDouble(5.5); // 可以处理浮点数
// printDouble("Hello"); // 编译时错误,字符串不是数字类型
这里的通配符是问号 (?).
通配符用问号 ? 来表示,它们允许在泛型中使用未知类型。主要有三种通配符类型:
无限制通配符(<?>):可以匹配任何类型。
List<?> list = new ArrayList<String>();
list = new ArrayList<Integer>(); // 可以使用任何类型
2. 上限通配符(<? extends Type>): 接受特定类型或其任何子类型。
public void 打印数字(List<? extends 数字> list) {
for (数字 n : list) {
// 输出数字n
System.out.println(n);
}
}
3. 下界通配符(<? super Type>): 可以接受指定类型及其任何超类型。
public void addNumbers(List<? super Integer> list) {
list.add(10); // 可以添加整数或其子类对象,例如 Long
}
以下几种情况的区别:泛型列表,List<T>,List<Object>,List<?>,和 List<Student>。
每个版本的列表参数项都有其独特的应用场景:
• 未泛型化的 List:因为没有类型安全性的保障,很少使用。
• List<T>:既灵活又类型安全,通常用于泛型函数中。
• List<Object>:灵活性较差,只能容纳 Object 类型。
• List<?>:适用于接受任意类型列表的只读操作。
• List<Student>:针对特定类型提供强类型安全性。
Java泛型面试常见问题
- *什么是泛型,为什么它们很有用?\
泛型让类和方法能够处理任何指定的类型,这样代码既安全又可重用,同时避免了运行时类型错误,减少了类型转换的需求。
- 什么是类型擦除?
类型擦除机制是指在运行时移除泛型类型,用它们的边界或 Object
替换。这使得它与旧版本的 Java 兼容,但这也意味着你无法在运行时使用类型参数。
参考链接_
- <? extends T> 和 <? super T> 有什么不同?
<? extends T> 允许任何 T 的子类型,并用于这类操作主要用于只读操作(你无法安全地向其中添加元素)。
<? super T> 允许任何 T 的超类型,并用于只写操作(你可以安全地添加元素,但读取时可能得到 Object)。
4. 你能用基本类型和泛型一起使用吗?
不,Java 泛型仅适用于引用类型,而不是基本类型。你需要使用对应的包装类(比如用 Integer 替换 int,用 Double 替换 double)。
5. 限定类型的参数怎样提高代码的类型安全?
有界类型允许你指定类型约束,确保泛型仅与符合特定条件(例如,Number的子类)的类型一起使用,从而减少运行时的错误。
6. List<Object>和List<?>有什么不同?
List <对象类型>只能包含对象类型,不能赋值为特定类型的列表,例如 List <String>。
_列表 <?>_可以接受任何类型,所以更灵活,但在添加元素方面却受到限制。
7. 为什么我们在Java中不能创建泛型数组,例如,T[] array = new T[10]; ?
Java 不允许创建泛型数组,因为类型擦除这使得不安全。因此,你可以使用 Object[] 并在需要时进行类型转换,或者使用集合类。
在泛型中,PECS 是指什么?
PECS 代表 生产者使用上限通配符,消费者使用下限通配符。当你想要 生产 集合中的项目时,使用 <? extends T>
。当你想要 消费 项目时,使用 <? super T>
。
9.: 什么是类型推断?
类型推断是指编译器可以根据方法参数的类型推断出泛型类型。例如,如果我们向一个返回 T 的方法传递 T 参数,那么编译器就能推断出返回类型。让我们来试试通过调用上一个问题中的泛型方法。
int inferredInteger = returnType(1);
String inferredString = returnType("String");"); background-repeat: no-repeat; background-position: center center; background-color: rgb(99, 177, 117);">复制代码
正如我们所看到的,无需进行类型转换,也不需要传递任何泛型类型参数。参数类型只会推导返回类型。
资源如下。
- https://www.amazon.com/Java-Generics-Collections-Development-Process/dp/0596527756 (《Java泛型与集合》这本书的Amazon页面)
- https://docs.oracle.com/javase/specs/jls/se8/html/jls-4.html#jls-4.6 (Java SE 8语言规范中关于类型的描述)
- https://en.wikipedia.org/wiki/Covariance_and_contravariance_(computer_science) (维基百科上关于协变和逆变的页面)
共同学习,写下你的评论
评论加载中...
作者其他优质文章