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.