My email system got horribly confused with the generics. So if you tried to copy & paste the code samples, it would not have worked.

Here they are again in plain text format.

(A small improvement is that instead of a HashMap we use a LinkedHashMap, thereby preserving the order of the initial stream. Thanks Federico for the suggestion.)

import java.util.*;
import java.util.function.*;
import java.util.stream.*;

public class EnhancedStream implements Stream {
private static final class Key {
private final T t;
private final ToIntFunction hashCode;
private final BiPredicate equals;

public Key(T t, ToIntFunction hashCode,
BiPredicate equals) {
this.t = t;
this.hashCode = hashCode;
this.equals = equals;
}

public int hashCode() {
return hashCode.applyAsInt(t);
}

public boolean equals(Object obj) {
if (!(obj instanceof Key)) return false;
@SuppressWarnings("unchecked")
Key that = (Key) obj;
return equals.test(this.t, that.t);
}
}

private Stream delegate;

public EnhancedStream(Stream delegate) {
this.delegate = delegate;
}

public EnhancedStream distinct(ToIntFunction hashCode,
BiPredicate equals,
BinaryOperator merger) {
delegate = collect(Collectors.toMap(
t -> new Key(t, hashCode, equals),
t -> t,
merger,
LinkedHashMap::new))
// thanks Federico Peralta Schaffner for suggesting that
// we use a LinkedHashMap. That way we can preserve the
// original order
.values()
.stream();
return this;
}

public EnhancedStream filter(
Predicate super T> predicate) {
this.delegate = delegate.filter(predicate);
return this;
}

public EnhancedStream map(
Function super T, ? extends R> mapper) {
return new EnhancedStream(delegate.map(mapper));
}

public IntStream mapToInt(ToIntFunction super T> mapper) {
return delegate.mapToInt(mapper);
}

public LongStream mapToLong(
ToLongFunction super T> mapper) {
return delegate.mapToLong(mapper);
}

public DoubleStream mapToDouble(
ToDoubleFunction super T> mapper) {
return delegate.mapToDouble(mapper);
}

public EnhancedStream flatMap(
Function super T,
? extends Stream extends R>> mapper) {
return new EnhancedStream(delegate.flatMap(mapper));
}

public IntStream flatMapToInt(
Function super T, ? extends IntStream> mapper) {
return delegate.flatMapToInt(mapper);
}

public LongStream flatMapToLong(
Function super T, ? extends LongStream> mapper) {
return delegate.flatMapToLong(mapper);
}

public DoubleStream flatMapToDouble(
Function super T, ? extends DoubleStream> mapper) {
return delegate.flatMapToDouble(mapper);
}

public EnhancedStream distinct() {
delegate = delegate.distinct();
return this;
}

public EnhancedStream sorted() {
delegate = delegate.sorted();
return this;
}

public EnhancedStream sorted(
Comparator super T> comparator) {
delegate = delegate.sorted(comparator);
return this;
}

public EnhancedStream peek(Consumer super T> action) {
delegate = delegate.peek(action);
return this;
}

public EnhancedStream limit(long maxSize) {
delegate = delegate.limit(maxSize);
return this;
}

public EnhancedStream skip(long n) {
delegate = delegate.skip(n);
return this;
}

public EnhancedStream takeWhile(
Predicate super T> predicate) {
delegate = delegate.takeWhile(predicate);
return this;
}

public EnhancedStream dropWhile(
Predicate super T> predicate) {
delegate = delegate.dropWhile(predicate);
return this;
}

public void forEach(Consumer super T> action) {
delegate.forEach(action);
}

public void forEachOrdered(Consumer super T> action) {
delegate.forEachOrdered(action);
}

public Object[] toArray() {
return delegate.toArray();
}

public A[] toArray(IntFunction generator) {
return delegate.toArray(generator);
}

public T reduce(T identity, BinaryOperator accumulator) {
return delegate.reduce(identity, accumulator);
}

public Optional reduce(BinaryOperator accumulator) {
return delegate.reduce(accumulator);
}

public U reduce(U identity,
BiFunction accumulator,
BinaryOperator combiner) {
return delegate.reduce(identity, accumulator, combiner);
}

public R collect(Supplier supplier,
BiConsumer accumulator,
BiConsumer combiner) {
return delegate.collect(supplier, accumulator, combiner);
}

public R collect(
Collector super T, A, R> collector) {
return delegate.collect(collector);
}

public Optional min(Comparator super T> comparator) {
return delegate.min(comparator);
}

public Optional max(Comparator super T> comparator) {
return delegate.max(comparator);
}

public long count() {
return delegate.count();
}

public boolean anyMatch(Predicate super T> predicate) {
return delegate.anyMatch(predicate);
}

public boolean allMatch(Predicate super T> predicate) {
return delegate.allMatch(predicate);
}

public boolean noneMatch(Predicate super T> predicate) {
return delegate.noneMatch(predicate);
}

public Optional findFirst() {
return delegate.findFirst();
}

public Optional findAny() {
return delegate.findAny();
}

public Iterator iterator() {
return delegate.iterator();
}

public Spliterator spliterator() {
return delegate.spliterator();
}

public boolean isParallel() {
return delegate.isParallel();
}

public Stream sequential() {
return delegate.sequential();
}

public Stream parallel() {
return delegate.parallel();
}

public Stream unordered() {
return delegate.unordered();
}

public Stream onClose(Runnable closeHandler) {
return delegate.onClose(closeHandler);
}

public void close() {
delegate.close();
}

public static EnhancedStream of(T t) {
return new EnhancedStream(Stream.of(t));
}

@SafeVarargs
@SuppressWarnings("varargs")
// Creating a stream from an array is safe
public static EnhancedStream of(T... values) {
return new EnhancedStream(Arrays.stream(values));
}
}


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() }


import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.function.*;
import java.util.stream.*;

public class MethodDistinctify {
public static void main(String... args) {
System.out.println("Normal ArrayDeque clone() Methods:");
EnhancedStream.of(ArrayDeque.class.getMethods())
.filter(method -> method.getName().equals("clone"))
.forEach(MethodDistinctify::print);

System.out.println();

System.out.println("Distinct ArrayDeque:");
EnhancedStream.of(ArrayDeque.class.getMethods())
.filter(method -> method.getName().equals("clone"))
.distinct(HASH_CODE, EQUALS, MERGE)
.forEach(MethodDistinctify::print);

System.out.println();

System.out.println("Normal ConcurrentSkipListSet:");
EnhancedStream.of(ConcurrentSkipListSet.class.getMethods())
.filter(method -> method.getName().contains("Set"))
.sorted(METHOD_COMPARATOR)
.forEach(MethodDistinctify::print);

System.out.println();

System.out.println("Distinct ConcurrentSkipListSet:");
EnhancedStream.of(ConcurrentSkipListSet.class.getMethods())
.filter(method -> method.getName().contains("Set"))
.distinct(HASH_CODE, EQUALS, MERGE)
.sorted(METHOD_COMPARATOR)
.forEach(MethodDistinctify::print);
}

private static void print(Method m) {
System.out.println(
Stream.of(m.getParameterTypes())
.map(Class::getSimpleName)
.collect(Collectors.joining(
", ",
" " + m.getReturnType().getSimpleName()
+ " " + m.getName() + "(",
")"))
);
}

public static final ToIntFunction HASH_CODE =
method -> method.getName().hashCode() +
method.getParameterCount();

public static final BiPredicate EQUALS =
(method1, method2) ->
method1.getName().equals(method2.getName()) &&
method1.getParameterCount() ==
method2.getParameterCount() &&
Arrays.equals(method1.getParameterTypes(),
method2.getParameterTypes());

public static final BinaryOperator MERGE =
(method1, method2) -> {
if (method1.getReturnType()
.isAssignableFrom(method2.getReturnType()))
return method2;
if (method2.getReturnType()
.isAssignableFrom(method1.getReturnType()))
return method1;
throw new IllegalArgumentException(
"Conflicting return types " +
method1.getReturnType().getCanonicalName() +
" and " +
method2.getReturnType().getCanonicalName());
};

public static final Comparator METHOD_COMPARATOR =
Comparator.comparing(Method::getName)
.thenComparing(method ->
Arrays.toString(method.getParameterTypes()));
}

Thanks for the comments so far :-)

Kind regards

Heinz

If you no longer wish to receive our emails, click the link below:

https://iw127.infusionsoft.com/app/optOut/8/a6ca3d5100f0d052/7d9988f404f0a1b22fb137d7a131034463677c36889c477cb0bdb35265a29bb22e1c1cd6086af549144faf18823aee3f

Cretesoft Limited 77 Strovolos Ave Strovolos, Lefkosia 2018 Cyprus