Related
I'm pretty experienced with Java, however a novice to using Reflection and Annotation classes, which I'm trying to learn for fun. To get some practice, I made an Identifiable class which is designed to add several helpful methods to any class it inherits.
Here is the full class:
abstract class Identifiable<T, K extends Comparable<K>> implements Comparable<Identifiable<T, K>> {
#Retention(RetentionPolicy.RUNTIME)
public #interface Identifier { }
private static Method getMethodAnnotatedWith(final Class<?> type) {
return Arrays.stream(type.getDeclaredMethods())
.filter(m -> m.isAnnotationPresent(Identifier.class))
.findFirst()
.orElse(null);
}
private K id;
#SuppressWarnings("unchecked")
public Identifiable(Class<T> clazz) {
var m = getMethodAnnotatedWith(clazz);
if (m == null) throw new IllegalArgumentException(
clazz.toString() + " does not have a method annotated by #Identifier"
);
try {
id = (K) m.invoke(this);
} catch (Exception e) {
e.printStackTrace();
}
}
#Override
public int compareTo(#NotNull Identifiable<T, K> i) {
return id.compareTo(i.id);
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Identifiable<?, ?> that = (Identifiable<?, ?>) o;
return id == that.id;
}
#Override
public int hashCode() {
return Objects.hash(id);
}
}
And here is how I am trying to design it to work:
class Foo extends Identifiable<Foo, Integer> {
private final int i;
Foo(int i) {
super(Foo.class);
this.i = i;
}
#Identifier
int getI() {
return i;
}
}
However, id is always 0 for some reason, so I'm not sure if it's a problem with my Identifier annotation class or the way I'm using reflection. I'm pretty sure it's the latter since while debugging, I found that it is able to access the method with the annotation. Any help would be appreciated, thanks!
Don't call the annotated method during construction.
If the identifier value is immutable (final), just pass the value to the super constructor.
public Identifiable(K id) {
this.id = id;
}
Foo(int i) {
super(i);
this.i = i;
}
If the identifier value is mutable, you need to change the logic to invoke the method when you need the value, not cache the value during construction.
abstract class Identifiable<T, K extends Comparable<K>> implements Comparable<Identifiable<T, K>> {
#Retention(RetentionPolicy.RUNTIME)
public #interface Identifier {/**/}
private Method idGetter;
protected Identifiable(Class<T> type) {
this.idGetter = Arrays.stream(type.getDeclaredMethods())
.filter(m -> m.isAnnotationPresent(Identifier.class))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException(type.getName() + " does not have a method annotated by #Identifier"));
}
#SuppressWarnings("unchecked")
private final K getIdentifiableKey() {
try {
return (K) this.idGetter.invoke(this);
} catch (IllegalAccessException e) {
throw new IllegalAccessError(e.getMessage());
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
#Override
public int compareTo(Identifiable<T, K> that) {
return this.getIdentifiableKey().compareTo(that.getIdentifiableKey());
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Identifiable<?, ?> that = (Identifiable<?, ?>) o;
return this.getIdentifiableKey().equals(that.getIdentifiableKey()); // Call equals(), don't use ==
}
#Override
public int hashCode() {
return Objects.hash(this.getIdentifiableKey());
}
}
Alternatively, use a functional interface and supply it with a method reference.
abstract class Identifiable<T extends Identifiable<T, K>, K extends Comparable<K>> implements Comparable<Identifiable<T, K>> {
private Function<T, K> idGetter;
protected Identifiable(Function<T, K> idGetter) {
this.idGetter = Objects.requireNonNull(idGetter);
}
#Override
#SuppressWarnings("unchecked")
public int compareTo(Identifiable<T, K> that) {
return this.idGetter.apply((T) this).compareTo(that.idGetter.apply((T) that));
}
#Override
#SuppressWarnings("unchecked")
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Identifiable<T, K> that = (Identifiable<T, K>) o;
return this.idGetter.apply((T) this).equals(that.idGetter.apply((T) that));
}
#Override
#SuppressWarnings("unchecked")
public int hashCode() {
return Objects.hash(this.idGetter.apply((T) this));
}
}
class Foo extends Identifiable<Foo, Integer> {
private final int i;
Foo(int i) {
super(Foo::getI);
this.i = i;
}
int getI() {
return i;
}
}
I am trying to create a two dimensional list in java.
My first and preferred method is as so:
List<List<Integer>> seqList = IntStream.range(0, n)
.mapToObj(ArrayList<Integer>::new)
.collect(Collectors.toList());
However, for some reason this method takes too long and I get a timeout.
On the other hand, when I tried to create the two dimensional list using java 7 like so, there was no timeout.
List<List<Integer>> seqList = new ArrayList<>();
for(int i = 0; i < n; i++) {
seqList.add(new ArrayList<>());
}
I am trying to use as much java-8 streams as possible. Could someone explain to me why my java-8 code is taking too long and what I can do to make it run in the same time complexity as the java-7 code.
This is an alternative way to do it.
int n = 10;
List<List<Integer>> seqList =Stream.<List<Integer>>generate(()->new ArrayList<>())
.limit(n).collect(Collectors.toList());
Thanks to Jacob G I was able to see the problem.
The call .mapToObj(ArrayList<Integer>::new) was creating ArrayLists of varying size. It was equivalent to .mapToObj(i -> new ArrayList<Integer>(i)). Now this means that creating new arraylist objects when i is huge take longer hence the timeout. The better code is as follows:
List<List<Integer>> seqList2 = IntStream.range(0, n)
.mapToObj(i -> new ArrayList<Integer>())
.collect(Collectors.toList());
The relative cost of the streaming APIs will be high, even with the correction. This can be seen by walking through the many steps which are performed. The complexity is rather quite extraordinary.
The code examples, below, are from the IBM Java SE Runtime Environment version 1.8.
// A walkthrough of the operation:
//
// "Create a list of lists by collecting the results of applying the ArrayList
// initializer to the stream of 'int' values ranging from 0 to 10."
static {
List<List<Integer>> seqList = IntStream.range(0, 10)
.mapToObj( ArrayList<Integer>::new )
.collect( Collectors.toList() );
}
// First step: Create an 'int' Stream.
//
// Roughly, create an 'int' iterator, then wrap that in a 'int' stream.
//
// The iterator is responsible for knowing the initial and final values
// over the range of iteration, and for providing basic iteration.
//
// However, 'mapToObj' is part of the streaming API. The iterator
// must be put into a stream to access that API.
// The 'int' stream factory method.
//
// Fan out to 'RangeIntSpliterator.init' and to 'StreamSupport.intStream'.
//
// The 'int' stream is created with 'parallel' set to false.
class IntStream {
public static IntStream range(int startInclusive, int endExclusive) {
if ( startInclusive >= endExclusive ) {
return empty();
} else {
return StreamSupport.intStream(
new Streams.RangeIntSpliterator(startInclusive, endExclusive, false),
false );
}
}
}
// The 'int' iterator type.
//
// After setup, 'forEachRemaining' will be used to perform
// the 'int' iteration.
class RangeIntSpliterator implements Spliterator.OfInt {
protected int from;
protected final int upTo;
protected int last;
RangeIntSpliterator(int from, int upTo, boolean closed) {
this( from, upTo, (closed ? 1 : 0) );
}
void forEachRemaining(Consumer<? super Integer> action);
void forEachRemaining(IntConsumer consumer);
}
// The 'int' stream factory method.
//
// Fan out to 'IntPipeline.Head<>.init'. 'IntPipeline.Head' extends
// 'IntPipeline', which extends 'AbstractPipeline'.
//
// 'IntPipeline.mapToObj' creates an stream of 'ArrayList' instances
// out of the stream of 'int' instances.
class StreamSupport {
public static IntStream intStream(Spliterator.OfInt spliterator, boolean parallel) {
return new IntPipeline.Head<>(
spliterator,
StreamOpFlag.fromCharacteristics(spliterator),
parallel );
}
}
class IntPipeLine.Head<> extends IntPipeline<> {
Head(Spliterator<Integer> source, int sourceFlags, boolean parallel) {
super(source, sourceFlags, parallel);
}
}
class IntPipeline<>
extends AbstractPipeline<, Integer, IntStream>
implements IntStream {
IntPipeline(Spliterator<Integer> source, int sourceFlags, boolean parallel) {
super(source, sourceFlags, parallel);
}
<U> Stream<U> mapToObj(IntFunction<? extends U> mapper);
}
class AbstractPipeline {
AbstractPipeline(Spliterator<?> source, int sourceFlags, boolean parallel) {
this.previousStage = null;
this.sourceSpliterator = source;
this.sourceStage = this;
this.sourceOrOpFlags = ( sourceFlags & StreamOpFlag.STREAM_MASK );
this.combinedFlags = ( (~(sourceOrOpFlags << 1)) & StreamOpFlag.INITIAL_OPS_VALUE );
this.depth = 0;
this.parallel = parallel;
}
}
// Second step: Create a second stream by composing the 'int' stream with the ArrayList
// initializer.
//
// Fan out to 'ReferencePipeline.StatelessOp'. 'StatelessOp' extends 'ReferencePipeline',
// which extends 'AbstractPipeline'.
class IntPipeline {
#Override
public final <U> Stream<U> mapToObj(IntFunction<? extends U> mapper) {
Objects.requireNonNull(mapper);
return new ReferencePipeline.StatelessOp<Integer, U>(
this,
StreamShape.INT_VALUE,
(StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) ) {
Sink<Integer> opWrapSink(int flags, Sink<U> sink) {
return new Sink.ChainedInt<U>(sink) {
public void accept(int t) {
downstream.accept( mapper.apply(t) );
}
};
}
};
}
}
class StatelessOp<E_IN, E_OUT> extends ReferencePipeline<E_IN, E_OUT> {
StatelessOp(AbstractPipeline<?, E_IN, ?> upstream, StreamShape inputShape, int opFlags) {
super(upstream, opFlags);
assert upstream.getOutputShape() == inputShape;
}
abstract class ReferencePipeline<P_IN, P_OUT>
extends AbstractPipeline<P_IN, P_OUT, Stream<P_OUT>>
implements Stream<P_OUT> {
ReferencePipeline(Supplier<? extends Spliterator<?>> source, int sourceFlags) {
super(source, sourceFlags);
}
}
abstract class AbstractPipeline<E_IN, E_OUT, S extends BaseStream<E_OUT, S>>
extends PipelineHelper<E_OUT> implements BaseStream<E_OUT, S> {
AbstractPipeline(AbstractPipeline<?, E_IN, ?> previousStage, int opFlags) {
if ( previousStage.linkedOrConsumed ) {
throw new IllegalStateException(MSG_STREAM_LINKED);
}
previousStage.linkedOrConsumed = true;
previousStage.nextStage = this;
this.previousStage = previousStage;
this.sourceOrOpFlags = opFlags & StreamOpFlag.OP_MASK;
this.combinedFlags = StreamOpFlag.combineOpFlags(opFlags, previousStage.combinedFlags);
this.sourceStage = previousStage.sourceStage;
if ( opIsStateful() ) {
sourceStage.sourceAnyStateful = true;
}
this.depth = previousStage.depth + 1;
}
}
// Third step: Obtain the collector which is to be used by the 'int' stream.
//
// Note use of 'CH_ID', which marks the collector as an 'identity finisher'.
class Collectors {
static final Set<Collector.Characteristics> CH_ID =
Collections.unmodifiableSet( EnumSet.of(Collector.Characteristics.IDENTITY_FINISH) );
public static <T> Collector<T, ?, List<T>> toList() {
return new CollectorImpl<>(
(Supplier<List<T>>) ArrayList::new,
List::add,
(left, right) -> { left.addAll(right); return left; },
CH_ID);
}
}
class CollectorImpl<T, A, R> implements Collector<T, A, R> {
private final Supplier<A> supplier;
private final BiConsumer<A, T> accumulator;
private final BinaryOperator<A> combiner;
private final Function<A, R> finisher;
private final Set<Characteristics> characteristics;
CollectorImpl(
Supplier<A> supplier,
BiConsumer<A, T> accumulator,
BinaryOperator<A> combiner,
Function<A,R> finisher,
Set<Characteristics> characteristics) {
this.supplier = supplier;
this.accumulator = accumulator;
this.combiner = combiner;
this.finisher = finisher;
this.characteristics = characteristics;
}
CollectorImpl(
Supplier<A> supplier,
BiConsumer<A, T> accumulator,
BinaryOperator<A> combiner,
Set<Characteristics> characteristics) {
this(supplier, accumulator, combiner, castingIdentity(), characteristics);
}
}
// Fourth step: Start collection.
//
// Push the collector through 'ReduceOps.makeRef'.
class ReferencePipeline {
public final <R, A> R collect(Collector<? super P_OUT, A, R> collector) {
A container;
if ( isParallel() &&
(collector.characteristics().contains(Collector.Characteristics.CONCURRENT)) &&
(!isOrdered() ||
collector.characteristics().contains(Collector.Characteristics.UNORDERED))) {
container = collector.supplier().get();
BiConsumer<A, ? super P_OUT> accumulator = collector.accumulator();
forEach(u -> accumulator.accept(container, u));
} else {
container = evaluate( ReduceOps.makeRef(collector) );
}
return collector.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)
? (R) container
: collector.finisher().apply(container);
}
}
class ReduceOps {
public static <T, I> TerminalOp<T, I> makeRef(Collector<? super T, I, ?> collector) {
Supplier<I> supplier = Objects.requireNonNull(collector).supplier();
BiConsumer<I, ? super T> accumulator = collector.accumulator();
BinaryOperator<I> combiner = collector.combiner();
class ReducingSink extends Box<I> implements AccumulatingSink<T, I, ReducingSink> {
public void begin(long size) {
state = supplier.get();
}
public void accept(T t) {
accumulator.accept(state, t);
}
public void combine(ReducingSink other) {
state = combiner.apply(state, other.state);
}
}
return new ReduceOp<T, I, ReducingSink>(StreamShape.REFERENCE) {
public ReducingSink makeSink() {
return new ReducingSink();
}
};
}
}
class ReduceOp<T, R, S extends AccumulatingSink<T, R, S>> implements TerminalOp<T, R> {
private final StreamShape inputShape;
ReduceOp(StreamShape shape) {
inputShape = shape;
}
}
// Fifth step: Walk into the stream API.
class ReferencePipeline {
<R> R evaluate(TerminalOp<E_OUT, R> terminalOp) {
assert ( getOutputShape() == terminalOp.inputShape() );
if ( linkedOrConsumed ) {
throw new IllegalStateException(MSG_STREAM_LINKED);
}
linkedOrConsumed = true;
return ( isParallel()
? terminalOp.evaluateParallel( this, sourceSpliterator( terminalOp.getOpFlags() ) )
: terminalOp.evaluateSequential( this, sourceSpliterator( terminalOp.getOpFlags() ) ) );
}
}
class AbstractPipeline {
Spliterator<E_OUT> sourceStageSpliterator() {
if ( this != sourceStage ) {
throw new IllegalStateException();
}
if ( linkedOrConsumed ) {
throw new IllegalStateException(MSG_STREAM_LINKED);
}
linkedOrConsumed = true;
if ( sourceStage.sourceSpliterator != null ) {
Spliterator<E_OUT> s = sourceStage.sourceSpliterator;
sourceStage.sourceSpliterator = null;
return s;
} else if ( sourceStage.sourceSupplier != null ) {
Spliterator<E_OUT> s = (Spliterator<E_OUT>) sourceStage.sourceSupplier.get();
sourceStage.sourceSupplier = null;
return s;
} else {
throw new IllegalStateException(MSG_CONSUMED);
}
}
}
class ReduceOp {
public <P_IN> R evaluateSequential(
PipelineHelper<T> helper,
Spliterator<P_IN> spliterator) {
return helper.wrapAndCopyInto( makeSink(), spliterator ).get();
}
}
class AbstractPipeline {
final <P_IN, S extends Sink<E_OUT>> S wrapAndCopyInto(S sink, Spliterator<P_IN> spliterator) {
copyInto( wrapSink( Objects.requireNonNull(sink) ), spliterator );
return sink;
}
}
<P_IN> Sink<P_IN> wrapSink(Sink<E_OUT> sink) {
Objects.requireNonNull(sink);
for ( AbstractPipeline p = AbstractPipeline.this; p.depth > 0; p = p.previousStage ) {
sink = p.opWrapSink( p.previousStage.combinedFlags, sink );
}
return (Sink<P_IN>) sink;
}
class StatelessOp {
Sink<Integer> opWrapSink(int flags, Sink<U> sink) {
return new Sink.ChainedInt<U>(sink) {
public void accept(int t) {
downstream.accept( mapper.apply(t) );
}
};
}
}
// Sixth step: Perform the actual iteration and collection.
//
// Ignoring 'begin' and 'end', iteration and collection occurs in the call
// to 'forEachRemaining'.
class AbstractPipeline {
<P_IN> void copyInto(Sink<P_IN> wrappedSink, Spliterator<P_IN> spliterator) {
Objects.requireNonNull(wrappedSink);
if ( !StreamOpFlag.SHORT_CIRCUIT.isKnown( getStreamAndOpFlags() ) ) {
wrappedSink.begin( spliterator.getExactSizeIfKnown() );
spliterator.forEachRemaining(wrappedSink);
wrappedSink.end();
} else {
copyIntoWithCancel(wrappedSink, spliterator);
}
}
}
class RangeIntSpliterator implements Spliterator.OfInt {
void forEachRemaining(Consumer<? super Integer> action) {
if ( action instanceof IntConsumer ) {
forEachRemaining((IntConsumer) action);
} else {
if ( Tripwire.ENABLED ) {
Tripwire.trip(getClass(), "{0} calling Spliterator.OfInt.forEachRemaining((IntConsumer) action::accept)");
forEachRemaining((IntConsumer) action::accept);
}
}
}
void forEachRemaining(IntConsumer consumer) {
Objects.requireNonNull(consumer);
int i = from;
final int hUpTo = upTo;
int hLast = last;
from = upTo;
last = 0;
while ( i < hUpTo ) {
consumer.accept(i++);
}
if ( hLast > 0 ) {
consumer.accept(i);
}
}
}
// Seventh step: For each iteration, unwind and perform the mapping and
// collection operations.
class new Sink.ChainedInt<U>(sink) {
public void accept(int t) {
downstream.accept( mapper.apply(t) );
}
}
class ArrayList {
public ArrayList(int initialCapacity) {
// ...
}
}
class ReducingSink {
public void accept(T t) {
accumulator.accept(state, t);
}
}
class ArrayList {
public boolean add(E e) {
// ...
}
}
// Eigth step: Walking out with the return value.
IntPipeline$4(AbstractPipeline<E_IN,E_OUT,S>).wrapAndCopyInto(S, Spliterator<P_IN>)
-- returns a 'ReducingSink' instance.
ReduceOps$3(ReduceOps$ReduceOp<T,R,S>).evaluateSequential(PipelineHelper<T>, Spliterator<P_IN>)
-- returns the 'ArrayList' instance.
IntPipeline$4(AbstractPipeline<E_IN,E_OUT,S>).evaluate(TerminalOp<E_OUT,R>)
-- returns the 'ArrayList' instance.
IntPipeline$4(ReferencePipeline<P_IN,P_OUT>).collect(Collector<? super P_OUT,A,R>)
-- returns the 'ArrayList' instance.
Tester.main
The used method reference has return type Integer. But an incompatible String is allowed in the following example.
How to fix the method with declaration to get the method reference type safe without manually casting?
import java.util.function.Function;
public class MinimalExample {
static public class Builder<T> {
final Class<T> clazz;
Builder(Class<T> clazz) {
this.clazz = clazz;
}
static <T> Builder<T> of(Class<T> clazz) {
return new Builder<T>(clazz);
}
<R> Builder<T> with(Function<T, R> getter, R returnValue) {
return null; //TODO
}
}
static public interface MyInterface {
Integer getLength();
}
public static void main(String[] args) {
// missing compiletimecheck is inaceptable:
Builder.of(MyInterface.class).with(MyInterface::getLength, "I am NOT an Integer");
// compile time error OK:
Builder.of(MyInterface.class).with((Function<MyInterface, Integer> )MyInterface::getLength, "I am NOT an Integer");
// The method with(Function<MinimalExample.MyInterface,R>, R) in the type MinimalExample.Builder<MinimalExample.MyInterface> is not applicable for the arguments (Function<MinimalExample.MyInterface,Integer>, String)
}
}
USE CASE: a type safe but generic Builder.
I tried to implement a generic builder without annotation processing (autovalue) or compiler plugin (lombok)
import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
public class BuilderExample {
static public class Builder<T> implements InvocationHandler {
final Class<T> clazz;
HashMap<Method, Object> methodReturnValues = new HashMap<>();
Builder(Class<T> clazz) {
this.clazz = clazz;
}
static <T> Builder<T> of(Class<T> clazz) {
return new Builder<T>(clazz);
}
Builder<T> withMethod(Method method, Object returnValue) {
Class<?> returnType = method.getReturnType();
if (returnType.isPrimitive()) {
if (returnValue == null) {
throw new IllegalArgumentException("Primitive value cannot be null:" + method);
} else {
try {
boolean isConvertable = getDefaultValue(returnType).getClass().isAssignableFrom(returnValue.getClass());
if (!isConvertable) {
throw new ClassCastException(returnValue.getClass() + " cannot be cast to " + returnType + " for " + method);
}
} catch (IllegalArgumentException | SecurityException e) {
throw new RuntimeException(e);
}
}
} else if (returnValue != null && !returnType.isAssignableFrom(returnValue.getClass())) {
throw new ClassCastException(returnValue.getClass() + " cannot be cast to " + returnType + " for " + method);
}
Object previuos = methodReturnValues.put(method, returnValue);
if (previuos != null) {
throw new IllegalArgumentException("Value alread set for " + method);
}
return this;
}
static HashMap<Class, Object> defaultValues = new HashMap<>();
private static <T> T getDefaultValue(Class<T> clazz) {
if (clazz == null || !clazz.isPrimitive()) {
return null;
}
#SuppressWarnings("unchecked")
T cachedDefaultValue = (T) defaultValues.get(clazz);
if (cachedDefaultValue != null) {
return cachedDefaultValue;
}
#SuppressWarnings("unchecked")
T defaultValue = (T) Array.get(Array.newInstance(clazz, 1), 0);
defaultValues.put(clazz, defaultValue);
return defaultValue;
}
public synchronized static <T> Method getMethod(Class<T> clazz, java.util.function.Function<T, ?> resolve) {
AtomicReference<Method> methodReference = new AtomicReference<>();
#SuppressWarnings("unchecked")
T proxy = (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] { clazz }, new InvocationHandler() {
#Override
public Object invoke(Object p, Method method, Object[] args) {
Method oldMethod = methodReference.getAndSet(method);
if (oldMethod != null) {
throw new IllegalArgumentException("Method was already called " + oldMethod);
}
Class<?> returnType = method.getReturnType();
return getDefaultValue(returnType);
}
});
resolve.apply(proxy);
Method method = methodReference.get();
if (method == null) {
throw new RuntimeException(new NoSuchMethodException());
}
return method;
}
// R will accep common type Object :-( // see https://stackoverflow.com/questions/58337639
<R, V extends R> Builder<T> with(Function<T, R> getter, V returnValue) {
Method method = getMethod(clazz, getter);
return withMethod(method, returnValue);
}
//typesafe :-) but i dont want to avoid implementing all types
Builder<T> withValue(Function<T, Long> getter, long returnValue) {
return with(getter, returnValue);
}
Builder<T> withValue(Function<T, String> getter, String returnValue) {
return with(getter, returnValue);
}
T build() {
#SuppressWarnings("unchecked")
T proxy = (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[] { clazz }, this);
return proxy;
}
#Override
public Object invoke(Object proxy, Method method, Object[] args) {
Object returnValue = methodReturnValues.get(method);
if (returnValue == null) {
Class<?> returnType = method.getReturnType();
return getDefaultValue(returnType);
}
return returnValue;
}
}
static public interface MyInterface {
String getName();
long getLength();
Long getNullLength();
Long getFullLength();
Number getNumber();
}
public static void main(String[] args) {
MyInterface x = Builder.of(MyInterface.class).with(MyInterface::getName, "1").with(MyInterface::getLength, 1L).with(MyInterface::getNullLength, null).with(MyInterface::getFullLength, new Long(2)).with(MyInterface::getNumber, 3L).build();
System.out.println("name:" + x.getName());
System.out.println("length:" + x.getLength());
System.out.println("nullLength:" + x.getNullLength());
System.out.println("fullLength:" + x.getFullLength());
System.out.println("number:" + x.getNumber());
// java.lang.ClassCastException: class java.lang.String cannot be cast to long:
// RuntimeException only :-(
MyInterface y = Builder.of(MyInterface.class).with(MyInterface::getLength, "NOT A NUMBER").build();
// java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Long
// RuntimeException only :-(
System.out.println("length:" + y.getLength());
}
}
In the first example, MyInterface::getLength and "I am NOT an Integer" helped to resolve the generic parameters T and R to MyInterface and Serializable & Comparable<? extends Serializable & Comparable<?>>respectively.
// it compiles since String is a Serializable
Function<MyInterface, Serializable> function = MyInterface::getLength;
Builder.of(MyInterface.class).with(function, "I am NOT an Integer");
MyInterface::getLength is not always a Function<MyInterface, Integer> unless you explicitly say so, which would lead to a compile-time error as the second example showed.
// it doesn't compile since String isn't an Integer
Function<MyInterface, Integer> function = MyInterface::getLength;
Builder.of(MyInterface.class).with(function, "I am NOT an Integer");
Its the type inference that is playing its role here. Consider the generic R in the method signature:
<R> Builder<T> with(Function<T, R> getter, R returnValue)
In the case as listed:
Builder.of(MyInterface.class).with(MyInterface::getLength, "I am NOT an Integer");
the type of R is successfully inferred as
Serializable, Comparable<? extends Serializable & Comparable<?>>
and a String does imply by this type, hence the compilation succeeds.
To explicitly specify the type of R and find out the incompatibility, one can simply change the line of code as :
Builder.of(MyInterface.class).<Integer>with(MyInterface::getLength, "not valid");
It is because your generic type parameter R can be inferred to be Object, i.e. the following compiles:
Builder.of(MyInterface.class).with((Function<MyInterface, Object>) MyInterface::getLength, "I am NOT an Integer");
This answer is based on the other answers which explain why it does not work as expected.
SOLUTION
The following code solves the problem by splitting the bifunction "with" into two fluent functions "with" and "returning":
class Builder<T> {
...
class BuilderMethod<R> {
final Function<T, R> getter;
BuilderMethod(Function<T, R> getter) {
this.getter = getter;
}
Builder<T> returning(R returnValue) {
return Builder.this.with(getter, returnValue);
}
}
<R> BuilderMethod<R> with(Function<T, R> getter) {
return new BuilderMethod<>(getter);
}
...
}
MyInterface z = Builder.of(MyInterface.class).with(MyInterface::getLength).returning(1L).with(MyInterface::getNullLength).returning(null).build();
System.out.println("length:" + z.getLength());
// YIPPIE COMPILATION ERRROR:
// The method returning(Long) in the type BuilderExample.Builder<BuilderExample.MyInterface>.BuilderMethod<Long> is not applicable for the arguments (String)
MyInterface zz = Builder.of(MyInterface.class).with(MyInterface::getLength).returning("NOT A NUMBER").build();
System.out.println("length:" + zz.getLength());
(is somewhat unfamiliar)
I have an ArraysHelper class which looks like this in Java:
public class ArraysHelper<T> {
public final Iterable<T> iterable;
private ArraysHelper(Iterable<T> iterable) {
this.iterable = iterable;
}
public static <T> T[] concat(T[] array, #Nullable T element) {
T[] result = Arrays.copyOf(array, array.length + 1);
result[array.length] = element;
return result;
}
public final ArraysHelper<T> filter(Prediction<T> prediction) {
return from(filter(iterable, prediction));
}
private static <T> List<T> filter(Iterable<T> iterable, Prediction<T> predicate) {
ArrayList<T> list = new ArrayList<>();
for (T object : iterable) {
if (predicate.apply(object)) {
list.add(object);
}
}
return list;
}
public final List<T> toList() {
List<T> list = new ArrayList<>();
for (T item : iterable) {
list.add(item);
}
return list;
}
public static <T> ArraysHelper<T> from(final Iterable<T> iterable) {
return new ArraysHelper<>(iterable);
}
}
I converted it to Kotlin like this:
class ArraysHelper<T> private constructor(val iterable: Iterable<T>) {
fun filter(prediction: Prediction<T>): ArraysHelper<T> {
return from(filter(iterable, prediction))
}
fun toList(): ArrayList<T> {
val list = ArrayList<T>()
for (item in iterable) {
list.add(item)
}
return list
}
companion object {
fun <T> concat(array: Array<T>, element: T?): Array<T> {
val result = Arrays.copyOf(array, array.size + 1)
result[array.size] = element
return result
}
private fun <T> filter(iterable: Iterable<T>, predicate: Prediction<T>): ArrayList<T> {
val list = ArrayList<T>()
for (`object` in iterable) {
if (predicate.apply(`object`)) {
list.add(`object`)
}
}
return list
}
fun <T> from(iterable: Iterable<T>): ArraysHelper<T> {
return ArraysHelper(iterable)
}
}
}
I am also converted one interface which i am using. In java:
public interface Prediction<T> {
boolean apply(#Nullable T input);
#Override
boolean equals(#Nullable Object object);
}
In Kotlin:
interface Prediction<T> {
fun apply(input: T?): Boolean
override fun equals(`object`: Any?): Boolean
}
Now to the point - I want to use those here:
val rssiEvents = locationRSSIEvents
if (rssiEvents.isNotEmpty()) {
val latestRSSIEvent = rssiEvents.last()
val cleanedGeoEvents = ArraysHelper.from<LocationGeoEvent>(locationGeoEvents).filter(object : Prediction<LocationGeoEvent> {
override fun apply(input: LocationGeoEvent): Boolean {
return Math.abs(input.timestamp - latestRSSIEvent.timestamp) < maxTimeDifferenceSeconds * 1000
}
}).toList()
}
But in IDE(Android Studio 3.0) underlines the filter method:
object : Prediction<LocationGeoEvent>
and it says:
Required: Prediction<LocationGeoEvent> Found:
So what exatcly is wrong with it?
UPDATE:
Of course, in Kotlin i can make something like this:
val cleanedGeoEvents = locationGeoEvents
.filter { locationGeoEvent -> Math.abs(locationGeoEvent.timestamp - latestRSSIEvent.timestamp) < maxTimeDifferenceSeconds * 1000 }
.toList()
But still, will be useful what exactly is wrong with this previous version
I am creating a class CommonAggregator which can aggregate any object which is Aggregatable.
public interface Aggregatable<T> {
public void addFieldValue(T t, AggregationField<T> field);
}
public class Charges implements Aggregatable<Charges>{
//privat fields
//Getter and Setters
#Override
public void addFieldValue(Charges Charges, AggregationField<Charges> field){
if(ChargeDetailAggregationField.TYPE1 == field){
Type1 += Charges.getType1();
}else if(ChargeDetailAggregationField.TYPE2 == field){
Type2 += Charges.getType2();
}else if(ChargeDetailAggregationField.TYPE3 == field){
Type3 += Charges.getType3();
}
}
}
public class CommonAggregator<T extends Aggregatable<T>> {
private static enum AggregationOperation {
SUM, MAX, MIN, AVG;
}
private AggregationField<T>[] fieldsForSum;
private AggregationField<T>[] fieldsForMax;
private AggregationField<T>[] fieldsForMin;
//private AggregationField groupByField = null;
public CommonAggregator<T> sum(AggregationField<T>... fields){
this.fieldsForSum = fields;
return this;
}
public CommonAggregator<T> max(AggregationField<T>... fields){
this.fieldsForMax = fields;
return this;
}
public CommonAggregator<T> min(AggregationField<T>... fields){
this.fieldsForMin = fields;
return this;
}
private <T> void performOperation(AggregationOperation op,AggregatedResponse<T> aggregatedDetails,List<T> aggregatables,AggregationField<T>... fields){
Aggregatable<T> aggregatedResponse = (Aggregatable<T>) getNewInstance();
T response = null;
for(AggregationField<T> field:fields){
if(op == AggregationOperation.MAX){
response = max(field,aggregatables);//Compilation Err
}else if(op == AggregationOperation.MIN){
response = min(field,aggregatables);//Compilation Err
}else if(op == AggregationOperation.SUM){
response = sum(field,aggregatables);//Compilation Err
}
aggregatedResponse.setFieldValue(response, field);
if(op == AggregationOperation.MAX){
aggregatedDetails.setMax(aggregatedResponse);
}else if(op == AggregationOperation.MIN){
aggregatedDetails.setMin(aggregatedResponse);
}else if(op == AggregationOperation.SUM){
aggregatedDetails.setSum(aggregatedResponse);
}
}
}
private T max(AggregationField<T> field,List<T> aggregatables){
CommonComparator<T> comparator = new CommonComparator<T>(SortOrder.ASCENDING, field);
return Collections.max(aggregatables, comparator);
}
private T min(AggregationField<T> field,List<T> aggregatables){
CommonComparator<T> comparator = new CommonComparator<T>(SortOrder.ASCENDING, field);
return Collections.min(aggregatables, comparator);
}
private T sum(AggregationField<T> field,List<T> listOfAggregatables){
T aggregatable = listOfAggregatables.get(0);
for(T response :listOfAggregatables.subList(1, listOfAggregatables.size())){
aggregatable.addFieldValue(response, field);
}
return aggregatable;
}
public AggregatedResponse<T> aggregate(List<T> aggregatables){
AggregatedResponse<T> aggregatedDetails = new AggregatedResponse<T>();
if(fieldsForMax != null)
performOperation(AggregationOperation.MAX,aggregatedDetails,aggregatables,fieldsForMax);
if(fieldsForMin != null)
performOperation(AggregationOperation.MIN,aggregatedDetails,aggregatables,fieldsForMin);
if(fieldsForSum != null)
performOperation(AggregationOperation.SUM,aggregatedDetails,aggregatables,fieldsForSum);
return aggregatedDetails;
}
public <E> Map<E,List<T>> groupBy(AggregationField<T> fieldName, List<T> listOfAggregatable){
Map<E,List<T>> groupedList = new HashMap<E,List<T>>();
for(T t:listOfAggregatable){
List<T> subList = null;
E fieldValue = (E)t.getFieldValue(fieldName);
if((subList = groupedList.get(fieldValue)) != null){
subList.add(t);
}else{
subList = new ArrayList<T>();
subList.add(t);
groupedList.put(fieldValue,subList);
}
}
return groupedList;
}
public <E> Map<E,AggregatedResponse<T>> groupByWithAggregation(AggregationField<T> fieldName, List<T> listOfAggregatable){
//groupByField = fieldName;
Map<E, List<T>> groupedByList = groupBy(fieldName, listOfAggregatable);
Map<E,AggregatedResponse<T>> mapOfAggregatedDetails = new HashMap<E, AggregatedResponse<T>>();
for(E key : groupedByList.keySet()){
mapOfAggregatedDetails.put(key, aggregate(groupedByList.get(key)));
}
return mapOfAggregatedDetails;
}
:
}
This is not the complete code.
Here, AggregationField tells which field of Aggregatable class has to be aggregated.
Problem:
I have facing followinf error when calling max(), min(), and sum() in performOperation()
The method max(AggregationField< T>, List< T>) in the type CommonAggregator< T> is not applicable for the arguments (AggregationField< T>, List< T>)
Edit: I have modified the original code and question after #Mikhail suggestion.
I am not good in generics. And I guess I am doing something wrong in generics only.
public interface AggregationField <T>
{
// ...
}
public interface Aggregatable <T>
{
public void addFieldValue (T t, AggregationField <T> field);
}
public class Charges implements Aggregatable <Charges>
{
#Override
public void addFieldValue (
Charges Charges, AggregationField <Charges> field)
{
// ...
}
}
public class CommonAggregator <T extends Aggregatable <T>> {
private T sum (
AggregationField <T> field,
List <? extends T> listOfAggregatables)
{
T aggregatable = listOfAggregatables.get(0);
for (T response: listOfAggregatables.subList (1, listOfAggregatables.size())){
aggregatable.addFieldValue(response, field);
}
return aggregatable;
}
}