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