闽公网安备 35020302035485号
简化代码,增强可读性:可以减少类型转换的需求,简化代码,可以使代码更加清晰和易于理解。通过使用具有描述性的泛型类型参数,可以更准确地表达代码的意图,还可以避免使用原始类型或Object类型,从而提供更多的类型信息,使代码更加具有表达力
3.使用泛型作为容器对象
public class IntList {
// 堆代码 duidaima.com
private int[] arr; // 只能存储整数类型的数据
private int size;
public IntList() {
arr = new int[10];
size = 0;
}
public void add(int value) {
arr[size++] = value;
}
public int get(int index) {
return arr[index];
}
public int size() {
return size;
}
public static void main(String[] args) {
IntList list = new IntList();
list.add(1);
list.add(2);
list.add(3);
int value = list.get(1); // 需要显式进行类型转换
System.out.println(value); // 输出: 2
}
}
在上述示例中,使用了一个明确的 int 类型存储整数的列表类 IntList,但是该类只能存储整数类型的数据。如果想要存储其他类型的数据,就需要编写类似的类,导致类的复用度较低。public class ObjectList {
private Object[] arr;
private int size;
public ObjectList() {
arr = new Object[10];
size = 0;
}
public void add(Object value) {
arr[size++] = value;
}
public Object get(int index) {
return arr[index];
}
public int size() {
return size;
}
public static void main(String[] args) {
// 示例使用
ObjectList list = new ObjectList();
list.add(1);
list.add("Hello");
list.add(true);
int intValue = (int) list.get(0); // 需要显式进行类型转换
String stringValue = (String) list.get(1); // 需要显式进行类型转换
boolean boolValue = (boolean) list.get(2); // 需要显式进行类型转换
}
}
在上述示例中,使用了一个通用的列表类 ObjectList,它使用了 Object 类型作为持有对象的容器。当从列表中取出对象时,需要显式进行类型转换,而且不小心类型转换错误程序就会抛出异常,这会带来代码的冗余、安全和可读性的降低。public class GenericList<T> {
private T[] arr;
private int size;
public GenericList() {
arr = (T[]) new Object[10]; // 创建泛型数组的方式
size = 0;
}
public void add(T value) {
arr[size++] = value;
}
public T get(int index) {
return arr[index];
}
public int size() {
return size;
}
public static void main(String[] args) {
// 存储 Integer 类型的 List
GenericList<Integer> intList = new GenericList<>();
intList.add(1);
intList.add(2);
intList.add(3);
int value = intList.get(1); // 不需要进行类型转换
System.out.println(value); // 输出: 2
// 存储 String 类型的 List
GenericList<String> stringList = new GenericList<>();
stringList.add("Hello");
stringList.add("World");
String str = stringList.get(0); // 不需要进行类型转换
System.out.println(str); // 输出: Hello
}
}
在上述示例中,使用了一个通用的列表类 GenericList,通过使用泛型类型参数 T,可以在创建对象时指定具体的类型。这样就可以在存储和取出数据时,不需要进行类型转换,代码更加通用、简洁和类型安全。class Tuple<T1, T2> {
private T1 first;
private T2 second;
// 堆代码 duidaima.com
public Tuple(T1 first, T2 second) {
this.first = first;
this.second = second;
}
public T1 getFirst() {
return first;
}
public T2 getSecond() {
return second;
}
}
public class TupleExample {
public static void main(String[] args) {
Tuple<String, Integer> person = new Tuple<>("Tom", 18);
System.out.println("Name: " + person.getFirst());
System.out.println("Age: " + person.getSecond());
Tuple<String, Double> product = new Tuple<>("Apple", 2.99);
System.out.println("Product: " + product.getFirst());
System.out.println("Price: " + product.getSecond());
}
}
在上述示例中,定义了一个简单的元组类 Tuple,它有两个类型参数 T1 和 T2,以及相应的 first 和 second 字段。在 main 方法中,使用元组存储了不同类型的值,并通过调用 getFirst 和 getSecond 方法获取其中的值。public class Tuple2<T1, T2, T3> extends Tuple<T1, T2>{
private T3 t3;
public Tuple2(T1 first, T2 second, T3 t3) {
super(first, second);
this.t3 = t3;
}
}
继续扩展:public class Tuple3<T1, T2, T3, T4> extends Tuple2<T1, T2, T3> {
private T4 t4;
public Tuple3(T1 first, T2 second, T3 t3) {
super(first, second, t3);
}
}
如上所述,元组提供了一种简洁而灵活的方式来组合和操作多个值,适用于需要临时存储和传递多个相关值的场景。但需要注意的是,元组并不具备类型安全的特性,因为它允许不同类型的值的组合。// 定义一个泛型接口
interface Container<T> {
void add(T item);
T get(int index);
}
// 实现泛型接口
public class ListContainer<T> implements Container<T> {
private List<T> list;
public ListContainer() {
this.list = new ArrayList<>();
}
@Override
public void add(T item) {
list.add(item);
}
@Override
public T get(int index) {
return list.get(index);
}
public static void main(String[] args) {
// 示例使用
Container<String> container = new ListContainer<>();
container.add("Apple");
container.add("Banana");
container.add("Orange");
String fruit1 = container.get(0);
String fruit2 = container.get(1);
String fruit3 = container.get(2);
System.out.println(fruit1); // 输出: Apple
System.out.println(fruit2); // 输出: Banana
System.out.println(fruit3); // 输出: Orange
}
}
在上述示例中,我们定义了一个泛型接口 Container<T>,它包含了两个方法:add 用于添加元素,get 用于获取指定位置的元素。然后,我们通过实现泛型接口的类 ListContainer<T>,实现了具体的容器类,这里使用了 ArrayList 来存储元素。在示例使用部分,我们创建了一个 ListContainer<String> 的实例,即容器中的元素类型为 String。我们可以使用 add 方法添加元素,使用 get 方法获取指定位置的元素。public class GenericMethodExample {
// 带返回值的泛型方法
public static <T> T getFirstElement(T[] array) {
if (array != null && array.length > 0) {
return array[0];
}
return null;
}
public static void main(String[] args) {
Integer[] intArray = {1, 2, 3, 4, 5};
String[] strings = {"Hello", "World"};
System.out.println("First element in intArray: " + getFirstElement(intArray));
System.out.println("First element in strings: " + getFirstElement(strings));
}
}
可以看到通过泛型方法,让 getFirstElement() 更具备通用性,无需为每个不同的类型编写单独的获取方法。public class GenericMethodExample {
// 带返回值的泛型方法,接受变长参数列表
public static <T> List<T> createList(T... elements) {
List<T> list = new ArrayList<>();
for (T element : elements) {
list.add(element);
}
return list;
}
public static void main(String[] args) {
List<String> stringList = createList("Apple", "Banana", "Orange");
List<Integer> intList = createList(1, 2, 3, 4, 5);
System.out.println("String List: " + stringList); // 输出: String List: [Apple, Banana, Orange]
System.out.println("Integer List: " + intList); // 输出: Integer List: [1, 2, 3, 4, 5]
}
}
泛型信息的擦除public class GenericErasureExample {
public static void main(String[] args) {
// 定义一个 String 类型的集合
List<String> stringList = new ArrayList<>();
stringList.add("Hello");
stringList.add("World");
// 定义一个 Integer 类型的集合
List<Integer> intList = new ArrayList<>();
intList.add(10);
intList.add(20);
// 你无法通过反射获取泛型的类型参数,因为泛型信息会在编译时被擦除
System.out.println(stringList.getClass()); // 输出: class java.util.ArrayList
System.out.println(intList.getClass()); // 输出: class java.util.ArrayList
// 原本不同的类型,输出结果却相等
System.out.println(stringList.getClass() == intList.getClass()); // 输出: true
// 使用原始类型List,可以绕过编译器的类型检查,但会导致类型转换错误
List rawList = stringList;
rawList.add(30); // 添加了一个整数,导致类型转换错误
// 从rawList中取出元素时,会导致类型转换错误
String str = stringList.get(0); // 类型转换错误,尝试将整数转换为字符串
}
}
通过上述代码,我们演示类的泛型信息是怎么被擦除的,并且演示由于泛型信息的擦除所导致的安全和转换错误。这也是为什么在泛型中无法直接使用基本类型(如 int、boolean 等),而只能使用其包装类的原因之一。4.与原始类型的混淆:擦除泛型信息可能导致与原始类型的混淆。并且泛型无法使用基本数据类型,只能依赖自动拆箱和装箱机制
public class ArrayMaker<T> {
private Class<T> kind;
public ArrayMaker(Class<T> kind) {
this.kind = kind;
}
@SuppressWarnings("unchecked")
T[] create(int size) {
return (T[]) java.lang.reflect.Array.newInstance(kind, size);
}
public static void main(String[] args) {
ArrayMaker<String> stringMaker = new ArrayMaker<>(String.class);
String[] stringArray = stringMaker.create(10);
System.out.println(Arrays.toString(stringArray));
}
}
输出结果:[null, null, null, null, null, null, null, null, null, null]
扩展泛型类型的功能:通过泛型边界,我们可以限制泛型类型参数的范围,以扩展泛型类型的功能。
public class MyExtendsClass<T extends Number> {
public static void main(String[] args) {
MyExtendsClass<Integer> integerMyExtendsClass = new MyExtendsClass<>(); // 可以,因为 Integer 是 Number 的子类
MyExtendsClass<Double> doubleMyExtendsClass = new MyExtendsClass<>(); // 可以,因为 Double 是 Number 的子类
// MyClass<String> myStringClass = new MyClass<>(); // 编译错误,因为 String 不是 Number 的子类
}
}
在泛型方法中,extends 关键字在泛型的读取模式(Producer Extends,PE)中常用到。比如,一个方法返回的是 List<? extends Number>,你可以确定这个 List 中的元素都是 Number 或其子类,可以安全地读取为 Number,但不能向其中添加任何元素(除了 null),示例:public void doSomething(List<? extends Number> list) {
Number number = list.get(0); // 可以读取
// list.add(3); // 编译错误,不能写入
}
public void addToMyList(List<? super Number> list) {
Object o1 = new Object();
list.add(3); // 可以,因为 Integer 是 Number 的子类
list.add(3.14); // 可以,因为 Double 是 Number 的子类
// list.add("String"); // 编译错误,因为 String 不是 Number 的子类
}
在泛型方法中,super 关键字在泛型的写入模式(Consumer Super,CS)中常用到。比如,一个方法参数的类型是 List<? super Integer>,你可以向这个 List 中添加 Integer 或其子类的对象,但不能从中读取具体类型的元素(只能读取为 Object),示例:public void doSomething(List<? super Integer> list) {
list.add(3); // 类型符合,可以写入
// Integer number = list.get(0); // 编译错误,不能读取具体类型
Object o = list.get(0); // 可以读取 Object
}
熟练和灵活的运用 PECS 原则(Producer Extends, Consumer Super)我们也可以轻松实现 Collection 里面的通用类型集合的 Copy 方法,示例:public static <T> void copy(List<? super T> dest, List<? extends T> src) {
for (T t : src) {
dest.add(t);
}
}
public static void main(String[] args) {
List<Object> objectList = new ArrayList<>();
List<Integer> integerList = Arrays.asList(1, 2, 3);
copy(objectList, integerList);
System.out.println(objectList); // [1, 2, 3]
}
记住,无论是 extends 还是 super,它们都只是对编译时类型的约束,实际的运行时类型信息在类型擦除过程中已经被删除了。public static void printList(List<?> list) {
for (Object elem : list)
System.out.println(elem + " ");
System.out.println();
}
public static void main(String[] args) {
List<Integer> li = Arrays.asList(1, 2, 3, 4, 5);
List<String> ls = Arrays.asList("one", "two", "three");
printList(li);
printList(ls);
}
那么,问题来了。public static void printListObject(List<Object> list) {
for (Object obj : list)
System.out.println(obj);
}
public static void printListWildcard(List<?> list) {
for (Object obj : list)
System.out.println(obj);
}
public static void main(String[] args) {
List<String> stringList = Arrays.asList("Hello", "World");
printListWildcard(stringList); // 有效
// printListObject(stringList); // 编译错误
}
因此,当你需要编写能接受任何类型 List 的代码时,应该使用 List<?> 而不是 List<Object>