一、简介
在Java 中,泛型
的意思是:“适用于许许多多的类型”,其目的是让类或者方法具有最广泛的表达能力。虽然多态(基类)和接口实现的方式也能够提供较通用的能力,但是还是有一些限制,而泛型能够最大程度的让你的代码更加灵活,具备更多不同的功能。泛型
的本质是参数化类型,(在不创建新的类型的情况下,通过泛型指定的不同类型来控制形参具体限制的类型)。也就是说在泛型使用过程中,操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和方法中,分别被称为泛型类
、泛型接口
、泛型方法
。
二、使用方式
1、泛型类
//这里的T可以随便写为任意标识,如T、E、K、V等形式的参数,常用于表示泛型
public class GeneratorClass<T> {
private T key;
public GeneratorClass(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
this.key = key;
}
public T getKey(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
return key;
}
}
其中,类型参数T必须是类类型或者是自定义类,不可以是简单的类型(如int),并且传入的实参需要和泛型的类类型参数相同。如下:
GeneratorClass<String> generatorClass = new GeneratorClass("123");
GeneratorClass<Integer> generatorClass1 = new GeneratorClass<>(123);
如果传入的实参跟定义的类类型不一致,编译器会直接提示报错。
另外,如果不在初始化的时候,不指定泛型的实参类型,则起不到相应的限制作用,其中的泛型方法和成员变量的类型可以为任何类型。如下面的写法则不会报错,
同时,我们可以使用继承机制,来实现参数更多的场景,
//泛型继承的写法,这里的T,是必须要的,K参数是对原有的参数进行扩展,
public class ExtendGenerator<T, K> extends GeneratorClass{
public ExtendGenerator(T key) {
super(key);
}
}
2、泛型接口
泛型也可以用于接口,例如生成器
,这是一种专门负责创建对象的类,他实际上也是工厂模式的一种应用,但这种使用生成器来创建新对象的用法,不需要任何参数,就可以知道怎样创建对象。
public interface IGenerator<T> {
/**
* 获取数据
* @return
*/
T getData();
}
当我们不传如实参时,需要将泛型声明一起加入到类中,
public class NoClearParamsIGenerator<T> implements IGenerator<T> {
@Override
public T getData() {
return null;
}
}
如果传入了实参,则,所有使用到泛型的地方,都需要替换成传入的实参类型。如,上面接口的getData方法。
public class ClearParamsIGenerator implements IGenerator<String> {
@Override
public String getData() {
return null;
}
}
3、泛型方法
要定义泛型方法,只需要将泛型参数列表置于返回值之前,就像下面这样子:
public class GenericMethods {
public <T> void f(T x) {
Log.d("TAG", x.getClass().getName());
}
}
如果不写void前面的<T>
,那么这个方法就是普通的成员方法而已,只不过他使用的参数,我们已经声明过,所以可以直接使用。
//在泛型类中声明了一个泛型方法,使用泛型T,这个T必须和声明中的T是同一种类型。
public void show_1(T t){
Log.d(TAG, "" + t.toString());
}
而如果使用一个没有声明过的泛型类型,则编译器会提示错误。
如果想要使用自定义的E,在前面增加声明即可。这种跟第一种泛型方法其实是一样的。
//在泛型类中声明一个泛型方法,使用泛型E,这种泛型E可以为任意类型。可以类型与T相同,也可不同。
//由于泛型方法在声明的时候会声明泛型<E>,因此即使在泛型类中并未声明泛型,编译器也能够正确识别泛型方法中识别的泛型。
public <E> void show_3(E t){
Log.d(TAG, "" + t.toString());
}
如果想要在泛型类中使用静态方法,则参数声明 <T>
是必不可少的。
/**
* 如果在类中定义使用泛型的静态方法,需要添加额外的泛型声明(将这个方法定义成泛型方法)
* 即使静态方法要使用泛型类中已经声明过的泛型也不可以。
* 如:public static void show(T t){..},此时编译器会提示错误信息:
* "xxx cannot be refrenced from static context"
*/
public static <T> void show(T t){ }
泛型方法可以和可变参数列表很好共存使用,
public class GenericVarargs {
public static <T> List<T> makeList(T... args) {
List<T> list = new ArrayList<T>();
for (T item : args) {
result.add(item);
}
result list;
}
}
泛型方法的定义就以上这些。
4、泛型通配符
泛型通配符使用问号 ?
来表示,通配符只有在修饰一个变量时会用到,使用它可以方便地引用包含了多种类型的泛型。
例如,如果我们已经有了如下方法,
public void showAppoint(GeneratorClass<String> t){
Log.d(TAG, "" + t.toString());
}
想要直接调用showAppoint来展示一个Integer的变量明显是不可行的,除非我们重新创建一个带Integer类型的方法。
有没有更加通用的方式呢?如下使用通配符的写法,则能够很好满足我们的诉求,
public void showCommon(GeneratorClass<?> t){
Log.d(TAG, "" + t.toString());
}
三、重要知识点
1、泛型擦除
在泛型代码内部,无法获得任何有关泛型参数类型的信息。
public static void test() {
Class class1 = new ArrayList<String>().getClass();
Class class1 = new ArrayList<Integer>().getClass();
Log.d("tag", "result = " + (class1 == class2)) //输出 result = true
}
Java泛型是使用擦除来实现的,这就意味着当你在使用泛型时,任何聚义的类型信息都被擦除掉了,你唯一知道的就是你在使用一个对象,因此List 和List在运行时实际上是同种类型,因为这两种类型在运行时都被擦除成他们的“原生”类型List 了。擦除丢失了在泛型代码中执行某些啊哦做的能力,任何在运行时需要知道确切类型信息的操作都无法完成,
如一下instanceof
的判断直接提示异常。
但我们还有有办法绕过这些问题来编程的,例如通过引入类型标签来对擦除进行补偿
。也即是需要显式地传递你的类型的Class对象,以便你可以在类型表达式中使用它。
public class GeneratorClass<T> {
private Class<T> classType;
public GeneratorClass(Class<T> classType) { //将类型type 存储起来。
this.classType = classType;
}
public void f2(Object arg) {
if (classType.isInstance(arg)) {
Log.d("TAG", "TRUE");
} else {
Log.d("TAG", "false");
}
}
}
2、泛型边界
因为擦除移除了类型信息,所以,可以用无界泛型参数调用的方法只是那些可以使用Object调用的方法。但是如果能够将这个参数限定为某个类型的子集,那么我们就能够使用这些类型子集来调用方法,为了执行这种限制,Java泛型重用了extends、super
关键字。当然,这种方式还有另外一个作用,就是将传入的参数限定为某个类的子类或者父类。
- 上边界例子
interface HasColor {
Color getColor();
}
/**
* 单继承方式
* @param <T>
*/
class Colored<T extends HasColor> {
T item;
Colored(T item) {
this.item = item;
}
T getItem() {
return item;
}
Color color() {
return item.getColor();
}
}
class Dimension {
public int x, y, z;
}
/**
* 多继承方式
* @param <T>
*/
class ColoredDimension<T extends Dimension & HasColor> {
T item;
public T getItem() {
return item;
}
public void setItem(T item) {
this.item = item;
}
ColoredDimension(T item) {
this.item = item;
}
Color color() {
return item.getColor();
}
int getX() {
return item.x;
}
int getY() {
return item.y;
}
//可以直接调用父类的对应方法或者成员变量
int getZ() {
return item.z;
}
}
如果我们使用一个没有继承自HasColor
的实参去创建一个Colored
对象,编译器会直接报异常。
泛型边界方法的例子如下:
//在泛型方法添加上下边界限制时,须在权限声明与返回值之间的<T>上添加上下边界,即在泛型声明时添加
//public <T> T showName(GeneratorClass<T extends Number> container),会报错:"Unexpected bound"
public <T extends Number> T showName(GeneratorClass<T> container){
Log.d("TAG", " key :" + container.getKey());
T test = container.getKey();
return test;
}
- 下边界例子
用<? super 子类型>标识下边界通配符,用于表示实例化时可以确定子类型的未知类型。
public class ExtendSuperClass extends SuperClass {
}
public class SuperClass extends SuperParentClass{
private int count;
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
List<? super SuperClass> list = new ArrayList<>();
list.add(new SuperClass());
list.add(new ExtendSuperClass());
SuperClass superClass = (SuperClass) list.get(0);
int count = superClass.getCount();
如果往里面加入父类非SuperClass的对象,则会提示出错。