聊一聊Java中的泛型
# 什么是泛型,有什么作用?
Java 泛型(Generics) 是 JDK 5
中引入的一个新特性。使用泛型参数,可以增强代码的可读性以及稳定性。编译器可以对泛型参数进行检测,并且通过泛型参数可以指定传入的对象类型。比如 ArrayList<Person> persons = new ArrayList<Person>()
这行代码就指明了该 ArrayList
对象只能传入 Person
对象,如果传入其他类型的对象就会报错。
ArrayList<E> extends AbstractList<E>
并且,原生 List
返回类型是 Object
,需要手动转换类型才能使用,使用泛型后编译器自动转换。
# 泛型的使用方式有哪几种?
泛型一般有三种使用方式:泛型类、泛型接口、泛型方法
1.泛型类:
//此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型
//在实例化泛型类时,必须指定T的具体类型
public class Generic<T>{
private T key;
public Generic(T key) {
this.key = key;
}
public T getKey(){
return key;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
如何实例化泛型类:
Generic<Integer> genericInteger = new Generic<Integer>(123456);
2.泛型接口:
public interface Generator<T> {
public T method();
}
2
3
实现泛型接口,不指定类型:
class GeneratorImpl<T> implements Generator<T>{
@Override
public T method() {
return null;
}
}
2
3
4
5
6
实现泛型接口,指定类型:
class GeneratorImpl<T> implements Generator<String>{
@Override
public String method() {
return "hello";
}
}
2
3
4
5
6
3.泛型方法:
public static < E > void printArray( E[] inputArray ){
for ( E element : inputArray ){
System.out.printf( "%s ", element );
}
System.out.println();
}
2
3
4
5
6
使用:
// 创建不同类型数组:Integer, Double 和 Character
Integer[] intArray = { 1, 2, 3 };
String[] stringArray = { "Hello", "World" };
printArray( intArray );
printArray( stringArray );
2
3
4
5
注意:
public static < E > void printArray( E[] inputArray )
一般被称为静态泛型方法;在 java 中泛型只是一个占位符,必须在传递类型后才能使用。类在实例化时才能真正的传递类型参数,由于静态方法的加载先于类的实例化,也就是说类中的泛型还没有传递真正的类型参数,静态的方法的加载就已经完成了,所以静态泛型方法是没有办法使用类上声明的泛型的。只能使用自己声明的<E>
# 什么是桥方法
桥方法是继承泛型类并指定泛型类型后,IDE
会自动为我们创建构造方法调用父类的有参构造函数确保泛型多态类。
泛型类,指定了一个有参构造函数
public class Node<T> {
public T data;
public Node(T data) {
this.data = data;
}
public void setData(T data) {
System.out.println("Node.setData");
this.data = data;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
实现类,自动补充构造方法并调用父类构造方法确保实现泛型的多态性。
public class MyNode extends Node<Integer>{
//继承泛型类后自动添加的,用于保证泛型的多态性
public MyNode(Integer data) {
super(data);
}
}
2
3
4
5
6
7
# 泛型有哪些限制?
泛型不可以被实例化,泛型会在编译器擦除,所以泛型在编译器还未知,所以不可被实例化
泛型参数不可以是基本类型
不能声明泛型错误
如下图所示,泛型会在编译器被擦除,那么下面这段代码的
catch
就等于catch
两个一样的错误,出现执行矛盾。try{ }catch(Problem<String> p){ }catch(Problem<Object> p){ }
1
2
3
4不能声明两个参数一样泛型不同的方法,编译器擦除后,参数一样,所以编译失败
泛型不能声明为static。泛型只有在类创建时才知晓,而静态变量在类加载无法知晓,故无法通过编译、
# 泛型通配符
# 什么是通配符,它用于解决什么问题
通配符是解决泛型之间无法协变的问题,当使用一种类型作为泛型参数时,却无法使用他的父类或者子类进行赋值,通配符就是解决这种问题的对策。
# 上界通配符
不知道子类的具体类型,上界通配符就是用于解决那些父类引用指向子类泛型引用的场景
定义父类
/**
* 水果父类
*/
public class Fruit {
}
2
3
4
5
子类代码
/**
* 水果的子类 苹果
*/
public class Apple extends Fruit {
}
2
3
4
5
容器类代码
/**
* 容器类
* @param <T>
*/
public class Container<T> {
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
测试代码,如下所示上界通配符使得苹果类可以作为水果类的指向引用。
/**
* 泛型测试
*/
public class TestParttern {
public static void main(String[] args) {
Container<? extends Fruit> container=new Container<Apple>();
Fruit data = container.getData();
container.setData(new Apple());
//这里set报错:'setData(capture<? extends com.XX.Fruit>)' in 'com.XX' cannot be applied to '(com.XX.Apple)'
}
}
2
3
4
5
6
7
8
9
10
11
# 为什么上界通配符只能get不能set
如上代码所示,当我们用上界通配符? extends Fruit
,我们用其子类作为泛型参数,这只能保证我们get
到的都是这个子类的对象。但我们却忘了一点,当我们用子类apple
作为泛型参数时,泛型的工作机制仅仅是对这个对象加个一个编号CAP#1
,当我set
一个新的对象,编译器无法识别这个对象类型是否和编号匹配。更通俗的理解,上界通配符决定可以指向的容器,但是真正使用是并不知晓这个容器是哪个子类容器。所以无法set
。
# 下界通配符
这里使用的对象还是上述对象,只不过通配符改为下界通配符
/**
* 泛型测试
*/
public class TestParttern {
public static void main(String[] args) {
Container<? super Apple> container1=new Container<Fruit>();
}
}
2
3
4
5
6
7
8
9
# 为什么下界通配符只能set不能get(或者说get的是object)
- 下界通配符决定泛型的类型上限,所有水果类的父亲都可以作为指向的引用
- get时无法知晓其具体为哪个父亲,所以取出来的类型只能是object
Container<? super Apple> container1=new Container<Fruit>();
Object data = container1.getData();
2
# 如何获取泛型类型
public class GenericType<T> {
private T data;
public T getData() {
return data;
}
public void setData(T data) {
this.data = data;
}
public static void main(String[] args) {
//注意这个类要使用子类,笔者为了方便期间使用了 {}
GenericType<String> genericType = new GenericType<String>() {};
Type superclass = genericType.getClass().getGenericSuperclass();
//getActualTypeArguments 返回确切的泛型参数, 如Map<String, Integer>返回[String, Integer]
Type type = ((ParameterizedType) superclass).getActualTypeArguments()[0];
System.out.println(type);//class java.lang.String
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20