简化代码,增强可读性:可以减少类型转换的需求,简化代码,可以使代码更加清晰和易于理解。通过使用具有描述性的泛型类型参数,可以更准确地表达代码的意图,还可以避免使用原始类型或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>