Java泛型(包括泛型+反射的应用)

Java基础 专栏收录该内容
8 篇文章 0 订阅

泛型简介

Java 泛型(generics)是 JDK 5 中引入的一个新特性,泛型提供了编译时类型安全检测机制,该机制允许程序员在编译时检测到非法的类型。

泛型的本质是参数化类型,也就是说,所操作的数据类型被指定为一个参数。在创建对象或调用方法的时候才明确具体的类型。

类型擦除

正确理解泛型概念的首要前提是理解类型擦除

更多关于类型擦除的问题,可以查看这篇文章:《Java泛型类型擦除以及类型擦除带来的问题》

什么是类型擦除

Java的泛型是伪泛型,这是因为Java在编译期间,所有的泛型信息都会被擦除。Java的泛型基本上都是在编译器这个层次上实现的,在生成的字节码中是不包含泛型中的类型信息的。

  • 使用泛型的时候加上类型参数,在编译器编译的时候参数会被去掉,这个过程称为类型擦除

如在代码中定义List<String>等类型,在编译后都会变成List,由泛型附加的类型信息对JVM是看不到的JVM看到的只是List。Java编译器会在编译时尽可能的发现可能出错的地方,但是仍然无法在运行时刻出现的类型转换异常的情况,类型擦除也是Java的泛型与C++模板机制实现方式之间的重要区别。

通配符

更多关于Java 泛型中的通配符可以查看这篇文章:《聊一聊-JAVA 泛型中的通配符 T,E,K,V,?》

常用的通配符为: T、E、K、V、?

  • ? 表示不确定的 Java 类型
  • T 表示具体的一个Java类型(Type)
  • KV 分别代表 Java 键值中的 Key Value
  • E 代表Element

泛型的三种使用方式

泛型一般有三种使用方式:泛型类泛型接口泛型方法

泛型类

此处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;
    }
}

实例化泛型类:

Generic<Integer> genericInteger = new Generic<Integer>(123456);

泛型接口

public interface Generator<T> {
    public T method();
}
  1. 实现泛型接口,不指定类型:
class GeneratorImpl<T> implements Generator<T>{
    @Override
    public T method() {
        return null;
    }
}
  1. 实现泛型接口,指定类型:
class GeneratorImpl<T> implements Generator<String>{
    @Override
    public String method() {
        return "hello";
    }
}

泛型方法

public static <E> void printArray(E[] inputArray){         
    for(E element : inputArray){        
      	System.out.printf( "%s ", element );
    }
    System.out.println();
}

使用:

// 创建不同类型数组: Integer, Double 和 Character
Integer[] intArray = { 1, 2, 3 };
String[] stringArray = { "Hello", "World" };
printArray(intArray); 
printArray(stringArray); 

泛型的优点

使用泛型的好处就是:

  1. 代码更加简洁,不再需要强制类型转换

  2. 程序更加健壮,在编译期间没有警告,在运行期就不会出现ClassCastException异常

  3. 提高代码的复用性,比如:在动态代理中,通过泛型可以代理任何需要被代理的类

public class ServiceProxy<T> implements InvocationHandler {
  	private T target;
  	public ServiceProxy(T target) {
        this.target = target;
    }
}

泛型的应用

  1. 操作集合时

容器中之所以可以存储各种类型的对象,就是因为引入了泛型

List lists = new ArrayList<String>();
  1. 用于基础组件时

因为组件的要求是需要做到一定的通用性,需要支持不同的类型,而泛型的特点是:在创建对象或调用方法的时候才明确具体的类型。可以参考SpringData JPAJpaRepository的写法:

public interface JpaRepository<T, ID> extends 
  PagingAndSortingRepository<T, ID>, QueryExampleExecutor<T> {
  	List<T> findAll();
  	List<T> findAll(Sort sort);
  	List<T> findAllById(Iterable<ID> ids);
  	<S extends T> List<S> saveAll(Iterable<S> entities);
  	void flush();
   	<S extends T> S saveAndFlush(S entity);
   	void deleteInBatch(Iterable<T> entities);
   	void deleteAllInBatch();
   	T getOne(ID id);
  
   	@Override
   	<S extends T> List<S> findAll(Example<S> example);
  
   	@Override
   	<S extends T> List<S> findAll(Example<S> example, Sort sort);
}

此外,在组件中,还离不开Java的反射机制,一般是反射+泛型

再举个实际的例子

比如有个需求是将某些数据库表的某些字段进行聚合,SQL语句就是

select sum(column1),sum(column2) from table group by field1,field2

需要sumgroup by 的列是由业务方自己传入,而SQL表其实就是我们的POJO,传入的字段肯定也是POJO的属性,单个业务实际可以在参数上将POJO写死,但是如果参数设置为泛型,便可以提高代码的复用性。

拿到参数后,通过反射获取其字段具体的值,就可以做累加了。具体代码示例如下:

// 传入需要 group by 和 sum 的字段名
public cacheMap(List<String> groupByKeys, List<String> sumValues) {
  	this.groupByKeys = groupByKeys;
  	this.sumValues = sumValues;
}

private void excute(T e) {
  
    // 从pojo 取出需要group by 的字段 list
    List<Object> key = buildPrimaryKey(e);

    // primaryMap 是存储结果的Map
    T value = primaryMap.get(key);

    // 如果从存储结果找到有相应记录
    if (value != null) {
        for (String elem : sumValues) {
          // 反射获取对应的字段,做累加处理
            Field field = getDeclaredField(elem, e);
            if (field.get(e) instanceof Integer) {
                field.set(value, (Integer) field.get(e) + (Integer) field.get(value));
            } else if (field.get(e) instanceof Long) {
                field.set(value, (Long) field.get(e) + (Long) field.get(value));
            } else {
                throw new RuntimeException("类型异常,请处理异常");
            }
      	}
        // 处理时间记录
        Field field = getDeclaredField("updated", value);
        if (null != field) {
            field.set(value, DateTimeUtils.getCurrentTime());
        }
    } else {
      	// group by 字段 第一次进来
        try {
            primaryMap.put(key, Tclone(e));
            createdMap.put(key, DateTimeUtils.getCurrentTime());
        }catch (Exception ex) {
         	 	log.info("first put value error {}" , e);
        }
    }
}

参考:https://mp.weixin.qq.com/s/fL6cMjSNAX_fZ7wFBJO80Q

  • 0
    点赞
  • 0
    评论
  • 0
    收藏
  • 打赏
    打赏
  • 扫一扫,分享海报

©️2022 CSDN 皮肤主题:大白 设计师:CSDN官方博客 返回首页

打赏作者

FerryGO

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值