1.介绍:
Java泛型是一种可以在编译时进行类型检查的机制,它允许程序员编写灵活且类型安全的代码。以下是Java泛型的一些关键概念:
1.泛型类:
泛型类可以有一个或多个类型参数,这些参数在类定义中被声明,并在实例化时被指定。例如,ArrayList<T>
就是一个泛型类,其中T
是一个类型参数,代表列表中存储的元素的类型。
2.泛型接口 :
与泛型类类似,接口也可以被参数化。当实现一个泛型接口时,必须遵循接口中定义的类型参数。
3.泛型方法:
泛型方法是指在方法声明中包含类型参数的方法。这些方法独立于类的类型参数,可以属于普通类或泛型类。
4.有界类型参数:
在泛型中使用extends
关键字来限制类型参数的范围,这称为有界类型参数。例如,List<T extends Number>
表示这个列表只能存储Number及其子类型的对象。
5.通配符:
通配符?
用于表示未知类型,它可以用作泛型的占位符。通配符有两种形式:无界通配符和有界通配符,分别用于不同的情况。
6.类型擦除:
由于Java泛型是在编译时进行类型检查的,运行时并不保留类型信息,这个过程称为类型擦除。这意味着泛型类型信息在生成的字节码中不可见,因此无法在运行时通过反射获取到这些信息。
7.PECS原则:
即Producer-Extends, Consumer-Super规则,它是使用通配符时的指导原则,帮助开发者正确地使用泛型来提高代码的可读性和安全性。
8.泛型的使用场景:
泛型常用于集合类、自定义数据结构、算法的实现等场合,以提供更高的类型安全性和代码复用性。
总的来说,Java泛型是Java语言中一个强大的特性,它允许开发者编写更加灵活和健壮的代码,同时减少类型转换的需要,避免了潜在的类型转换错误
2.示例:
1.泛型类:
// 定义一个泛型类
public class Box<T> {
private T item;
public void setItem(T item) {
this.item = item;
}
public T getItem() {
return item;
}
}
// 实例化泛型类
Box<String> stringBox = new Box<>();
stringBox.setItem("Hello");
String item = stringBox.getItem(); // 类型为String
2.泛型接口 :
// 定义一个泛型接口
public interface Comparator<T> {
int compare(T o1, T o2);
}
// 实现泛型接口
public class StringComparator implements Comparator<String> {
@Override
public int compare(String o1, String o2) {
return o1.compareTo(o2);
}
}
3.泛型方法(可变参数):
public class Utility {
// 定义一个泛型方法
public static <T> void printArray(T[] array) {
for (T element : array) {
System.out.print(element + " ");
}
System.out.println();
}
public static <E> void print(E... e) { //泛型可变参数
for (int i=0; i < e.length; i++) {
System.out.println(e[i]);
}
}
}
// 调用泛型方法
Integer[] intArray = {1, 2, 3};
Utility.printArray(intArray); // 输出:1 2 3
Utility.print(1,5,"ttt"); // 输出:1 5 ttt
4.有界类型参数:
public class NumberList<T extends Number> {
private List<T> list;
public void add(T number) {
list.add(number);
}
public T get(int index) {
return list.get(index);
}
}
NumberList<Integer> integerList = new NumberList<>();
integerList.add(1); // 正确,因为Integer是Number的子类
5.通配符:
// 使用无界通配符
public static void printList(List<?> list) {
for (Object item : list) {
System.out.print(item + " ");
}
System.out.println();
}
// 使用有界通配符
public static void printNumberList(List<? extends Number> list) {
for (Number item : list) {
System.out.print(item + " ");
}
System.out.println();
}
6.PECS原则:
1.Producer-Extends(生产者-扩展):
// 定义一个泛型方法,接受一个List作为参数,并返回其中的元素
public static <T> T getFirst(List<? extends T> list) {
return list.get(0);
}
// 调用该方法
List<Integer> integerList = Arrays.asList(1, 2, 3);
Integer firstElement = getFirst(integerList); // 正确,因为Integer是Number的子类
在这个例子中,getFirst
方法接受一个List<? extends T>
类型的参数,表示这个列表可以存储任何T的子类型的对象。因此,我们可以将List<Integer>
传递给这个方法,因为Integer是Number的子类。
2.Consumer-Super(消费者-超类):
// 定义一个泛型方法,接受一个List作为参数,并将元素添加到另一个List中
public static <T> void addAll(List<? super T> destination, List<T> source) {
for (T item : source) {
destination.add(item);
}
}
// 调用该方法
List<Object> objectList = new ArrayList<>();
List<String> stringList = Arrays.asList("A", "B", "C");
addAll(objectList, stringList); // 正确,因为String是Object的子类
在这个例子中,addAll
方法接受一个List<? super T>
类型的参数,表示这个列表可以存储任何T的父类型的对象。因此,我们可以将List<String>
传递给这个方法,因为String是Object的子类。
通过遵循PECS原则,我们可以避免使用通配符时的类型转换错误,提高代码的可读性和类型安全性。
3.注意:
1.没有提供泛型时默认为Object类型:
Generic<String> generic = new Generic<> ( key: "ABC");
String key = generic. getKey ();
//泛型类在创建对象的时候,没有指定类型,将按照0bject类型来操作。
Generic generic = new Generic ( key: "ABC");
Object key = generic. getKey ();
2.java泛型不支持基础类型只支持类类型。
3.泛型类型在逻辑上可以看成是多个不同的类型,但实际上都是相同类型。
4.泛型类派生子类:
1.父类和子类都是泛型时,父类泛型要和子类泛型一样 (接口一样):
//泛型类派生子类,子类也是泛型类,那么子类的泛型标识要和父类一致。
//1
public class ChildFirst<T, E> extends Parent<T>{
public T getValue() {
return super. getValue() ;
}
}
//2
public class ChildFirst<T> extends Parent<T>{
public T getValue() {
return super. getValue() ;
}
}
//1和2都行
2.父类是泛型而子类不是泛型时,父类泛型要明确 (接口一样):
//1
public class ChildSecond extends Parent{
public Object getValue () {
return super. getValue () ;
}
}
//2
public class ChildSecond extends Parent<String>{
public String getValue () {
return super. getValue () ;
}
}