当前位置: 首页 > 后端技术 > Java

为Java造轮子——Chain

时间:2023-04-01 19:44:55 Java

在不久前发表的《模拟Java中的C#扩展方法》一文中模拟了Java的扩展方法。但毕竟没有语法支持,使用起来还是有很多不便,尤其是在不同类实现的“扩展方法”需要不断交错时,切换对象非常麻烦。前面说了,之所以想到研究“扩展方法”,其实就是为了“链式调用”。那么,为什么不直接从最初的需求出发,只解决链式调用的问题,而不考虑更广泛的扩展方式呢?在上一篇文章中,我们研究了如何通过Builder模式实现链式调用。这个方法需要自己定义扩展方法类(也就是Builder),还是比较繁琐的。Chain的原型链调用的主要特点是,在使用了一个对象之后,还可以继续使用这个对象……一直使用下去。你看,这里有两件事:一是提供一个对象;另一种是使用这个对象——这不就是供应商和消费者吗?Java在java.util.function包中恰好提供了两个同名的函数式接口。这样,我们就可以定义一个Chain类,从一个Supplier开始,不断地“消费”它,这样一个简单的Chain原型就出来了:publicclassChain{privatefinalTvalue;publicChain(Suppliersupplier){this.value=supplier.get();}publicChainconsume(Consumerconsumer){consumer.accept(this.value);归还这个;}}现在,如果我们有一个Person类,它有一些行为方法:}还是上面那个业务场景:谈妥,出去,吃饭,回来,睡觉。非链测试调用是这样的:publicstaticvoidmain(String[]args){varperson=newPerson();人.talk();person.walk("餐厅");人.吃();人。走路回家”);person.sleep();}如果与Chain链接在一起:publicstaticvoidmain(String[]args){newChain<>(Person::new).consume(Person::talk).consume(p->p.walk("restaurant")).consume(Person::eat).consume(p->p.walk("home")).consume(Person::sleep);}上面已经完成了Chain封装,就是挺简单的,但是有两个小问题:consume()字数太多,写起来麻烦。如果改个名字,do很合适,可惜是关键字……不如改成act;链式调用经常用在表达式中,需要返回值,所以还得加上Chain::getValue()来完善实际的Chain其实,在链式调用的过程中,并不一定只是“消费”,而“转换”也可能是必需的。用程序员的话来说,就是map()——将当前对象作为参数输入,计算完成后获取。另一个对象。可能你在javastream中用得更多的是map(),但是这里的场景更像是Optional::map。看一下Optional::map的源代码:publicOptionalmap(Functionmapper){Objects.requireNonNull(mapper);如果(!isPresent()){返回空();}else{returnOptional.ofNullable(mapper.apply(value));}}可以看出这个map()的逻辑很简单,就是把Function操作的结果封装成一个Optional对象。我们可以在链中做同样的事情:publicChainmap(Functionmapper){returnnewChain<>(()->mapper.apply(value));}写到这里,发现虽然使用Supplier的思路是正确的,但是直接从“值”构造一个Chain对象并不容易——当然可以加一个构造函数的重载来解决这个问题,但是我想像Optional一样写两个静态方法来实现,同时隐藏构造函数。修改后的完整Chain如下:publicstaticChainof(Tvalue){returnnewChain<>(value);}publicstaticChainfrom(Suppliersupplier){returnnewChain<>(supplier.get());}私有链(T值){这个。价值=价值;}publicTgetValue(){返回值;}publicChainact(Consumerconsumer){consumer.接受(这个。值);归还这个;}publicChainmap(Functionmapper){returnChain.of(mapper.apply(value));}}继续改造Chainmap()Total是的,会返回一个新的Chain对象。如果在某个处理中有很多map步骤,就会产生很多Chain对象。它可以在Chain对象中解决吗?定义Chain时使用了泛型,泛型在编译后会被抹掉,这和我们直接将value定义为Object没有太大区别。既然如此,在map()时直接替换值而不是生成新的Chain对象是否可行?——确实有可能。但一方面,我们需要继续使用泛型来约束消费者和映射者。另一方面,我们需要在内部进行强制的类型转换,并且必须保证这种转换不会出现问题。从理论上讲,Chain处理链调用,一个接一个链接,每个链接的结果存储在值中,用于下一个链接的开始。所以,在泛型的约束下,你怎么改都不会有问题。理论可行,不如实践一下://因为类型转换较多(逻辑确认可行),相关警告需要忽略@SuppressWarnings("unchecked")publicclassChain{//declarevalueasObject类型,以便引用各种类型的值//同时去掉最后的修饰,使其成为可变私有的Object值;publicstaticChainof(Tvalue){returnnewChain<>(value);}publicstaticChainfrom(Suppliersupplier){returnnewChain<>(supplier.get());}privateChain(Tvalue){this.value=value;}publicTgetValue(){//在使用value的地方,需要将value转换为Chain<>的泛型参数类型,同return(T)value;}publicChainact(Consumerconsumer){consumer.accept((T)this.value);归还这个;}publicChainmap(Functionmapper){//mapper的计算结果可以给Object类型,不管其类型值赋值this.value=mapper.apply((T)值);//虽然返回的Chain仍然是它本身(只是这个对象),但它是通用的类型参数必须改为U。//改变类型后,后续操作将以U类型为准return(Chain)this;}}最后一句类型转换(Chain)这个很有灵性,在Java中可以做到(因为类型擦除),但是在C#中无论如何都做不到!再写一段代码做实验:publicstaticvoidmain(String[]args){//注意:String操作会产生新的String对象,所以mapChain.of("HelloWorld").map(String::trim).map(String::toLowerCase)//↓将String拆分为String[],这里不兼容的类型被转换。map(s->s.split("\s+"))//↓消费这个String[]并一一打印出来。act(ss->Arrays.stream(ss).forEach(System.out::println));}输出如预期:helloworldEpilogue为了解决链式调用的问题,我们在上一篇文章中研究过延伸法,研究有点“过火”。这次又回到了最初的源头,处理链式调用。如果你在研究过程中有想法,不妨试试看。如果你在JDK中发现了类似的处理方法,不要犹豫,去看源码——毕竟OpenJDK是开源的!还有一点,Java泛型的类型擦除特性有时候确实不方便,但有时候真的很方便!