目录
Java 中的泛型是什么?
泛型是指参数化类型。泛型允许您创建对指定为参数的类型进行操作的类、接口和方法。使用泛型,您可以编写适用于不同类型的代码,同时强制执行编译时类型安全。
不使用泛型的示例:
List list = new ArrayList ();
list.add( "Hello" );
String s = (String) list.get( 0 ); // 必须转换为 String
使用泛型:
List < String > list = new ArrayList<>();
list.add( "Hello" );
String s = list.get ( 0 ) ; // 无需强制转换,确保类型安全
为什么要使用泛型?
1.类型安全
泛型允许您在编译时捕获类型错误,从而减少 ClassCastException 的可能性。
// 不使用泛型时,我们可以存储任何类型的对象。
List list = new ArrayList();
list.add(10);
list.add("10");
// 使用泛型时,需要指定我们要存储的对象的类型。.
List<Integer> list = new ArrayList<Integer>();
list.add(10);
list.add("10");// compile-time error
2.消除强制类型转换
检索元素时无需强制类型转换,因为编译器已经知道类型。
// 在使用泛型之前,我们需要进行类型转换。
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0); //typecasting
// 在使用泛型之后,我们不需要对对象进行类型转换。
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.print("Sorted Integer array : ");
bubbleSort(a);
System.out.print("Sorted Character array : ");
bubbleSort(c);
System.out.print("Sorted String array : ");
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;
}
}
// Output:
Sorted Integer array : [6, 23, 50, 55, 58, 76]
Sorted Character array : [a, c, d, g, t, v, x]
Sorted String array : [Ahmed, Ali, Aysu, Lale, Leman, Orkhan, Vali]
4.通用类
通用类允许您定义可与用户指定的任何类型的类一起使用的类。
句法:
class Box<T> {
private T item;
public void setItem(T item) {
this.item = item;
}
public T getItem() {
return item;
}
}
在这个例子中,T 是一个类型参数,在创建 Box 实例时可以将其替换为任何类型。
用法:
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);
1.泛型示例
- 相互递归类型变量界限
interface ConvertibleTo<T> {
T convert();
}
class ReprChange<T extends ConvertibleTo<S>,
S extends ConvertibleTo<T>> {
T t;
void set(S s) { t = s.convert(); }
S get() { return t.convert(); }
}
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 {
try (S s = src.get(); T t = tgt.get()) {
CharBuffer buf = CharBuffer.allocate(size);
int i = s.read(buf);
while (i >= 0) {
buf.flip(); // prepare buffer for writing
t.append(buf);
buf.clear(); // prepare buffer for reading
i = s.read(buf);
}
}
}
}
2.泛型中的类型参数
类型参数命名规范对于彻底学习泛型非常重要。常见的类型参数如下:
- T — 类型
- E — 元素
- K — 钥匙
- N — 数字
- V — 价值
有界类型参数
泛型还可以限制可使用的类型,称为“有界类型”。
句法:
public <T extends Number> void printDouble(T number) {
System.out.println(number.doubleValue());
}
解释:这里,<T extends Number> 表示 T 必须是 Number 的子类,因此只允许使用 Integer、Double、Float 等类型。
用法:
printDouble(5); // Works with Integer
printDouble(5.5); // Works with Double
// printDouble("Hello"); // 编译错误, String 不是 Number子类
通配符(?)
通配符用问号 ? 表示,允许在泛型中使用未知类型。通配符主要有三种类型:
- 无界通配符( <?> ):接受任何类型。
List<?> list = new ArrayList<String>();
list = new ArrayList<Integer>(); // Works with any type
2.上限通配符( <? extends Type> ):接受一个类型或任何子类型。
public void printNumbers(List<? extends Number> list) {
for (Number n : list) {
System.out.println(n);
}
}
3.下限通配符( <? super Type> ):接受一个类型或任何超类型。
public void addNumbers(List<? super Integer> list) {
list.add(10); // You can add Integer or its subclass
}
差异List、List<T>、List<Object>、List<?> 和 List<Student>。
每个版本的 List 参数都有独特的用例:
• 原始 列表:由于缺乏类型安全性,因此很少使用。
• List<T>:最灵活且类型安全,常用于泛型方法。
• List<Object>:灵活性较差,仅限于 Object 类型。
• List<?>:适用于接受任何列表类型的只读方法。
• List<Student>:针对特定类型的强类型安全性。
Java 泛型的常见面试问题
1.什么是泛型以及它们为什么有用?
泛型允许类和方法对任何指定类型进行操作,从而使代码类型安全且可重用。它们可以防止运行时类型错误并减少强制类型转换的需要。
2.什么是类型擦除?
类型擦除是在运行时删除泛型类型并由其边界或对象替换的过程。这允许向后兼容旧 Java 版本,但意味着您不能在运行时使用类型参数。
3. <?extends T>和<?super T>有什么区别?
<? extends T>允许任何 T 的子类型,并用于只读操作(不能安全地添加元素)。
<? super T>允许任何类型,只要它是 T 的超类型,并用于只写操作(可以安全地添加元素,但读取可能会给出 Object)。
4.可以将原始类型与泛型一起使用吗?
不可以,Java 泛型仅适用于引用类型。您需要使用包装类(例如,Integer 用于 int,Double 用于 double)。
5.有界类型参数如何提高类型安全性?
有界类型允许您指定类型上的约束,确保泛型仅适用于满足特定条件的类型(如扩展数字),从而减少运行时错误。
6. List<Object>和List<?>有什么区别?
List<Object>只能接受对象类型,并且不能分配特定类型的列表(如 List<String>)。
List<?>可以接受任何类型,因此更灵活,但在添加元素方面受到限制。
7.为什么我们不能在 Java 中创建泛型数组(例如, T[] array = new T[10]; )?
由于类型擦除,Java 禁止创建泛型数组,这使其不安全。相反,您可以使用 Object[] 并根据需要进行强制转换,或者使用集合。
8.仿制药中的 PECS 是什么?
PECS代表Producer Extends, Consumer Super。当你想从集合中生产项目时,使用 <? extends T>。当你想消费项目时,使用 <? super T>。
9.什么是类型推断?
类型推断是指编译器可以通过查看方法参数的类型来推断泛型类型。例如,如果我们将T传递给返回 T 的方法,那么编译器就可以找出返回类型。让我们通过调用上一个问题中的泛型方法来尝试一下:
Integer inferredInteger = returnType(1);
String inferredString = returnType("String");
我们可以看到,不需要强制类型转换,也不需要传入任何泛型类型参数。参数类型只会推断返回类型。