JDK进阶(6):泛型

泛型基础

认识泛型

  1. 集合如 ArrayList 在没有泛型的时候,add函数接收一个Object型的参数,取出的也是Object
  • 每次需要类型转换,很麻烦
  • 如果往一个String 的集合中添加了 Date对象,编译是不会报错的,但这不是我们
ArrayList list = new ArrayList();
list.add(1);
list.add("Sam");
list.add(false);
list.add(new Date());

Object o = list.get(0);
int obj = (int) o;
System.out.println(obj);

String objStr = (String) list.get(1);
System.out.println(objStr);

boolean flag = (boolean) list.get(2);
System.out.println(flag);

Date date = (Date) list.get(3);
System.out.println(date);

  1. 使用泛型:泛型是针对编译器有效,规定泛型类型是什么。如在泛型是 String的集合中添加Date对象,编译器报错
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("Sam");
// 编译不过
// arrayList.add(new Date());

泛型类

  1. 所谓泛型类(generic class)就是具有一个或多个类型参数的类
  2. 注意,基本类型不能作为泛型类型
  3. 示例:
// 一般的类
public class GeneralClazz {}

// 泛型类,参数类型为 T、K
public class GenericClazz<T> {}
public class GenericClazz<T, K> {}
  1. 泛型类实例化:把泛型的类型参数换成具体的类型。一个类可以得到最大的复用。
GenericClazz<String> clazz = new GenericClazz<String>();
GenericClazz<Integer> clazz = new GenericClazz<Integer>();

泛型方法

  1. 泛型方法,就是带有类型参数的方法,它既可以定义在泛型类中,也可以定义在普通类中
  2. 如果某个类中多个方法使用泛型,那么建议使用类级别的泛型(定义为泛型类)
private T t;

public void set(T t) {
    this.t = t;
}

public T get() {
    return t;
}

静态方法的泛型

  1. 泛型类中的静态方法不能用泛型
  • 静态方法不需要创建对象就可以使用,在示例之前
  • 泛型类却规定了对象的泛型,一定要实例化才可以使用
public class GenericityClazz<T> {
    
    // 编译报错
    public static void staticSet(T t) {}
}
  1. 静态方法使用泛型:独立于所属的类,单独存在
public class GenericityClazz<T> {
    
    // 编译报错
    public static void staticSet(T t) {}
    
    // 静态方法是独立的
    // 该方法的泛型与这个泛型类的泛型不是同一个
    public static <T> void staticSet(T t){}
}

参数化类型与原始类型的兼容性

  1. 参数化类型与原始类型的对象可以相互引用
// 参数化类型可以引用一个原始类型的对象,但是编译器会警告
ArrayList<String> c = new ArrayList();

// 原始类型可以引用一个参数化类型的对象
ArrayList c2 = new ArrayList<String>();
  1. 参数化类型不考虑类型参数的继承关系
// 编译器报错:原因在于 v 中拿到的是String,而实际上ArrayList中则为Object,其中有可能是 Date
// ArrayList<String> v = new ArrayList<Object>();

// 编译器报错
// ArrayList<Object> v2 = new ArrayList<String>();
  1. 创建数组实例时,数组的元素不能使用参数化的类型
// 编译器报错
ArrayList<Integer> v3[] = new ArrayList<Integer>[10];

泛型的通配符

通配符 ?

  1. 定义一个方法,打印出任意参数化类型的集合
// 只能打印Integer类型的
public static void printCollection(Collection<Integer> collection) {}

// 由于 参数化类型不考虑类型参数的继承关系,所以编译报错
public static void printCollection2(Collection<Object> collection) {}
  1. 使用通配符 ? ,表示任意类型
public static void printCollection3(Collection<?> collection) {
    for (Object o : collection) {
        System.out.println(o);
    }
}

// 使用guava 的 Lists 工具类
ArrayList<Integer> list = Lists.newArrayList(1, 2); // Integer类型 
ArrayList<String> list2 = Lists.newArrayList("sam", "rabby"); // String 类型
// 调用
printCollection3(list);
printCollection3(list2);
  1. 通配符方法注意问题
  • ? 通配符定义的变量主要用于引用,是不确定类型的
  • 可以调用与参数化无关的方法,不能调用与参数化有关的方法
public static void printCollection3(Collection<?> collection) {
    // 编译报错:? 是不确定类型,如果collection参数类型是String,add()一个Integer就有问题
    collection.add(1);
    
    // 正常运行
    collection.size();
}

通配符的边界:对?进行限制,限定通配符总是包括自己

  1. 限定通配符的上边界,? extends Number,意思为NumberNumber的子类
ArrayList<? extends Number> = new ArrayList<Integer>()
  1. 限定通配符的下边界,? super Integer,意思为IntegerInteger的父类
ArrayList<? super Integer> = new ArrayList<Number>()
  1. 通配符的方法可以改造
public static void printCollection3(Collection<? extends Number> collection) {
    for (Object o : collection) {
        System.out.println(o);
    }
}

// Integer类型
ArrayList<Integer> list = Lists.newArrayList(1, 2);  
printCollection3(list);

// String 类型,编译不过
ArrayList<String> list2 = Lists.newArrayList("sam", "rabby"); 
printCollection3(list2);

PECS 原则:Producer Extends, Consumer Super

  1. Producer Extends:如果你需要一个只读 List,用它来 produce T,那么使用 ? extends T
  2. Consumer Super:如果你需要一个只写 List,用它来 consume T,那么使用 ? super T
class Reader<T> {
    T readExact(List<T> list) {
        return list.get(0);
    }

    /**
     * 接受的参数只要是满足Fruit的子类就行(包括Fruit自身),这样子类和父类之间的关系也就关联上了
     * “Producer Extends” – 如果你需要一个只读List,用它来produce T,那么使用? extends T。
     *
     * @param list
     * @return
     */
    T readCovariant(List<? extends T> list) {
        return list.get(0);
    }

    /**
     * “Consumer Super” – 如果你需要一个只写List,用它来consume T,那么使用? super T。
     *
     * @param list
     * @param item
     */
    void writeWithWildcard(List<? super T> list, T item) {
        list.add(item);
    }
}
  1. JDK 源码中PECS原则使用: Collections
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
    for (int i = 0; i < src.size(); i++) {
        dest.set(i, src.get(i));
    }
}

泛型擦除(泛型的去类型化)

  1. 泛型是提供给编译器使用的, 但是编译器编译以后会去掉类型信息,使程序运行不受影响
  2. 对于各个参数化的泛型类型,getClass()方法的返回值和原始类型完全一样
// 两个不同泛型的集合,但是字节码是一样的
ArrayList<String> list = new ArrayList<String>();
ArrayList<Integer> list1 = new ArrayList<Integer>();
//true 说明是同一份字节码
System.out.println(list.getClass() == list1.getClass());
  1. 由于编译器生成的字节码会去掉泛型的类型信息,只要能跳过编译器,就可以往某个泛型集合中加入其它类型的数据,如用反射得到集合,再调用其add方法
// 两个不同泛型的集合,但是字节码是一样的
ArrayList<String> list = new ArrayList<String>();
ArrayList<Integer> list1 = new ArrayList<Integer>();

// 往泛型为Integer的集合中放入String的值,编译肯定不过,但是可以利用反射跳过编译器,实现
// list1.add("ababab");

// 利用反射实现
list1.getClass().getMethod("add", Object.class).invoke(list1, "现在就可以传字符串了");
// 本来是 Integer 的类型,但是现在却成功加入了 String
System.out.println(list1);

// 获取的时候,编译器会返回 Integer,实际应该是字符串了,编译正确,运行会报错
Integer a = list1.get(0);
System.out.println(list1.get(0));

输出:
[现在就可以传字符串了]
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer

泛型反射

  1. 获取泛型的方法:ParameterizedType.getActualTypeArguments()
  2. 示例
// getGenericSuperclass() 返回表示此 Class 所表示的实体(类、接口、基本类型或 void)的直接超类的 Type。
Type genericSuperclass = clazz.getGenericSuperclass();
print(genericSuperclass instanceof ParameterizedType);
ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;

// 获取泛型的类型
Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();
Type actualTypeArgument = actualTypeArguments[0];
System.out.println(actualTypeArgument.getTypeName());

// 返回的是当前这个 ParameterizedType 的类型
Type rawType = parameterizedType.getRawType();

// Type getOwnerType()返回 Type 对象,表示此类型是其成员之一的类型。例如,如果此类型为 O<T>.I<S>,则返回 O<T> 的表示形式。
// 如果此类型为顶层类型,则返回 null。
Type ownerType = parameterizedType.getOwnerType();
  1. 通用的泛型获取方法
package com.learning.optimize.jdk.genericity;


import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;

/**
 * ClassName: GenericsUtils
 * Description: 泛型工具类
 * Date: 2018/7/18 14:19 【需求编号】
 *
 * @author Sam Sho
 * @version V1.0.0
 */
public class GenericsUtils {
    /**
     * 通过反射,获得指定类的父类的泛型参数的实际类型. 如BuyerServiceBean extends DaoSupport<Buyer>
     *
     * @param clazz clazz 需要反射的类,该类必须继承范型父类
     * @param index 泛型参数所在索引,从0开始.
     * @return 范型参数的实际类型, 如果没有实现ParameterizedType接口,即不支持泛型,所以直接返回<code>Object.class</code>
     */
    @SuppressWarnings("unchecked")
    public static Class getSuperClassGenericType(Class clazz, int index) {
        //得到泛型父类
        Type genType = clazz.getGenericSuperclass();
        //如果没有实现ParameterizedType接口,即不支持泛型,直接返回Object.class   
        if (!(genType instanceof ParameterizedType)) {
            return Object.class;
        }
        //返回表示此类型实际类型参数的Type对象的数组,数组里放的都是对应类型的Class, 如BuyerServiceBean extends DaoSupport<Buyer,Contact>就返回Buyer和Contact类型   
        Type[] params = ((ParameterizedType) genType).getActualTypeArguments();
        if (index >= params.length || index < 0) {
            throw new RuntimeException("你输入的索引" + (index < 0 ? "不能小于0" : "超出了参数的总数"));
        }
        if (!(params[index] instanceof Class)) {
            return Object.class;
        }
        return (Class) params[index];
    }

    /**
     * 通过反射,获得指定类的父类的第一个泛型参数的实际类型. 如BuyerServiceBean extends DaoSupport<Buyer>
     *
     * @param clazz clazz 需要反射的类,该类必须继承泛型父类
     * @return 泛型参数的实际类型, 如果没有实现ParameterizedType接口,即不支持泛型,所以直接返回<code>Object.class</code>
     */
    @SuppressWarnings("unchecked")
    public static Class getSuperClassGenericType(Class clazz) {
        return getSuperClassGenericType(clazz, 0);
    }

    /**
     * 通过反射,获得方法返回值泛型参数的实际类型. 如: public Map<String, Buyer> getNames(){}
     *
     * @param method 方法
     * @param index  泛型参数所在索引,从0开始.
     * @return 泛型参数的实际类型, 如果没有实现ParameterizedType接口,即不支持泛型,所以直接返回<code>Object.class</code>
     */
    @SuppressWarnings("unchecked")
    public static Class getMethodGenericReturnType(Method method, int index) {
        Type returnType = method.getGenericReturnType();
        if (returnType instanceof ParameterizedType) {
            ParameterizedType type = (ParameterizedType) returnType;
            Type[] typeArguments = type.getActualTypeArguments();
            if (index >= typeArguments.length || index < 0) {
                throw new RuntimeException("你输入的索引" + (index < 0 ? "不能小于0" : "超出了参数的总数"));
            }
            return (Class) typeArguments[index];
        }
        return Object.class;
    }

    /**
     * 通过反射,获得方法返回值第一个泛型参数的实际类型. 如: public Map<String, Buyer> getNames(){}
     *
     * @param method 方法
     * @return 泛型参数的实际类型, 如果没有实现ParameterizedType接口,即不支持泛型,所以直接返回<code>Object.class</code>
     */
    @SuppressWarnings("unchecked")
    public static Class getMethodGenericReturnType(Method method) {
        return getMethodGenericReturnType(method, 0);
    }

    /**
     * 通过反射,获得方法输入参数第index个输入参数的所有泛型参数的实际类型. 如: public void add(Map<String, Buyer> maps, List<String> names){}
     *
     * @param method 方法
     * @param index  第几个输入参数
     * @return 输入参数的泛型参数的实际类型集合, 如果没有实现ParameterizedType接口,即不支持泛型,所以直接返回空集合
     */
    @SuppressWarnings("unchecked")
    public static List<Class> getMethodGenericParameterTypes(Method method, int index) {
        List<Class> results = new ArrayList<Class>();
        Type[] genericParameterTypes = method.getGenericParameterTypes();
        if (index >= genericParameterTypes.length || index < 0) {
            throw new RuntimeException("你输入的索引" + (index < 0 ? "不能小于0" : "超出了参数的总数"));
        }
        Type genericParameterType = genericParameterTypes[index];
        if (genericParameterType instanceof ParameterizedType) {
            ParameterizedType aType = (ParameterizedType) genericParameterType;
            Type[] parameterArgTypes = aType.getActualTypeArguments();
            for (Type parameterArgType : parameterArgTypes) {
                Class parameterArgClass = (Class) parameterArgType;
                results.add(parameterArgClass);
            }
            return results;
        }
        return results;
    }

    /**
     * 通过反射,获得方法输入参数第一个输入参数的所有泛型参数的实际类型. 如: public void add(Map<String, Buyer> maps, List<String> names){}
     *
     * @param method 方法
     * @return 输入参数的泛型参数的实际类型集合, 如果没有实现ParameterizedType接口,即不支持泛型,所以直接返回空集合
     */
    @SuppressWarnings("unchecked")
    public static List<Class> getMethodGenericParameterTypes(Method method) {
        return getMethodGenericParameterTypes(method, 0);
    }

    /**
     * 通过反射,获得Field泛型参数的实际类型. 如: public Map<String, Buyer> names;
     *
     * @param field 字段
     * @param index 泛型参数所在索引,从0开始.
     * @return 泛型参数的实际类型, 如果没有实现ParameterizedType接口,即不支持泛型,所以直接返回<code>Object.class</code>
     */
    @SuppressWarnings("unchecked")
    public static Class getFieldGenericType(Field field, int index) {
        Type genericFieldType = field.getGenericType();

        if (genericFieldType instanceof ParameterizedType) {
            ParameterizedType aType = (ParameterizedType) genericFieldType;
            Type[] fieldArgTypes = aType.getActualTypeArguments();
            if (index >= fieldArgTypes.length || index < 0) {
                throw new RuntimeException("你输入的索引" + (index < 0 ? "不能小于0" : "超出了参数的总数"));
            }
            return (Class) fieldArgTypes[index];
        }
        return Object.class;
    }

    /**
     * 通过反射,获得Field泛型参数的实际类型. 如: public Map<String, Buyer> names;
     *
     * @param field 字段
     * @return 泛型参数的实际类型, 如果没有实现ParameterizedType接口,即不支持泛型,所以直接返回<code>Object.class</code>
     */
    @SuppressWarnings("unchecked")
    public static Class getFieldGenericType(Field field) {
        return getFieldGenericType(field, 0);
    }
}

参考

  1. 源码地址:Java 进阶
  2. java进阶(五):Java泛型
  3. Java 泛型详解
  4. Java泛型详解

Fork me on Gitee

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值