Java:利用反射和泛型编程让你的Java代码更优雅

  1. Java代码优化的起因

    程序员在日常工作中,根据业务需求开发各种业务系统,比如电商系统,秒杀系统,即时通信,视频直播等,如果我们 Java 开发人员只是把项目代码当作 CRUD ,不思考,去做大量重复性开发,时间长了会感到厌倦,与此同时,技术能力也得不到提升,同时还产生了很多不好维护和迭代的代码。

    Java 语言为我们提供了多种高级技巧。例如,反射和泛型的特性。如果善于使用这些特性,既能提高 Java 开发人员的技术水平,也能开发出更好的项目。

  2. 反射与泛型的使用场景

    这里就以电商系统为例,来讲解一下如何应用这两个特性。

    假设我们在做一个电商系统,里面有订单和商品的实体。我们只说其中一个过程,就是用户可以下单,订单创建后保存到 MongoDB 中,那么这个过程在电商项目的代码中该如何实现呢?

    我们定义了上述图中的两个实体类。如果想把 Order 和 Product 存储到 MongoDB 中,那么你需要先把这些 POJO 转化成 MongoDB 的数据结构 Document.

    这是一个无特定结构的数据类型,非常类似于 JSONObject ,生成 Document 以后,再开发出一个 Dao 层的代码。把Document 持久化到 MongoDB 中。

    使用这样的方式来实现,功能逻辑是行得通的。然而我们遇到了两个问题,这里以 Order 订单类举例来说,

    第一个问题是,我们需要为每个 POJO 开发一个将它转换为 Document 的代码逻辑,即: order2Doc 方法。 如果以后 POJO 中的字段发生了修改。比如新增了一个字段,必须要去修改对应的 order2Doc 代码。如果忘记修改了怎么办呢?如果 POJO 比较多,如果修改错了呢?

    当 POJO 比较多时,人为的编码错误概率就会大大增加,最后你会发现,所有的 POJO 转换为 Document 的代码逻辑做的都是一件事,那就是把 POJO 里面的各个字段put 到 Document 里面。

    遇到的第二个问题是,Dao 实现的都是增删改查的逻辑,不同的 POJO, 对应的 Dao 有大量的重复的代码。

    例如,我们需要开发一个 OrderDao,再开发一个 ProductDao ,可能还有其他的 Dao 。其实 它们的代码相似度非常高。那么我们有没有办法改进我们的项目代码呢?

    答案是肯定的。

    其方式就是使用 Java 中的高级特性,反射和泛型。

反射优化第一个问题

这里先简单说明一下什么是反射?

Java 的反射机制提供了一套反射 API ,它非常强大和有用。使用它你可以做到程序运行期间在不知道有哪些类、接口、方法、成员变量的情况下查看或者修改这些类、接口、方法、成员变量的属性。你可以用反射 API 来实例化新的对象,调用特定方法或者设置和获取字段的值。

例如,调用某个对象的 getClass() 方法,能够获得这个对象对应 Java 类的基本信息,比如有哪些方法,哪些字段等。

这里仍然使用前面提到的订单类 Order 来举例,我们使用反射 API 来看一下 Order 这个类中都有哪些成员变量?

       

它调用反射中的 getDeclaredFields() 方法来获取到 order 对象所有成员变量的数据结构,是一个 Field 对象的列表。经过 getFieldNames() 方法处理后,获取到 order 对象的所有成员变量的名称。

前面说的实现 POJO 转换成 Document 的逻辑,使用反射来做,我们只需要实现一个同一的 pojo2Doc 方法就可以了。这样一来,所有的 POJO 都可以使用它。

以上部分是将 POJO 转为 Document 的代码,能够满足 POJO 序列化到 MongoDB 的需求。除此之外,还需要一个 doc2pojo 的方法,以此来满足 MongoDB 中的 Document 反序列化到 POJO 实体类的需求。

           

我们结合上图中的代码来看,这里要注意对比一下,现在的代码和之前的代码,相对要简单了许多,而且也不会那么容易错写或漏写代码了。

总结以上的方案的优势:

第一个好处就是 POJO 转换成 Document 的代码,不需要重复写很多遍了;

第二个好处是可以避免开发中,手动实现不同 POJO 的 pojo2Doc 方法出错频率较高的情况。

Java 代码中的相关类:

在开发程序过程中常用到的 Java 中的 JSON  解析库 Jackson 以及单元测试工具 Junit 。其实它们的核心代码都是利用了 Java 的反射机制实现了它们的核心功能。

思考一下几个问题:

  1. Jackson 是如何把一个 POJO 序列化成 Json 字符串的?
  2. JUnit 是如何找到那些加了 @Test 、@After、@Before 注解的方法,并自动编排这些方法,完成了整个单元测试流程的?

使用泛型优化第二个问题

各种 Dao 实现都是增删改查的逻辑,不同的 POJO 对应的 Dao 有大量的重复代码,实现起来很麻烦。这里提供的解决方案就是使用 Java 的泛型。

简单介绍下 Java 中的泛型

泛型是 Java 中一个非常重要的知识点,在 Java 集合类的框架中,泛型被广泛地应用。比如: List<String>、Map<String,Object>、Set<String>等等。这些支持泛型的集合允许我们创建不同类型的集合容器来完成计算,是非常方便的。

那么我们如何创建属于我们的泛型工具呢?

这里我们通过一个简单的例子来举例说明并实现:

public class Box{
    private String object;
    public void set(String object){ this.object = object;}
    public String get() {	return object;}
}

这里我们首先定义一个简单的 Box 类,这是一个最常见的做法。这样的做法是有坏处的。这样创建的 Box 类,现在只能装入 String 类型的元素。如果我们需要装入 Integer 等其他类型的元素,必须要重新写另一个 Box 。

使用泛型可以很好的解决这个问题。

public class Box<T>{
    private T t;
    public void set(T t){ this.t = t;}
    public T get(){ return t; }
}

在上述代码中,我重新定义了一个 Box 类。这样我们的 Box 类便可以得到复用。可以将泛型符号 T  替换成任何我们想要的类型。

版本差异

	Box<Integer> integerBox  = new Box<>();
	Box<Double> doubleBox = new Box<>();
	Box<String> doubleBox = new Box<>();

要注意, Java 1.7 以后,编译器利用 type inference 特性自动推测类型。所以在 new Box<>() 中就不需要指定类型了。

小结:

由前面的代码实例,我们可以得到结论,使用了泛型以后,代码的复用性得到了比较大的提升,再也不需要为了支持不同类型而把相同的逻辑实现很多次了。

那么对于我们的 Dao 层持久化的需求,要想实现 Document 持久化到 MongoDB 的功能,而且还要支持泛型,这里定义一个这样的基础类。这个 BaseMongoDao 实现了 5 个 MongoDB 常见的基础方法: insertOne、insertMany、find 、findOne、updateByFilter 。这里需要注意一下这个类的定义方式。它是支持在 MongoDB 中存取任意 POJO 的,使用起来非常方便,我们不需要再像之前那样,为每一个 POJO 配备一个 POJO 与 Document 之间的转换方法,也不需要再重写一个 MongoDao, 更不需要在整个项目中维护大量类似的重复代码。

由于 BaseMongoDao 是一个抽象类,不能直接实例化,因此使用时需要继承他,然后定义自己的持久化层 Dao 。

public class OrderDao extends BaseMongoDao<Order>{
    public OrderDao(MongodbConnection mongodbConnection, String collection){
        super(mongodbConnection, collection, Order.class)
    }
}

这里以实现 Order 订单类的 Dao 举例,其增删改查的代码如下:

	final Order orderInserted = ...;
	
	MongodbConnection conn = new MongodbConnection(...);
	final OrderDao orderDao = new OrderDao(conn, "order_coll");
	
	// 插入 Order
	orderDao.insertOne(orderInserted);
	
	// 查询Order
	Bson query = eq("uuid", order.getUuid());
	final Order orderFound = orderDao.finadOne(query);
	
	//由以上的代码逻辑可以知道, orderInserted 就是 orderFound

在这一步,我们结合前面的代码,可以看出,在使用了泛型后,重复的代码大大减少了。

总结:

我们不需要再像之前那样为每个POJO配备一个POJO与Document 之间的转化方法,再重写一个 Mongo 的 Dao 层增删改查代码,也不需要在整个项目中维护大量的类似的重复代码。		

BaseMongoDao 都帮我们完成了,我们唯一需要做的就是继承 BaseMongoDao ,实现一个OrderDao ,几乎不需要额外的代码。

合理的使用泛型特性,我们可以大大提高项目的开发效率,降低代码的维护成本。这样做的第一个好处是,统一实现的泛型模板类,可以兼容多种类型的 POJO ;第二个好处是 代码逻辑得到了更好的抽象,重复代码减少,项目迭代速度加快;第三个好处,项目中的专家来实现泛型模板类,小白可以拿来即用,人为编码错误减少。
posted @ 2022-02-28 23:37  维宇空灵  阅读(400)  评论(0编辑  收藏  举报