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 ) ; // 无需强制转换,确保类型安全
为什么要使用泛型?
- 类型安全:泛型允许您在编译时捕获类型错误,从而减少 ClassCastException 的可能性。
// 不使用泛型,我们可以存储任何类型的对象 List list = new ArrayList(); list.add(10); list.add("10"); // 使用泛型时,需要指定需要存储的对象类型。 List<Integer> list = new ArrayList<Integer>(); list.add(10); list.add("10");// 编译时 错误
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("已排序的整数数组:");
bubbleSort(a);
System.out.print("已排序的字符数组: ");
bubbleSort(c);
System.out.print("已排序的字符串数组: ");
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 setItem(T item) {
this.item = item;
}
public T getItem() {
return item;
}
}
泛型方法
泛型方法允许您定义具有类型参数的方法,使它们能够与运行时指定的任何类型一起使用。
句法:
public class GenericsExample {
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.println(element);
}
}
}
用法:
Integer[] intArray = {1, 2, 3};1, 2, 3};
String[] strArray = {"Hello", "World"};
GenericsExample.printArray(intArray);
GenericsExample.printArray(strArray);
泛型示例
- 相互递归类型变量界限
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); } } } }
泛型中的类型参数
类型参数命名规范对于彻底学习泛型非常重要。常见的类型参数如下:- 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 ); // 适用于 Integer printDouble ( 5.5 ); // 适用于 Double // printDouble("Hello"); // 编译时错误,String 不是 Number 的子类
通配符(?)
通配符用问号 ? 表示,允许在泛型中使用未知类型。通配符主要有三种类型:
无界通配符( <?> ):接受任何类型。
List <?> list = new ArrayList < String >(); list = new ArrayList < Integer >(); // 适用于任何类型
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 ); // 您可以添加Integer 或其子类 }
每个版本的 List 参数都有独特的用例:
• 原始 列表:由于缺乏类型安全性,因此很少使用。
• List<T>:最灵活且类型安全,常用于泛型方法。
• List<Object>:灵活性较差,仅限于 Object 类型。
• List<?>:适用于接受任何列表类型的只读方法。
• List<Student>:针对特定类型的强类型安全性。Java 泛型的常见面试问题
1.什么是泛型以及它们为什么有用?
泛型允许类和方法对任何指定类型进行操作,从而使代码类型安全且可重用。它们可以防止运行时类型错误并减少强制类型转换的需要。
2.什么是类型擦除?
类型擦除是在运行时删除泛型类型并由其边界或对象替换的过程。这允许向后兼容旧 Java 版本,但意味着您不能在运行时使用类型参数。https
://www.baeldung.com/java-type-erasure3. <?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");");
我们可以看到,不需要强制类型转换,也不需要传入任何泛型类型参数。参数类型只会推断返回类型。