泛型:字面意思可理解为广泛得、泛泛地类型,不具体指定为某一种类型;在使用上,则可以理解为类型参数,将类型当做参数传递给一个类、接口或者方法
泛型在哪里使用?
泛型可使用在类、接口以及方法。
泛型在类中使用
示例如下:
public class GenericClass<T>{
private T data;
public GenericClass(T data) {
this.data = data;
}
public T getData() {
return data;
}
}
复制代码
调用如下:
public class GenericClassTest {
public static void main(String[] args) {
GenericClass<String> genericClassStr = new GenericClass<String>("generic class test");
System.out.println(genericClassStr.getData());
GenericClass<Integer> videoClassInt = new GenericClass<Integer>(123);
System.out.println(videoClassInt.getData());
}
}
复制代码
在创建对象的时候,将数据的类型传递给泛型类,标识符“T”则替换成了传递的类型,其中new Video<T>中的"T"可以不写,声明变量中已经写了类型,java提供了良好的类型检查,这里为了方便理解。
控制台输出:
generic class test
123
复制代码
泛型在接口中使用
与泛型在类中使用相似,不做赘述,示例如下:
public interface GenericInterface<T> {
T getData();
}
复制代码
泛型在方法中使用
示例如下:
public class GenericMethod {
public <T> String returnString(T data) {
return "generic method test";
}
public <T> T returnGeneric(T data) {
return data;
}
}
复制代码
调用如下:
public class GenericMethodTest {
public static void main(String[] args) {
GenericMethod genericMethod = new GenericMethod();
Object objGeneric = genericMethod.returnGeneric(123);
System.out.println(objGeneric);
Object objStr = genericMethod.returnString("generic method test");
System.out.println(objStr);
}
}
复制代码
在调用方法的时候,将传参的实际数据类型传入泛型方法,作为泛型的真实类型。很多同学对方法签名前面出现"<T> T"表示很疑惑,实际上不难理解,其中"<T>"是泛型声明,就像是上文VideoClass<T>中的"<T>"一样,而后面的"T"则是方法返回值的类型,在decodeData方法中将返回值类型由T改成了String,编译运行依然是正常的。
控制台输出:
123
generic method test
复制代码
用法补充
泛型类与泛型方法结合
泛型方法是可以出现在泛型类中的,他们之间的泛型声明相互独立,互不影响,例如,将上文泛型方法放入泛型类中:
public class GenericClassMethod<T> {
private T data;
public GenericClassMethod(T data) {
this.data = data;
}
public T getData() {
return data;
}
public <E> E returnGeneric(E data) {
return data;
}
}
复制代码
泛型方法中泛型声明"T"变成了"E",为啥呢?其实这只是一个变量,不改变也行,只是为了区别于泛型类中的泛型,换一个泛型变量有利于代码阅读。阅读过源码的同学,经常会看到还有"K", "V" 之类的,这些也都只是一个泛型变量,习惯用法如下:
T -- 任何类型
E -- Element 或者 Exception类型
K -- Key 代表键
V -- Value 代码值
S -- Subtype 子类型
复制代码
通配符"?""
除了上述常用泛型变量,还有一个比较特殊的替换符"?",它存在的意义是什么呢?
看代码如下:Base base = sub编译成功,而 List<Base> baseList = subList编译失败。
class Base{}
class Sub extends Base{}
public class WildCardTest {
public static void main(String[] args) {
Sub sub = new Sub();
Base base = sub; // compile successfully
List<Sub> subList = new ArrayList<Sub>();
List<Base> baseList = subList; // compile failed
}
}
复制代码
显然,泛型之间是没有继承关系得,自然也无法使用多态了。那么如果真的需要泛型之间的继承关系怎么办呢,通配符"?"应运而生,它表示指定类型范围内的类型范围。
通配符的形式:
- : 无限定范围的通配符;
- : 有上限的通配符;
- : 有下线的通配符;
当涉及到通配符“?”, 那么就与具体的类型操作无关。示范如下:
List<?> wildList = new Arraylist<String>();
wildList.size(); // compile successfully
wildList.add(666); // compile failed;
复制代码
wildList.size()编译通过,而wildList.add(666)编译失败,说明java对通配符只保留了与具体类型无关的操作。
传递多个泛型变量
泛型设计者也没有死板地只允许一种类型的传入,声明多个泛型以英文逗号隔开即可:
public class GenericMulti<T, E>{} // 泛型类
public <K, V> V convertData(K dataVai, V styleVai){} // 泛型方法
复制代码
应用场景及优点
- 类中的数据类型可以通过外界传入的方式灵活使用,增加了类的扩展性,以及代码的复用;
- 泛型多用于容器类,增加了编译器类型检查,不需要每次赋值都需要强制类型转化;
- 代码清晰,利于阅读,一眼就看得出来容器里面存了什么类型的数据;
扩展之类型擦除
类型擦除:泛型只存在于编译期间,在运行期,所有的泛型都会变成Object类型,即具体的类型被擦除了。 示例如下:
public class GenericErase<T> {
private T data;
public GenericErase(T data) {
this.data = data;
}
}
public class GenericEraseTest {
public static void main(String[] args) {
GenericErase<String> genericErase = new GenericErase<>("movie");
Class clazz = genericErase.getClass();
System.out.println("genericErase class is " + clazz.getName());
Field[] fields = clazz.getDeclaredFields();
for (Field field: fields) {
System.out.println("field " + field.getName() + "`s type is " + field.getType().getName() );
}
}
}
复制代码
控制台输出如下:
genericErase class is com.peng.generic.blog.GenericErase
field data`s type is java.lang.Object
复制代码
由上可知,GenericErase在运行期为GenericErase,而data类型也又泛型T变成了Object
但这只是对类而言的,存入其中的对象依然是原有的具体类型。例如,new ArrayList("demo")在编译期,ArrayList是具有特性类型String的,但是在运行期间,String类型会被擦除,变成Object类型,但已经存入的“demo”对象依然是String类型,用多态的思想理解就是Object obj = "demo", obj依然是java.lang.String类型。
如果在代码运行期间,调用new ArrayList()这段代码,往里面存入Integer数据也是可以的,因为此时String类型被擦除,程序处于运行期间,没有编译器类型检查。那么如何实现呢?
反射:提供了在运行期操作类以及对象的功能,示例如下:
public class GenericClassTest {
public static void main(String[] args) {
List<String> listObj = new ArrayList<String>();
listObj.add("movie");
// listObj.add(666); compile error
try {
Method method = listObj.getClass().getDeclaredMethod("add", Object.class);
method.invoke(listObj, 666); // add successfully
System.out.println(listObj.toString());
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
复制代码
int类型的字段666也被加入到List<String>中了,控制台输出如下:
[movie, 666]
复制代码
总结
泛型大量出现在框架源码中,显得有些高深莫测,加之平常使用得不多,不甚了解。抽点时间深入学习了一下,也就豁然开朗了。