首页 > 经验记录 > 谈谈如何优雅的写代码 (框架、工具类、SDK骚操作指南)——灵性的泛型

谈谈如何优雅的写代码 (框架、工具类、SDK骚操作指南)——灵性的泛型

 

相信泛型做 Java 开发的都不陌生,也是天天接触的玩意了。不过真正自己写代码玩泛型玩的比较溜的我看还是比较少的。
基础应用、泛型是什么 这些东西就不说了。J2EE的东西到处都有,而且在职的 Java 开发看这种基础肯定没什么意思。
这篇主要就说一些泛型相关的骚操作。把泛型,给他玩的灵性起来。

 
 


 

  • 传入 A 对象返回指定的 B 对象如何做到?

实现了这个操作的效果是很感人的,看起来非常魔法,具体是个什么效果? 看下面代码:

public class MainApp {
    public static void main(String[] args) {
        BClassOne b1 = Executor.execute(new AClassOne());
        BClassTwo b2 = Executor.execute(new AClassTwo());
    }
}

 
随便想想就知道,在请求/响应模型的设计中。比如接口请求、第三方API请求等场景用起来的感受。
你封装一个指定的Request,  比如OrderCreateRequest, 中间执行者请求一下,马上一个 OrderCreateResponse 就返回回来了。直接拿起这个对象就能操作下一步业务。 美妙。
那么具体如何实现这样的效果? 感觉有意思的可以先自己写一写,我下面直接贴代码、讲设计:
 
首先是 AClass 和 BClass 这两个基类的设计, 可以将其看为Request/Response :

public class BClass {
}
public abstract class AClass<B extends BClass> {
    public abstract Class<B> getBType();
}

 
可以看到, B平平无奇。 而 A 有一个泛型。 这个泛型限定的就是B及B的子类
这个A基类就是最重要的设计,所有的奥妙全在这里。并且A 是一个抽象类, 提供了一个 getBType 的方法,  返回值就是泛型的class。
这里是专门为继承做的设计, 如果是在真实业务中,一般这些传输对象都会有几个固定的字段: 比如用户ID/商户ID 之类的。
如果是Request/Response 都有的字段,还会抽出一个BaseModel出来, 两边都继承自它。
 
两方基类已经定义好,那么接下来就是两方的具体实现。
B的实现就不贴了,因为我也没在里面写东西,这里主要看A的实现。

public class AClassOne extends AClass<BClassOne>{
    @Override
    public Class<BClassOne> getBType() {
        return BClassOne.class;
    }
}

这里 AClassOne 继承了AClass 后, 指定的泛型为 BClassOne。
并且实现了基类的抽象方法, 返回了BClassOne 的 Class 对象.
那么现在如果有一个 AClassOne 对象,  我们就可以直接通过其 getBType() 方法获取到BClassOne.class。
 
好, 现在A实现已经有了,与其对应的B实现也有了。就需要一个中间转化层。
这个转化层一般来说就是做一些Http请求啊、RPC啊之类的。获取到了返回值后封装为你需要的对象返回给你的调用者程序就可以了。
重要是这个方法是怎么定义的?  没看代码的话是有点抽象的, 但实际上看了、写了就明白了。是个很简单的东西:

public class Executor {
    public static <B extends BClass> B execute(AClass<B> aClass) {
        Class<B> bType = aClass.getBType();
        B result = null;
        try {
            result = bType.newInstance();
        } catch (ReflectiveOperationException e) {
            e.printStackTrace();
        }
        return result;
    }
}

该方法定义的泛型为: <B extends BClass>    返回的是B实体对象。入参接收一个 AClass<B>
如何获取这个B的class? 从 aClass.getBType() 中获取。
为什么要这么获取? 那是因为java的泛型是会被擦除掉的。你如果不这么定义一个方法,程序跑起来不知道你传进来的是个什么鬼东西。
上面加粗标红的三处关键字, 就是整个设计的精髓。 简单、有效、解耦彻底、拓展性高。
 


 

  • 泛型递归的用法

泛型递归? 有人看到这可能就感觉很诡异。 这又是个什么玩意,听都没听过。
来我贴一个实现, 自己感受一下,这玩意也挺抽象,我来细细讲一下。

public interface GenericRecursive<E extends GenericRecursive<E>> {
    <E> E process(E e);
}

你看到的这个类: GenericRecursive 泛型定义为E ,E的约束条件是必须为 GenericRecursive 实现类,如果只是这样还好。
但是偏偏后边还跟了一个<E> 。GenericRecursive<E>  这个E是什么呀?
那不就是<E extends GenericRecursive<E>>
这不就递归起来了?什么意思?
 
其实这里这个 E 就是用于约束实现类的。
这里我写了个实现类:

public class GenericRecursiveImpl implements GenericRecursive<GenericRecursiveImpl> {
    @Override
    public GenericRecursiveImpl process(GenericRecursiveImpl genericRecursive) {
        return this;
    }
}

这个实现类,实现了 GenericRecursive 接口,泛型只能指定为自己。
即 GenericRecursiveImpl 实现 GenericRecursive 时, E 必为 GenericRecursiveImpl 。
通过继承和泛型,可以强制子类型所有泛型相关对象的都是子类型本身
 
这个泛型的逻辑:
已知:

GenericRecursive<E extends GenericRecursive<E>>

条件:

E = GenericRecursiveImpl :  泛型约束 E extends GenericRecursive 成立

所以:

因为E = GenericRecursiveImpl 得出:  GenericRecursive<GenericRecursiveImpl>
GenericRecursiveImpl implement GenericRecursive<GenericRecursiveImpl>

 
 
那他的用处呢? 可以在哪里使用到?
他的最主要作用就是强制约束,泛型递归可以让你的系统更加的严谨。
java.lang.Enum 这个类是所有枚举的基类, 看下他的源码,他就使用到了泛型递归:

public abstract class Enum<E extends Enum<E>>
        implements Comparable<E>, Serializable

其实可以从他的角度思考一下,强制约束子类的泛型是子类,有什么用?
观察他的 compareTo() 方法:

public final int compareTo(E o) {
    Enum<?> other = (Enum<?>)o;
    Enum<E> self = this;
    if (self.getClass() != other.getClass() && // optimization
        self.getDeclaringClass() != other.getDeclaringClass())
        throw new ClassCastException();
    return self.ordinal - other.ordinal;
}

为了具体说明泛型递归在枚举中的用处,我写了个演示:

enum TempEnum {
    A, B, C
}
{
    TempEnum.A.compareTo(TempEnum.B);
    TempEnum.B.compareTo(TempEnum.C);
}

随便写了个临时枚举: TempEnum
可以看到他的 compareTo 方法,传入的类型被限定死了,只能传入 TempEnum
相信从这点中小伙伴们肯定对于泛型递归的好处和用处都可以理解通透了。
 


 

结语:

泛型这玩意, 这些骚操作要是不说出来整理一下,可能还有不少人都不知道Java泛型能这么玩。
当然,其实这种操作你在平时的业务中确实不怎么可能会有机会用到。
但是,有一天你要自己写工具、SDK的时候。相信我,这篇文章里的泛型操作肯定有机会用到的。用出来后那肯定是效果拉满。
我在一些工具类、框架里边已经发现了。
不瞒你说,这篇文章里 传入 A 返回 B 这个操作,就是我从阿里的某SDK中偷学来的 ( 笑 。
 

           


CAPTCHAis initialing...
EA PLAYER &

历史记录 [ 注意:部分数据仅限于当前浏览器 ]清空

      00:00/00:00