In the recently issued " C # extension methods in Java simulation ," a paper to simulate a Java extension methods. But after all, there is no grammar support, and there are still many inconveniences to use, especially when the "extension methods" implemented in different classes need to be used continuously, it is very cumbersome to switch objects. As mentioned in the previous article, the reason why we thought of studying "extension methods" was actually just for "chain call".
So, why not start directly from the original requirements and only solve the problem of chained calls, instead of considering broader extension methods? In the previous article, I have studied the realization of chain calls through the Builder mode. This method requires you to define your own extension method classes (that is, Builder), which is still more cumbersome.
Chain prototype
The main feature of chain call is that after using an object, you can continue to use the object... Keep using it. You see, there are two things here: one is to provide an object; the other is to use this object-isn't this the Supplier and Consumer? Java just provides two functional interfaces with the same name in the java.util.function
In this way, we can define a Chain
class, starting from a Supplier, and constantly "consuming" it, such a simple Chain
prototype comes out:
public class Chain<T> {
private final T value;
public Chain(Supplier<? extends T> supplier) {
this.value = supplier.get();
}
public Chain<T> consume(Consumer<? super T> consumer) {
consumer.accept(this.value);
return this;
}
}
Now, if we have a Person
, it has some behavior methods:
class Person {
public void talk() { }
public void walk(String target) { }
public void eat() { }
public void sleep() { }
}
The same business scenario as in the previous article: negotiated, go out, eat, come back, sleep. The non-chain trial call is like this:
public static void main(String[] args) {
var person = new Person();
person.talk();
person.walk("饭店");
person.eat();
person.walk("家");
person.sleep();
}
If you use Chain
string together:
public static void main(String[] args) {
new Chain<>(Person::new).consume(Person::talk)
.consume(p -> p.walk("饭店"))
.consume(Person::eat)
.consume(p -> p.walk("家"))
.consume(Person::sleep);
}
The Chain encapsulation has been completed above, it is quite simple, but there are two small problems:
consume()
Too many words, it is troublesome to write. If you change the name,do
is very suitable, but it is a keyword... It is better to change toact
;- Link calls are often used in expressions and need to have a return value, so
Chain::getValue()
Perfect Chain
In fact, in the chain call process, it is not necessarily just "consumption", it may also need "conversion", in the words of the programmer, it is map()
-the current object is passed in as a parameter, and the calculation is completed. An object. Maybe you use map()
more in java stream, but the scene here is more like Optional::map
.
Take a look at Optional::map
of source code :
public <U> Optional<U> map(Function<? super T, ? extends U> mapper) {
Objects.requireNonNull(mapper);
if (!isPresent()) {
return empty();
} else {
return Optional.ofNullable(mapper.apply(value));
}
}
It can be seen that map()
is very simple, that is Function
operation is then encapsulated into a Optional
object. We can do the same Chain
public <U> Chain<U> map(Function<? super T, ? extends U> mapper) {
return new Chain<>(() -> mapper.apply(value));
}
I wrote here and found that Supplier
Chain
objects directly from the "value"-of course, you can add an overload of the constructor to solve this problem, but I want to write Optional
Two static methods are implemented, and the constructor is hidden at the same time. The modified and complete Chain
as follows:
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
public class Chain<T> {
private final T value;
public static <T> Chain<T> of(T value) {
return new Chain<>(value);
}
public static <T> Chain<T> from(Supplier<T> supplier) {
return new Chain<>(supplier.get());
}
private Chain(T value) {
this.value = value;
}
public T getValue() {
return value;
}
public Chain<T> act(Consumer<? super T> consumer) {
consumer.accept(this.value);
return this;
}
public <U> Chain<U> map(Function<? super T, ? extends U> mapper) {
return Chain.of(mapper.apply(value));
}
}
Continue to transform Chain
map()
will always return a new Chain
object. If there are many map steps in a certain process, then many Chain
objects will be generated. Can it be Chain
object?
Defined Chain
when used generics, generic type and will be erased after compilation, and we directly value
defined as Object
not much difference. That being the case, map()
time, directly to the value
to replace, rather than creating new Chain
whether the object is a viable option? ——It is indeed feasible. But on the one hand, you need to continue to use generics to constrain consumer
and mapper
. On the other hand, you need to perform forced type conversion internally, and you must ensure that this conversion will not be a problem.
Theoretically speaking, Chain
handles chain calls, one after another, and the result of each ring is saved in value
for the beginning of the next ring. Therefore, under the constraints of generics, no matter how you change, there will be no problems. The theory is feasible, it is better to practice:
// 因为存在大量的类型转换(逻辑确认可行),需要忽略掉相关警告
@SuppressWarnings("unchecked")
public class Chain<T> {
// 把 value 声明为 Object 类型,以便引用各种类型的值
// 同时去掉 final 修饰,使之可变
private Object value;
public static <T> Chain<T> of(T value) {
return new Chain<>(value);
}
public static <T> Chain<T> from(Supplier<T> supplier) {
return new Chain<>(supplier.get());
}
private Chain(T value) {
this.value = value;
}
public T getValue() {
// 使用到 value 的地方都需要把 value 转换为 Chain<> 的泛型参数类型,下同
return (T) value;
}
public Chain<T> act(Consumer<? super T> consumer) {
consumer.accept((T) this.value);
return this;
}
public <U> Chain<U> map(Function<? super T, ? extends U> mapper) {
// mapper 的计算结果无所谓是什么类型都可以给 Object 类型的 value 赋值
this.value = mapper.apply((T) value);
// 返回的 Chain 虽然还是自己(就这个对象),但是泛型参数得换成 U 了
// 换了类型之后,后序的操作才会基于 U 类型来进行
return (Chain<U>) this;
}
}
The last sentence of type conversion (Chain<U>) this
is very spiritual. It can be done in Java (because of type erasure), but it cannot be done in C# anyway!
Write another piece of code to test it out:
public static void main(String[] args) {
// 注意:String 的操作会产生新的 String 对象,所以要用 map
Chain.of(" Hello World ")
.map(String::trim)
.map(String::toLowerCase)
// ↓ 把 String 拆分成 String[],这里转换了不相容类型
.map(s ->s.split("\s+"))
// ↓ 消费这个 String[],依次打印出来
.act(ss -> Arrays.stream(ss).forEach(System.out::println));
}
The output is as expected:
hello
world
Concluding remarks
In order to solve the problem of chain call, we studied the extension method in the previous article, and the study was a bit "excessive". This time back to the roots, we will deal with chain calls.
If you have an idea during your research, you might as well give it a try. If you find a similar approach in the JDK, don't prevent yourself from looking at the source code-after all, OpenJDK is open source!
Another point is that the type erasure feature of Java generics does sometimes bring inconvenience, but sometimes it is really convenient!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。