Generated from Example_Defaults_Test.java at 3.0.0 2020-08-11T17:36:18Z
Default and static methods
Abstract
Basic introduction (by example) to default and static methods available in functional interfaces in this library.
General comments
- Not all functional interfaces have exactly the same default and static methods.
- a matter of difference of interface type (e.g. consumer vs predicate),
- a matter of availability of other specialised interfaces (especially with growing number of arguments).
- In examples that follow following other features of the library are used:
The LFunction.apply() is actually a default method.
Because library interfaces inherit from JRE interfaces, and in addition to that library interfaces must handle checked exception, all the methods 'accept', 'get', 'apply', and 'test' are already implemented and the actual methods to implement are having almost the same name but only with additional suffix 'X". This suffix comes from the fact that the method is allowed to throw other than runtime exceptions.
Example:
@Test
public void defaultX() {
LFunction<String, String> f1 = new LFunction<String, String>() {
@Override public String applyX(String a) throws Throwable {
return a;
}
};
}
Static wrapper
Obviously, you are not required to use those static wrapper methods, they only exists to help in certain circumstances:
- specialize interface (either because of hidden type requirement, or to actually help compiler to guess the interface)
- exception handling is needed for standard JRE functional interface (and tidy code is welcomed)
Example:
@Test
public void staticWrappers() {
LFunction<String, String> f1 = unaryOp(s -> s); // specialize actual implementation of interface
Stream.of(new File("")).map(
func(File::getCanonicalPath) // wrap lambda to handle exceptions in places where JRE functional interface is used
);
}
Another static wrapper is for JRE functions. Every interface in this library has this method if it inherits from actual JRE interface.
@Test
public void wrapJre() {
Function<String, String> f1 = s-> s;
LFunction<String, String> f2 = LFunction.wrap(f1);
}
Generated from Example_Defaults_Test.java at 3.0.0 2020-08-11T17:36:18Z
Tuple call
Since there is a tuple interface representing all domains of all functions (mentioned in Overview), it is natural that there is a default method dedicated to call function with such tuple.
Example:
@Test
public void tupleCall() {
LIntBinaryOperator f1 = Math::addExact;
int actual = f1.tupleApplyAsInt(LIntPair.of(2, 5));
attest(actual).must2Ex(Be::equalEx, 7);
}
Multiple handling possibilities for exceptions.
Example: TODO move to {@link Example_ExceptionHandling_Test}
@Test
public void handlingCalls() {
LBinaryOperator<Integer> badFunction = Math::addExact;
attestBinaryOp(badFunction)
.doesApply(2, 5).asEqualTo(7)
.doesApply(null, 7).withException(e -> e.isInstanceOf(NullPointerException.class));
}
Non null result of a function.
Default method -nonNull*()- (e.g. -nonNullApply-), that execute call to a function with validation for non-null result. Default method -nonNullable()- that returns implementation of a function that does that on every call.
Example:
@Test
public void nonNullApply() {
LUnaryOperator<Integer> badFunction = LUnaryOperator.identity();
attestUnaryOp(badFunction)
.doesApply(2).asEqualTo(2)
.doesApply(null).asEqualTo(null);
attestThatThrownBy(
() -> badFunction.nonNullApply(null)
).must2Ex(Be::instanceOfEx, RuntimeException.class)
.mustEx(P.haveEx(Throwable::getMessage, P::containEx, "Evaluated value by nonNullApply() method cannot be null"));
attestUnaryOp(badFunction.nonNullable())
.doesApply(2).asEqualTo(2)
.doesApply(null)
.withException(e -> e
.isInstanceOf(NullPointerException.class)
.hasMessageContaining("Evaluated value by nonNullApply() method cannot be null")
);
}
fromTo, fromTill, times
Best to present example:
@Test
public void fromTo() {
LIntConsumer.fromTo(1, 10, x -> System.out.println(x));
LIntConsumer.fromTill(1, 10, System.out::println);
LConsumer.fromTo(1, 10, System.out, o -> o.println("tick"));
LObjIntConsumer.fromTo(1, 10, System.out, (printStream, x) -> printStream.println(x));
LObjIntConsumer.fromTo(1, 10, System.out, PrintStream::println);
LObjIntConsumer.times(10, System.out, (o, i) -> o.println(i));
LObjIntConsumer.times(10, System.out, PrintStream::println);
}
untyped(), cast()
Both methods have the same purpose - sometimes it is faster and actually more readable to make compiler just stop complaining, than fully declare the whole set of generic parameters.
It is not recommended to use those methods freely.
@Test
public void untyped() {
List<LConsumer<Integer>> list = new ArrayList<>();
LConsumer<?> f1 = LConsumer::doNothing;
list.add(f1.untyped()); // untyped changes compilation error to warning
list.add(f1.cast()); // cast make sure there are no complains
list.add(LConsumer.<Integer>cast(f1)); // static version of the cast()
}
beforeDo(), afterDo()
Both methods allow to 'visit' actual values of either domain or codomain on each call of newly created function.
@Test
public void beforeAfter() {
test().given(() -> new Object() {
StringBuilder sb = new StringBuilder();
LBinaryOperator<Integer> func = Math::addExact;
}).precondition(state -> {
// the assignment is required
state.func = state.func
.beforeDo((i1, i2) -> state.sb.append(i1).append("+").append(i2))
.afterDo(r -> state.sb.append("=").append(r));
}).when(state -> {
state.func.apply(3, 4);
}).then(state -> {
attest(state.sb.toString()).must2Ex(Be::equalEx, "3+4=7");
});
}
capture
'Capture' the fixed arguments and returns the supplier that returns the result of a call to original function. The function is called each time.
@Test
public void capture() {
LUnaryOperator<String> f1 = s -> s;
LSupplier<String> f2 = f1.capture("345");
attestSup(f2).doesGet().asEqualTo("345");
}
recursive()
In cases where needed, recursive method allows to define recursive implementation of a function (by declaring higher order function).
@Test
public void recursive() {
StringBuilder sb = new StringBuilder();
LLongUnaryOperator factorial = LLongUnaryOperator.recursive(f -> n -> (n <= 1) ? 1 : n * f.applyAsLong(n - 1L));
attestLongUnaryOp(factorial)
.doesApplyAsLong(0).asEqualTo(1L)
.doesApplyAsLong(1).asEqualTo(1L)
.doesApplyAsLong(2).asEqualTo(2L)
.doesApplyAsLong(3).asEqualTo(6L);
}
memento(), deltaOf()
Within this library, 'Memento' is a derivative function, that keeps state. Specifically two values: last value returned by original function and last value returned by derivative function. This allows for creating functions that calculate for example: delta or sum.
The limitations/complications are obvious: - it is inherently not threadsafe - it keeps references to objects (if codomain is object)
@Test
public void memento() {
LLongUnaryOperator originalFunction = i -> i; // lets keep it simple
LLongUnaryOperator.M sum = LLongUnaryOperator.memento(0, 0, originalFunction, (lastR_bis, lastR, r) -> lastR_bis + r);
LLongUnaryOperator.M delta = LLongUnaryOperator.memento(0, 0, originalFunction, (lastR_bis, lastR, r) -> r - lastR);
attestLongUnaryOp(sum)
.doesApplyAsLong(0).asEqualTo(0L)
.doesApplyAsLong(2).asEqualTo(2L)
.doesApplyAsLong(3).asEqualTo(5L)
.doesApplyAsLong(50).asEqualTo(55L);
attestLongUnaryOp(delta)
.doesApplyAsLong(0).asEqualTo(0L)
.doesApplyAsLong(2).asEqualTo(2L)
.doesApplyAsLong(3).asEqualTo(1L)
.doesApplyAsLong(50).asEqualTo(47L)
.doesApplyAsLong(40).asEqualTo(-10L);
}
An example that actually could be used more frequently is:
@Test
public void timeDelta() {
LLongSupplier.M timeDelta = LLongSupplier.deltaOf(System::currentTimeMillis);
LLongConsumer.tryAccept(200, Thread::sleep);
long elapsedTime = timeDelta.getAsLong();
attest(elapsedTime).must3(Be::inRange, 150, 500, "Elapsed time must be between 150 and 500 ms !"); // sleep() CPU scheduling is not precise
}
Generated from Example_Defaults_Test.java at 3.0.0 2020-08-11T17:36:18Z
compose(), then(), thenConsume(), thenToInt(), ...
Methods like compose() and then() are analogous to JRE's Function.compose() and Function.andThen() + few more.