什么是泛型?
Java 泛型(generics)是 JDK5 引入的一个新特性,泛型提供了编译时类型安全监测机制,该机制允许我们在编译时检测到非法的类型数据结构。泛型的本质是参数化类型,也就是所操作的数据类型被指定为一个参数。
在 JDK5 之前,定义一个集合可以存储任何的数据类型,意味着它是一个 Object 类型的数据。这样是非常不安全的,必须明确知道存储的每个元素的数据类型,否则容易引发 ClassCastException 异常。如下代码所示:
// 编译正常
List list = new ArrayList<>();
list.add("java");
list.add(100);
list.add(true);
String str = (String) list.get(0); // 每个元素都需要类型转换
Integer integer = (Integer) list.get(1);
复制代码
而有了泛型之后,集合的定义变得有点跟数组一样了。我们知道数组就是必须要求类型一致的,但是数组的长度是固定的,而集合是会动态扩容的。这样我们在设置和获取元素的时候,就不需要考虑类型不一致和转换异常的问题了。
List<String> list = new ArrayList();
list.add("java8");
list.add("java11");
list.add(100); // 编译错误,类型不一致
String str = list.get(0); // 不需要进行类型转换
复制代码
泛型类
泛型类没有指定具体的数据类型,此时操作类型是 Object。参数只能是类类型,不能是基本数据类型。
定义语法如下:
class 类名称<泛型标识, 泛型标识, ...> {
private 泛型标识 变量名;
// ...
}
复制代码
使用语法如下:
类名<具体的数据类型> 对象名 = new 类名<具体的数据类型>();
// JDK7后,后面的<>中的具体数据类型可以省略
类名<具体的数据类型> 对象名 = new 类名<>();
复制代码
实例:
public class Generic<T> {
private T key;
public Generic(T key) {
this.key = key;
}
public T getKey() {
return key;
}
public void setKey(T key) {
this.key = key;
}
}
public class MainTest {
public static void main(String[] args) {
// 如果创建时指定了类型,则会自动转换成对应的类型
Generic<String> generic = new Generic<>("java");
String key = generic.getKey();
// 如果创建时没指定类型,则按照Object类型来操作
Generic genericObj = new Generic("object");
Object obj = genericObj.getKey();
}
}
复制代码
从泛型类派生子类
class ChildGeneric<T> extends Generic<T>
复制代码
class ChildGeneric extends Generic<String>
复制代码
泛型接口
实现类如果不是泛型类,接口要明确数据类型。实现类也是泛型类,实现类和接口的泛型类型要一致。
定义语法如下:
interface 接口名称<泛型标识, 泛型标识, ...> {
泛型标识 方法名();
// ...
}
复制代码
实例:
public interface Fruit<T> {
T getKey();
}
// 实现泛型接口的类不是泛型类,必须指明泛型类型。
public class Apple implements Fruit<String> {
@Override
public String getKey() {
return "apple";
}
}
// 实现泛型接口的类也是泛型类,泛型类型必须一致。
public class Apple<T> implement Fruit<T> {
private T key;
public Apple(T key) {
this.key = key;
}
@Override
public T getKey() {
return this.key;
}
}
复制代码
泛型方法
定义语法如下:
修饰符 <T, E, ...> 返回值类型 方法名(参数列表) {
方法体...
}
复制代码
public 与返回值中间的<T>非常重要,可以理解为声明此方法为泛型方法。
只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
<T>表明该方法将使用泛型类型 T,此时才可以在方法中使用泛型类型 T。
与泛型类的定义一样,此处 T 可以随便写为任意标识,常见的如 T、E、K、V 等用于表示泛型。
举例:
public class Product<T> {
private List<T> list = new ArrayList<>();
public void addProduct(T t) {
this.list.add(t);
}
// 该方法没有使用<T>,只是返回T类型的成员方法,不是泛型方法。
public T getFirst() {
return list.isEmpty() ? null : list.get(0);
}
// 该方法是一个泛型方法,使用<E>声明,返回E类型。
// 该类型由调用方法时指定,泛型方法的类型与类的泛型类型没有关系。
public <E> E getFirst(List<E> list) {
return list.isEmpty() ? null : list.get(0);
}
}
复制代码
类型通配符
类型通配符一般就是使用“?”代替具体的类型实参。所以,类型通配符是类型实参,而不是类型形参。
常用的通配符:
E:Element,一般用来代表集合的元素。
T:Type,一般代表 Java 类。
K:Key,键值。
V:Value,数据值。
N:Number,数值类型。
?:表示不确定的 Java 类型。
类型通配符的上限
<? extends T>,表示这个泛型中的参数必须是 T 或者 T 的子类。
使用固定上边界的通配符的泛型, 就能够接受指定类及其子类类型的数据。
public void getFirst(List<? extends Product> list) {
// 这里不能再对list.add操作,因为上限通配符不知道是啥类型
Product product = list.isEmpty() ? null : list.get(0);
System.out.println(product);
}
复制代码
类型通配符的下限
<? super T>,表示这个泛型中的参数必须是 T 或者 T 的父类。
使用固定下边界的通配符的泛型, 就能够接受指定类及其父类类型的数据。
public void getFirst(List<? super Product> list) {
// 这里可以再对list.add操作,因为下限通配符只需要保证是Product的子类即可
Object obj = list.isEmpty() ? null : list.get(0);
System.out.println(obj);
}
复制代码
类型擦除
泛型是 JDK5 才引进的概念,在这之前是没有泛型的,但是,泛型代码能很好地和之前的版本代码兼容。那是因为泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉,我们称之为类型擦除。
无限制类型擦除
// 编译前
public class Generic<T> {
public T key;
public T getKey() {
return key;
}
public void setKey(T key) {
this.key = key;
}
}
// 编译后
public class Generic {
public Object key;
public Object getKey() {
return key;
}
public void setKey(Object key) {
this.key = key;
}
}
复制代码
有限制的类型擦除
// 编译前
public class Generic<T extends Number> {
public T key;
public T getKey() {
return key;
}
public void setKey(T key) {
this.key = key;
}
}
// 编译后
public class Generic {
public Number key;
public Number getKey() {
return key;
}
public void setKey(Number key) {
this.key = key;
}
}
复制代码
擦除方法中类型定义的参数
// 编译前
public <T extends Number> T getValue(T value) {
return value;
}
// 编译后
public Number getValue(Number value) {
return value;
}
复制代码
桥接方法
// 编译前
public interface Info<T> {
T info(T value);
}
public class InfoImpl implements Info<Integer> {
@Override
public Integer info(Integer value) {
return value;
}
}
// 编译后
public interface Info {
Object info(Object var);
}
public class InfoImpl implements Info {
public Integer info(Integer var) {
return var;
}
// 桥接方法,保持接口和类的实现关系
@Override
public Object info(Object var) {
return info((Integer)var);
}
}
复制代码
测试结果
public static void main(String[] args) {
Method[] methods = InfoImpl.class.getDeclaredMethods();
for (Method method : methods) {
System.out.println(method.getName() + " === " + method.getReturnType().getSimpleName());
}
}
// 输出结果
info === Integer
info === Object
复制代码
以上就是泛型的全部介绍了,谢谢大家。
评论