Laden...
[275] EnhancedStream with Dynamic Proxy
Online: https://www.javaspecialists.eu/archive/Issue275.html
----------------------------------------------------------------------
Abstract:
In our previous newsletter we enhanced Java 8 Streams by decorating
them with an EnhancedStream class. The code had a lot of repetition,
which often leads to bugs if written by hand. In this newsletter we
use a dynamic proxy to create an EnhancedStream. The resulting code is
shorter and more consistent.
----------------------------------------------------------------------
Welcome to the 275th edition of The Java(tm) Specialists' Newsletter.
The sun is shining and beckoning me to go for a swim in the sea. And I
will do exactly that as soon as I've sent off this newsletter.
https://javaspecialists.teachable.com: Please visit our new self-study
course catalog to see how you can upskill your Java knowledge.
*EnhancedStream with Dynamic Proxy*
In our previous newsletter we presented an EnhancedStream that allowed
a more flexible approach to managing distinctness. We implemented
Stream and changed all those methods that returned Stream, to instead
return EnhancedStream. I'm not good at such mundane tasks (few humans
are) and forgot a few.
I'm co-authoring a book entitled "Dynamic Proxies in Java". Could
dynamic proxies help us to reduce the amount of repetitive code?
(Would you like to help us review the book? Please sign up on
https://www.javaspecialists.eu/books/dynamic-proxies-in-java)
We start by changing EnhancedStream to be a subinterface of Stream.
This contains our new distinct() method, then all the methods whose
return type we need to change to EnhancedStream, and lastly two static
factory methods of() and from().
import java.lang.reflect.*;
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
public interface EnhancedStream extends Stream {
EnhancedStream distinct(ToIntFunction hashCode,
BiPredicate equals,
BinaryOperator merger);
EnhancedStream filter(Predicate super T> predicate);
EnhancedStream map(
Function super T, ? extends R> mapper);
EnhancedStream flatMap(
Function super T, ? extends Stream extends R>> mapper);
EnhancedStream distinct();
EnhancedStream sorted();
EnhancedStream sorted(Comparator super T> comparator);
EnhancedStream peek(Consumer super T> action);
EnhancedStream limit(long maxSize);
EnhancedStream skip(long n);
EnhancedStream takeWhile(Predicate super T> predicate);
EnhancedStream dropWhile(Predicate super T> predicate);
EnhancedStream sequential();
EnhancedStream parallel();
EnhancedStream unordered();
EnhancedStream onClose(Runnable closeHandler);
// static factory methods
@SafeVarargs
@SuppressWarnings("varargs")
static EnhancedStream of(E... elements) {
return from(Stream.of(elements));
}
static EnhancedStream from(Stream stream) {
return (EnhancedStream) Proxy.newProxyInstance(
EnhancedStream.class.getClassLoader(),
new Class>[] {EnhancedStream.class},
new EnhancedStreamHandler(stream)
);
}
}
Our EnhancedStreamHandler contains the Key and the Stream delegate
that we had inside the EnhancedStream class in our previous
newsletter. Furthermore, we find the enhanced distinct() method and
create a methodMap of all the remaining methods from EnhancedStream to
Stream. That way we can quickly find the correct method on our
delegate.
All method calls are routed via the invoke() method. Inside invoke(),
we first decide whether the method is our enhanced distinct() method.
If it is, we call that directly. Otherwise, if the return type is
EnhancedStream, we find the matching method in our methodMap and
invoke that on our delegate. In this case we return the proxy, which
is an instance of type EnhancedStream. Alternatively we return the
result of calling the method directly on our delegate.
Here is the EnhancedStreamHandler:
import java.lang.reflect.*;
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
public class EnhancedStreamHandler
implements InvocationHandler {
private static final class Key {
private final E e;
private final ToIntFunction hashCode;
private final BiPredicate equals;
public Key(E e, ToIntFunction hashCode,
BiPredicate equals) {
this.e = e;
this.hashCode = hashCode;
this.equals = equals;
}
@Override
public int hashCode() {
return hashCode.applyAsInt(e);
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Key)) return false;
@SuppressWarnings("unchecked")
Key that = (Key) obj;
return equals.test(this.e, that.e);
}
}
private Stream delegate;
public EnhancedStreamHandler(Stream delegate) {
this.delegate = delegate;
}
private static final Method enhancedDistinct;
static {
try {
enhancedDistinct = EnhancedStream.class.getMethod(
"distinct", ToIntFunction.class, BiPredicate.class,
BinaryOperator.class
);
} catch (NoSuchMethodException e) {
throw new Error(e);
}
}
private static final Map methodMap =
Stream.of(EnhancedStream.class.getMethods())
.filter(m -> !m.equals(enhancedDistinct))
.filter(m -> !Modifier.isStatic(m.getModifiers()))
.collect(Collectors.toUnmodifiableMap(
Function.identity(),
m -> {
try {
return Stream.class.getMethod(
m.getName(), m.getParameterTypes());
} catch (NoSuchMethodException e) {
throw new Error(e);
}
}));
@Override
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
if (method.equals(enhancedDistinct)) {
return distinct(
(EnhancedStream) proxy,
(ToIntFunction) args[0],
(BiPredicate) args[1],
(BinaryOperator) args[2]);
} else if (method.getReturnType() == EnhancedStream.class) {
Method match = methodMap.get(method);
this.delegate = (Stream) match.invoke(delegate, args);
return proxy;
} else {
return method.invoke(this.delegate, args);
}
}
private EnhancedStream distinct(EnhancedStream proxy,
ToIntFunction hashCode,
BiPredicate equals,
BinaryOperator merger) {
delegate = delegate.collect(Collectors.toMap(
t -> new Key(t, hashCode, equals),
Function.identity(),
merger,
LinkedHashMap::new))
.values()
.stream();
return proxy;
}
}
Our client code looks exactly the same as before. Here is again our
BeachDistinctify class:
import java.util.function.*;
public class BeachDistinctify {
public static void main(String... args) {
EnhancedStream.of("Kalathas", "Stavros", "STAVROS",
"marathi", "kalathas", "baLos", "Balos")
.distinct(HASH_CODE, EQUALS, MERGE)
.forEach(System.out::println);
}
// case insensitive hashCode() and equals()
public static final
ToIntFunction HASH_CODE =
s -> s.toUpperCase().hashCode();
public static final
BiPredicate EQUALS =
(s1, s2) ->
s1.toUpperCase().equals(s2.toUpperCase());
// keep the string with the highest total ascii value
public static final
BinaryOperator MERGE =
(s1, s2) ->
s1.chars().sum() }
There are some disadvantages with this new approach. There might be a
slight method call overhead with some of the proxied methods. For
example, each time our return type is EnhancedStream, we need to do a
map lookup. Secondly, return values might need to be boxed from
primitives to objects and back. Thirdly, methods suffer from amnesia;
they have to check every single time that we have the permission to
invoke them. We deal with these issues in the upcoming book Dynamic
Proxies in Java, which we are publishing as a free e-book on InfoQ.
Would you like to help us by reviewing the book? Please sign up on
https://www.javaspecialists.eu/books/dynamic-proxies-in-java
Kind regards
Heinz
P.S. Apologies for the horrible text email. Unfortunately I cannot
get the generics to render correctly. For a nicer HTML format, go to
https://www.javaspecialists.eu/archive/Issue275.html
Java Specialists Superpack 2019
https://javaspecialists.teachable.com/p/superpack2019
Our entire Java Specialists Training in One Huge Bundle
If you no longer wish to receive our emails, click the link below:
Cretesoft Limited 77 Strovolos Ave Strovolos, Lefkosia 2018 Cyprus
Laden...
Laden...