1. 泛型基本概念
泛型程序设计就是编写的代码可以对多种类型的对象进行重用,比如:java中排序类可以对String、Integer等多种类型的对象给出排序结果。在java5之前java是没有泛型的,当初为了实现多种对象可以重用同一个代码,就采用Object作为基础引用,例如:在java5之前,ArrayList维护了Object引用数组。
public class ArrayList{
private Object[] elementData;
public Object get(int i);
public void add(Object o);
}
但是这种方式实现有两种缺陷:1)程序在编译阶段无法对传入的对象进行类型检查;2)当取值和添加值的时候需要进行强制类型转换,如果传入不正确对象会导致异常。
为了解决这个两个问题,java5引入了类型参数来实现泛型程序设计,=单个参数类型写法如下:
<T>
多个参数类型写法如下:
<T,U>
2. 泛型类
泛型类就是有一个或者多个类型参数的类。接下将实现一个可以存放任意类型最大最小值的泛型类,代码如下:
public class Pair<T>{
private T min;
private T max;
public Pair(T min, T max){
this.min = min;
this.max = max;
}
public T getMin(){
return min;
}
public T getMax()}
return max;
}
}
下面是使用该泛型类实现String类型字符串数组最大最小值保存案例:
package generic;
public class GenericClass {
// 泛型测试
public static void main(String[] args) {
// TODO Auto-generated method stub
String[] words = {"Mary","had","H","little","lamb"};
Pair<String> minmax = minmax(words);
// 输出最大最小值
System.out.println(minmax.getMax());
System.out.println(minmax.getMin());
}
// 寻找字符串数组最大最小值方法
public static Pair<String> minmax(String[] a){
if(a == null || a.length == 0) {
return null;
}
String min = a[0];
String max = a[0];
for(int i=0;i<a.length;i++) {
if(a[i].compareTo(max)>0) {
max = a[i];
}
if(a[i].compareTo(min)<0) {
min = a[i];
}
}
// 通过泛型保存最大最小值
return new Pair<>(min,max);
}
}
3. 泛型方法
- 概念:泛型方法就是在方法上加上类型参数,例如:
public class ArrayAlg{
public static <T> T getMiddle(T... t);
}
- 调用泛型方法:
方式一:
String middle = ArrayAlg.<String>getMiddle("A","B");
方式二:
String middle = ArrayAlg.getMiddle("A","B");
总结:由于在大多数情况下,方式二更为推荐,这是因为编译器会自动推断当前实际参数类型,比方式一清晰,书写方便。但值得注意的是,当传入参数类型数要大于定义类型参数数目时候,就会导致泛型类型推导混乱,引发错误,并且这种错误比较晦涩不容易察觉。例如:
String middle = ArrayAlg.getMiddle("A","B",0); // 这种情况下,就会导致
// 无法推导出泛型类型T所对应参数类型,T表示String还是Integer?
4.类型参数限定
在进行泛型程序设计的时候,有些情况我们编写代码并不是对所有类型的对象进行重用,这时只是需要特定类或者及其子类的对象进行重用,类型参数限定很好解决这个问题。
类型参数限定语法:
// 单类或接口限定
< T extends 类或接口> // 作用:只允许该类及其子类进行重用
// 多接口限定
< T extends 类或接口1 & 类或接口2 > // 作用:只允许这些类及其子类进行重用
5.类型擦除
在java中,java虚拟机是不支持泛型类型,因而泛型类型的对象只是存在编译之前,编译后泛型类型的对象都是确定类型普通类。在从编译前泛型类到编译后泛型类时间段内,我们将类型参数转成确定类型参数过程称为类型擦除。在进行类型擦除后泛型类称为原始类型。
在类型擦除过程中转成确定类型是什么?在java中针泛型类中的类型参数是否限定,分别进行规定:
1)类型参数无限定:
// 原始泛型类
public class Pair<T>{
private T min;
private T max;
public Pair(T min, T max);
public T getMin();
public T getMax();
}
// Pair<T>类的原始类型
public class Pair{
private Object min;
private Object max;
public Pair(Object min, Object max);
public Object getMin();
public Object getMax();
}
总结:从上面可以看出对应无限定类型参数的泛型类在进行类型擦除的过程就是将类型参数改为Object对象。为了证实这一点,可以通过反射来说明。
反射类:
package generic;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Scanner;
public class ReflectionDemo {
public static void main(String[] args) throws ClassNotFoundException {
// 反射
String className;
if(args.length > 0 ) {
className = args[0];
}else {
Scanner scanner = new Scanner(System.in);
System.out.println("请输入类名:");
className = scanner.next();
}
Class cl = Class.forName(className); // 根据类名找到类对象
Class sucl = cl.getSuperclass(); // 得到此类的父类的Class对象
Class[] interfaces = cl.getInterfaces(); // 获得实现接口
String modifiers = Modifier.toString(cl.getModifiers()); // 得到类修饰符号 public final
System.out.print(modifiers + " " + className);
if(sucl!=null && sucl != Object.class) {
System.out.print(" extends " + sucl.getName());
}
if(interfaces.length>0) {
System.out.print(" implements ");
for(int i=0;i<interfaces.length;i++) {
System.out.print(interfaces[i].getName()+" ");
}
}
System.out.println("\n{");
//输出构造方法
printConstructors(cl);
// 输出方法
printMethods(cl);
// 输出字段
printFields(cl);
System.out.println("}");
}
private static void printFields(Class cl) {
Field[] fields = cl.getDeclaredFields();
for(Field f:fields) {
String fname = f.getName(); // 字段的名称
String modifiers = Modifier.toString(f.getModifiers()); // 字段修饰符号
Class type = f.getType(); // 字段类型
System.out.println(modifiers+" "+type.getSimpleName()+" "+fname+";");
}
}
private static void printMethods(Class cl) {
Method[] methods = cl.getDeclaredMethods();
for(Method method : methods) {
String name = method.getName(); // 获得方法名
String modifiers = Modifier.toString(method.getModifiers()); // 方法修饰符
Class[] parameterTypes = method.getParameterTypes(); // 获得方法参数类型
Class runClassType = method.getReturnType(); // 获得方法返回参数类型
System.out.print(modifiers + " " + runClassType.getSimpleName() + " " + name + " (");
for(int i=0;i<parameterTypes.length;i++) {
if(i>0) {
System.out.print(",");
}
System.out.print(parameterTypes[i].getSimpleName());
}
System.out.println(");");
}
}
public static void printConstructors(Class cl) {
Constructor[] constructors = cl.getConstructors();
for(Constructor constructor : constructors) {
String name = constructor.getName();
String modifiers = Modifier.toString(constructor.getModifiers()); // 方法修饰符
Class[] parameterTypes = constructor.getParameterTypes(); // 获得参数类型
System.out.print(modifiers + " " + name + " (");
for(int i=0;i<parameterTypes.length;i++) {
if(i>0) {
System.out.print(",");
}
System.out.print(parameterTypes[i].getSimpleName());
}
System.out.println(");");
}
}
}
运行该反射类,我们可以得到这个泛型类在擦除后样子:
2)类型参数有限定:
举例的只有一个限定泛型类:
public class Pair<T extends Comparable> {
private T min; // T类型最大最小值
private T max;
public Pair(T min,T max) {
this.min = min;
this.max = max;
}
public T getMax() {
return max;
}
public T getMin() {
return min;
}
}
类型擦除后原始类型为:
public generic.Pair
{
public generic.Pair (Comparable,Comparable);
public Comparable getMin ();
public Comparable getMax ();
private Comparable min;
private Comparable max;
}
举例的多个限定泛型类:
public class Pair<T extends Comparable & Cloneable> {
private T min; // T类型最大最小值
private T max;
public Pair(T min,T max) {
this.min = min;
this.max = max;
}
public T getMax() {
return max;
}
public T getMin() {
return min;
}
}
类型擦除后原始类型为:
public generic.Pair
{
public generic.Pair (Comparable,Comparable);
public Comparable getMin ();
public Comparable getMax ();
private Comparable min;
private Comparable max;
}
总结:对应限定泛型在进行类型擦除的过程就是将类型参数改为第一个限定的类。
6.泛型方法类型擦除
在编写一个泛型方法调用的时候,如果擦除了该方法的返回值,编译器会自动插入强制类型转换。例如以下是一个带返回值泛型方法调用:
Pair<Employee> b = ....;
Employee buddy = b.getMax();
我们通过第5节学习我们知道,Pair泛型类中getMax()方法在原始类型中的返回值是Object,可我们实际在调用的时候是幅值类型Employee,并且没有报错,这就说明,编译器会帮我们在b.getMax()加上强制类型转换代码,例如下面:
Employee buddy = (Employee )b.getMax();
注意:只要进行类型擦除后,在获得带泛型的属性也会自动加上强制类型转换。
Employee buddy = (Employee )b.min;
7.桥接方法
当我们对继承泛型类时候,为了解决类型擦除和多态冲突的问题,在java编译的时候会自动桥接方法,来解决冲突。以下是一个泛型类子类实现:
父类泛型类:
public class Pair<T extends Comparable> {
private T min; // T类型最大最小值
private T max;
public Pair(T min,T max) {
this.min = min;
this.max = max;
}
public T getMax() {
return max;
}
public T getMin() {
return min;
}
public void setMax(T max) {
this.max = max;
}
}
子类继承父类(泛型类):
public class DataInterval extends Pair<String>{
public DataInterval(String min, String max) {
super(min, max);
}
// 重写泛型类方法
@Override
public void setMax(String max) {
super.setMax(max);
}
}
接下类对子类进行反射分析,可以得到该类擦除后字节码文件:
public DataInterval extends Pair
{
public DataInterval (String,String);
public void setMax (String);
public volatile void setMax (Comparable); // 这个就是行桥接方法
}
前面说了加桥接方法是为了解决类型擦除和多态冲突的问题的,现在考虑这样一个:普通类在继承泛型后使用多态情况:
//第一步创建子类
DataInterval dataInterval = new DataInterval();
// 用父类引用子类 (多态)
Pair<String> pair = dataInterval ;
// 第三步,父类调用子类重写方法
pair.setMax("123");
我们前面知道Pair pair对象会在运行前类型擦除,pair的Class就是Pair(类型擦除后类模板如下),pair.setMax("123");
它在运行的时候首先在声明类模板中匹配void setMax(Comparable c)
签名在实际执行对象类模板找匹配方法,如果没有就会到实例的父类找,如果没有桥接方法,这个方法还是调用父类的方法,因为在子类DataInterval
就没有public void setMax(Comparable c)
这个方法,为了能保证多态特性,就要在子类增加public void setMax(Comparable c)
方法,在方法内部调用真正重写的方法,代码如下,这个过程是由java编译器自动完成的:
public generic.Pair
{
public Pair (Comparable,Comparable);
public Comparable getMin ();
public Comparable getMax ();
public void setMax(Comparable c);
}
桥接方法,及其内部调用:
public void setMax(Comparable c){
setMax((String )c);
}
总结:普通类继承泛型类不推荐,可读性差,容易混淆