Skip to content

Instantly share code, notes, and snippets.

@tlinkowski
Last active February 16, 2019 18:18
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tlinkowski/bceba241e670cb6efeeea4490e363591 to your computer and use it in GitHub Desktop.
Save tlinkowski/bceba241e670cb6efeeea4490e363591 to your computer and use it in GitHub Desktop.
Accumulative: Custom Java Collectors Made Easy
/*
* Copyright 2019 Tomasz Linkowski.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package java.util.stream;
import java.util.function.Supplier;
import java.util.stream.Collector;
/**
* A standard contract for an intermediate accumulation type of the reduction operation,
* as defined in {@link Collector}.
*
* <p>The intermediate accumulation type also goes by the name of "mutable result container".
*
* @param <T> the type of input elements to the reduction operation
* @param <A> the intermediate accumulation type that is a subtype of {@link Accumulative}
* @param <R> the result type of the reduction operation
*
* @see Collector#of(Supplier, Collector.Characteristics...)
*/
public interface Accumulative<T, A extends Accumulative<T, A, R>, R> {
/**
* Folds a value into this instance.
*
* <p>Method reference target for {@link Collector#accumulator()}.
*
* @param t value to fold
*/
void accumulate(T t);
/**
* Accepts another partial result and merges it with this instance. The
* combine method may fold state from one partial result into the other and
* return that, or may return a new result container.
*
* <p>Method reference target for {@link Collector#combiner()}.
*
* @param other another partial result to combine
* @return a combined result
*/
A combine(A other);
/**
* Performs the final transformation from the intermediate accumulation type
* {@code A} to the final result type {@code R}.
*
* <p>Method reference target for {@link Collector#finisher()}.
*
* @return the final result
*/
R finish();
}
/*
* Copyright 2019 Tomasz Linkowski.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package java.util.stream;
import java.util.function.Supplier;
import java.util.stream.Collector;
public interface Collector<T, A, R> {
/**
* Returns a new {@code Collector} described by the given {@code supplier} of an {@link Accumulative} instance.
*
* @param supplier The supplier function for the new collector
* @param characteristics The collector characteristics for the new collector
* @param <T> The type of input elements for the new collector
* @param <A> The intermediate accumulation type of the new collector which is a subtype of {@link Accumulative}
* @param <R> The final result type of the new collector
* @throws NullPointerException if any argument is null
* @return the new {@code Collector}
*/
static <T, A extends Accumulative<T, A, R>, R> Collector<T, ?, R> of(
Supplier<A> supplier, Collector.Characteristics... characteristics) {
return Collector.of(supplier, A::accumulate, A::combine, A::finish, characteristics);
}
}
import java.util.Optional;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.stream.Collector;
public class Collectors {
public static <T> Collector<T, ?, Optional<T>> reducing(BinaryOperator<T> op) {
class OptionalBox implements Accumulative<T, OptionalBox, Optional<T>> {
T value = null;
boolean present = false;
@Override
public void accumulate(T t) {
if (present) {
value = op.apply(value, t);
} else {
value = t;
present = true;
}
}
@Override
public OptionalBox combine(OptionalBox other) {
if (other.present) accumulate(other.value);
return this;
}
@Override
public Optional<T> finish() {
return Optional.ofNullable(value);
}
}
return Collector.of(OptionalBox::new);
}
}
public class Collectors {
public static <T, R1, R2, R>
Collector<T, ?, R> teeing(Collector<? super T, ?, R1> downstream1,
Collector<? super T, ?, R2> downstream2,
BiFunction<? super R1, ? super R2, R> merger) {
return teeing0(downstream1, downstream2, merger);
}
private static <T, A1, A2, R1, R2, R>
Collector<T, ?, R> teeing0(Collector<? super T, A1, R1> downstream1,
Collector<? super T, A2, R2> downstream2,
BiFunction<? super R1, ? super R2, R> merger) {
Supplier<A1> c1Supplier = downstream1.supplier();
Supplier<A2> c2Supplier = downstream2.supplier();
BiConsumer<A1, ? super T> c1Accumulator = downstream1.accumulator();
BiConsumer<A2, ? super T> c2Accumulator = downstream2.accumulator();
BinaryOperator<A1> c1Combiner = downstream1.combiner();
BinaryOperator<A2> c2Combiner = downstream2.combiner();
Function<A1, R1> c1Finisher = downstream1.finisher();
Function<A2, R2> c2Finisher = downstream2.finisher();
class PairBox implements Accumulative<T, PairBox, R> {
A1 left = c1Supplier.get();
A2 right = c2Supplier.get();
@Override
public void accumulate(T t) {
c1Accumulator.accept(left, t);
c2Accumulator.accept(right, t);
}
@Override
public PairBox combine(PairBox other) {
left = c1Combiner.apply(left, other.left);
right = c2Combiner.apply(right, other.right);
return this;
}
@Override
public R finish() {
R1 r1 = c1Finisher.apply(left);
R2 r2 = c2Finisher.apply(right);
return merger.apply(r1, r2);
}
}
return Collector.of(PairBox::new);
}
}
class CoverageContainer implements Accumulative<IssueWiseText, CoverageContainer, Double> {
int totalIssueLength = 0;
int totalTextLength = 0;
@Override
public void accumulate(IssueWiseText t) {
totalIssueLength += t.issueLength();
totalTextLength += t.textLength();
}
@Override
public CoverageContainer combine(CoverageContainer other) {
totalIssueLength += other.totalIssueLength;
totalTextLength += other.totalTextLength;
return this;
}
@Override
public Double finish() {
return (double) totalIssueLength / totalTextLength;
}
}
public interface IssueWiseText {
int textLength();
int issueLength();
}
class Usage {
static Collector<IssueWiseText, ?, Double> toIssueCoverage() {
return Collector.of(CoverageContainer::new, Collector.Characteristics.UNORDERED);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment