聊聊泛型
前言
泛型是一种参数化类型的概念,它允许我们在定义类、接口和方法时使用类型参数。这样一来,我们可以在使用这些类、接口和方法时指定具体的类型,以增加代码的灵活性和重用性
- 方便:可以提高代码的复用性。以List接口为例,我们可以将String、Integer等类型放入List中,如不用泛型,存放String类型要写一个List接口,存放Integer要写另外一个List接口,泛型可以很好的解决这个问题
- 安全:在泛型出之前,通过Object实现的类型转换需要在运行时检查,如果类型转换出错,程序直接GG,可能会带来毁灭性打击。而泛型的作用就是在编译时做类型检查,这无疑增加程序的安全性
正文
需要先了解下,关于泛型的一些知识:
1. 泛型中K T V E ? ( 这不是固定的, 你要想是J,X,Y都行,但别人得知道这是什么意思)
- E – Element (在集合中使用,因为集合中存放的是元素)
- T – Type(Java 类)
- K – Key(键)Map
- V – Value(值)Map
- N – Number(数值类型)
- ? – 表示不确定的java类型(无限制通配符类型)
2. 通配符
- < ? extends T> 表示类型的上界,表示参数化类型的可能是T 或是 T的子类 , 例如,List < ? extends Number>表示可以接受任何类型参数为Number或其子类型的List实例
- < ? super T> 表示类型下界(Java Core中叫超类型限定),表示参数化类型是此类型的超类型(父类型),直至Object , 例如, List< ? super Integer> 表示可以接受任何类型参数为Integer或其父类型的List实例
通配符需要遵守PECS原则,即Producer Extends, Consumer Super;上界生产,下界消费, 解释一下:
< ? extends T> 表示 “某个未知类型,它是 T 的子类型”。这种通配符泛型用于限制集合中的元素类型,使其只能是 T 类型或 T 的子类型。这样做的好处是可以安全地从集合中读取元素,因为我们知道它们都是 T 类型或 T 的子类型。但是,不能向这种集合中添加元素,因为我们无法确定具体的子类型。
< ? super T> 表示 “某个未知类型,它是 T 的父类型”。这种通配符泛型用于限制集合中的元素类型,使其只能是 T 类型或 T 的父类型。这样做的好处是可以安全地向集合中添加 T 类型的元素,因为我们知道它们都是 T 类型或 T 的父类型。但是,不能安全地从这种集合中读取元素,因为我们无法确定具体的父类型。
下面是一些示例用法:
// 使用 < ? extends T> 限制集合元素类型为 T 或 T 的子类型
List<? extends Number> numbers = new ArrayList<>();
List<? extends Integer> integers = new ArrayList<>();
List<? extends Double> doubles = new ArrayList<>();
// 从集合中读取元素是安全的
Number number = numbers.get(0);
Integer integer = integers.get(0);
Double aDouble = doubles.get(0);
// 但是不能向集合中添加元素
numbers.add(10); // 编译错误
integers.add(20); // 编译错误
doubles.add(3.14); // 编译错误
// 使用 < ? super T> 限制集合元素类型为 T 或 T 的父类型
List<? super Integer> integers = new ArrayList<>();
List<? super Number> numbers = new ArrayList<>();
List<? super Object> objects = new ArrayList<>();
// 向集合中添加元素是安全的
integers.add(10);
numbers.add(3.14);
objects.add("Hello");
// 但是不能安全地从集合中读取元素
Integer integer = integers.get(0); // 编译错误
Number number = numbers.get(0); // 编译错误
Object object = objects.get(0); // 编译错误
泛型类案例:
public class Box<T> {
private T content;
public void setContent(T content) {
this.content = content;
}
public T getContent() {
return content;
}
}
使用泛型类
Box<String> stringBox = new Box<>();
stringBox.setContent("Hello");
String content = stringBox.getContent();
在这个案例中,Box 类是一个泛型类,类型参数 T 可以在实例化时指定,这里指定为 String。通过 setContent 方法设置内容,通过 getContent 方法获取内容。
泛型接口案例:
public interface List<T> {
void add(T element);
T get(int index);
}
// 实现泛型接口
public class ArrayList<T> implements List<T> {
private T[] elements;
public void add(T element) {
// 添加元素的逻辑
}
public T get(int index) {
// 获取元素的逻辑
return null;
}
}
在这个案例中,List 接口是一个泛型接口,类型参数 T 可以在实现时指定。ArrayList 类实现了 List 接口,并指定了泛型类型参数。
泛型方法案例:
public class Utils {
public static <T> T getLastElement(T[] array) {
if (array.length > 0) {
return array[array.length - 1];
}
return null;
}
}
// 使用泛型方法
String[] stringArray = {"Hello", "World"};
String lastElement = Utils.getLastElement(stringArray);
在泛型方法中, 会看到有两种情况, 一种是方法体带 < T >, 一种不带, 那两者有什么区别呢? 简单的案例直接看明白, [参考文章]
就简单一句话: < T > T表示返回值是一个泛型,传递啥,就接收/返回啥类型的数据,根据传入的参数类型而定, 而单独的T就是表示限制你传递的参数类型, 在实例化的时候就限制了
需要注意的是, 泛型方法中, 你方法体定义的什么, 返回的就是什么, 如果你方法体是T , 那就需要返回T类型
我们常用到的泛型, 那就是集合了, 这里可以看到有几种情况 , 那以下这几种情况有什么区别呢?
List list3= new ArrayList<String>();
List<?> list= new ArrayList<String>();
List<Object> list2= new ArrayList<>();
List< ? >, List< Object >, List 之间的区别
1. List< ?> 是一个未知类型的List,而List< Object> 其实是任意类型的List。可以把List< String>, List< Integer>赋值给List< ?>,却不能把List< String>赋值给 List< Object>
2. 可以把任何带参数的类型传递给原始类型List,但却不能把List< String>赋值给List< Object>,因为会产生编译错误(不支持协变)
List<String> stringList = new ArrayList<>();
List<Integer> integerList = new ArrayList<>();
List<?> unknownList;
unknownList = stringList; // 可以将 List<String> 赋值给 List<?>
unknownList = integerList; // 可以将 List<Integer> 赋值给 List<?>
List<Object> objectList;
objectList = stringList; // 不可以将 List<String> 赋值给 List<Object>
objectList = integerList; // 不可以将 List<Integer> 赋值给 List<Object>
泛型是通过类型擦除的方式来实现, 就是说在代码编译后, 所有的<指定类型>会被擦拭掉, 所以不指定类型, List 啥都能接, 那我想泛型为Integer的ArrayList中存放一个String类型可不可以呢, 可以, 编译语法不通过, 那咱就用反射来规避校验
public void test() throws Exception {
ArrayList<Integer> list = new ArrayList<Integer>();
Method method = list.getClass().getMethod("add", Object.class);
method.invoke(list, "Java反射机制实例");
System.out.println(list.get(0));
}