一、为何要有泛型?
1.一个抛出异常的示例
List list = new ArrayList();
list.add("abc");
list.add(123);
for(Object o : list) {
System.out.print((String) o);
}
在本例中,编译阶段并不会报错,但到了运行时则抛出Integer cannot be cast to String的错误。
一个没有指定泛型的List,可以存放任何类型,即Object。对于String类类型来说,它是继承自Object的,而Integer的直接父类是Number,因此会有Integer无法转String的错误。
2.规避类型转换异常
既然遇到了强转的异常,那么尝试解决异常:
List list = new ArrayList();
list.add("abc");
list.add(123);
for(Object o : list) {
if (o instanceof String) {
System.out.print((String) o);
} else {
System.out.print(o);
}
}
上例使用了类型检查的机制规避了类型转换异常,但任然避免不了存入错误类型数据的情况(需要String类数据却存放了Integer类数据)。
为了避免这样可能的错误,引入泛型,使编译器在编译阶段就提示不匹配的类类型。
3.应用泛型
将上例改写:
List<String> list = new ArrayList<>(); //限定类类型
list.add("abc");
list.add(123); //编译错误
为List指定String类型,那么在list.add(123)的编译阶段就已提示错误。
同时,泛型只在编译阶段有效。
二、泛型的三类用法
泛型有三种使用方式:泛型类、泛型接口、泛型方法。
1.泛型类
public class Generic<T> {
private T key;
public void set(T key) {
this.key = key;
}
public T get() {
return this.key;
}
}
上例中的T
,即是泛型标识,字符不限定,常见的如T、E、K、V、R等。
- E, Element
- T, Type(Java Class)
- K, Key
- V, Value
- N, Number
- ?, Uncertain Java Class
Example:
- Set 表示集合里是T类的实例
- List 表示集合里是E类的实例
- List<?> 表示集合里的对象类型不确定,未指定
- List 等同与List<?>
2.泛型接口
定义一个泛型接口:
public interface Generator<T> {
public T next();
}
实现泛型接口,泛型接口未传入泛型实参时:
class FruitGenerator<T> implements Generator<T> {
@Override
public T next() {
return null;
}
}
在实现未传入泛型实参的泛型接口的实现类定义中,实现类要声明泛型,否则会编译错误。
实现泛型接口,泛型接口传入泛型实参时:
class FruitGenerator implements Generator<String> {
@Override
public String next() {
return null;
}
}
在实现传入泛型实参的泛型接口的实现类定义中,实现类即使不声明泛型,也不会编译错误。
3.泛型方法
泛型类是在实例化类的时候指明泛型的具体类型,那么泛型方法就是在调用方法时指明泛型的具体类型。
public <T> T genericMethod(Class<T> clazz) throws Exception{
T instance = clazz.newInstance();
return instance;
}
<T>
,表示这是一个泛型方法;T
,方法的返回类型(泛型);Class<T> clazz
,传入的泛型实参。
调用示例:
Object obj = genericMethod(Class.forName("com.test.test"));
多个泛型:
public <T,K> K get() {}
三、泛型通配符
在Java中,Integer是继承自Number的,而Generic与Generic是不存在继承关系的。
Number num = new Integer(); //父类的引用指向子类对象
Generic<Number> numG = new Generic<Integer>(); //错误用法,泛型没有多态
为表示具有逻辑上的继承关系,使用通配符:?
public void get(Generic<?> obj) {}
<? extends T>
:上界通配符<? super T>
:下界通配符
四、Class
在早期的JDK版本中,还只有Class类,没有Class。使用Class.newInstance()方法返回值为Object,需要根据实际类类型做转换。
//Class这个类中提供的newInstance()方法,其返回值为Object
class Class{
Object newInstance();
}
在后来加入泛型的JDK版本后,Class引入了泛型,其中的newInstance()
方法返回了一个更加特定的返回类型。
class Class<T>{
T newInstance();
}
实际上,Class
或Class<T>
依然是指代类类型。
例如:String.class
的类型是Class<String>
。
Java中Class<T>
的定义如下:
public final class Class<T> implements Serializable, GenericDeclaration, Type, AnnotatedElement
Java基础类型(boolean, byte, char, short, int, long, flout. double)和关键字void同样表示为Class对象。
Class没有公共构造方法。
以下示例使用Class对象来显示对象的类名:
void printClassName(Object obj) {
System.out.println(obj.getClass().getName());
//或者使用一个类字面值来获取指定类型(或void)的Class对象
//System.out.println(Object.class.getName());
}
推荐文章
学习心得,希望对你在学习道路上能信心倍增:✨分享心得,点亮信心✨两个月前端基础+半个月实践能做什么
实践成果,超过万字的详细开发过程提供参考:AI最佳实践全栈式从0到1开发个人博客系统