Sneakyfun
Java utility-library that makes lambda expressions look elegant via disabling enforcement of checked exceptions handling
Install / Use
/learn @ciechanowiec/SneakyfunREADME
[.text-justify] = 🥳 Sneaky Fun :reproducible: :doctype: article :author: Herman Ciechanowiec :email: herman@ciechanowiec.eu :chapter-signifier: :sectnums: :sectnumlevels: 5 :sectanchors: :toc: left :toclevels: 5 :icons: font // Docinfo is used for foldable TOC. // -> For full usage example see https://github.com/remkop/picocli :docinfo: shared,private :linkcss: :stylesdir: https://www.ciechanowiec.eu/linux_mantra/ :stylesheet: adoc-css-style.css :favicon: favicon.png
== Overview
Sneaky Fun is a Java utility-library that makes lambda expressions look elegant via disabling enforcement of checked exceptions handling. To achieve that, the library provides enhanced analogues of all 44 functional interfaces from the java.util.function package and of the Runnable. Those analogues could be used as a replacement everywhere, where usage of their counterparts is expected.
Sneaky Fun library is lightweight (~50 KB), doesn't have any dependencies and is published in Maven Central Repository.
== Usage
=== Without Sneaky Fun & Unsightly
Functional methods of functional interfaces from the java.util.function package and of the Runnable don't have a throws clause specified. Therefore, implementations of those methods cannot propagate checked exceptions down the call stack and those exceptions must be handled within the implementation via a try-catch block. This leads to boilerplate and unsightly code:
[source, java]
public static void main(String[] args) { Function<String, URI> toURI = input -> { <1> try { return new URI(input); <2> } catch (URISyntaxException exception) { log.error("Unable to create a URI", exception); return null; <3> } }; URI uri = toURI.apply("google.com"); }
<1> Here implementation of a lambda expression starts.
<2> Inside the lambda expression, a public URI(String str) constructor is used, which has a throws clause with a checked URISyntaxException. Since this is a checked exception within a lambda expression related to a functional interface from the java.util.function package, it must be handled within the same lambda expression, which causes boilerplate and unsightly code.
<3> In the catch block, the caught checked URISyntaxException cannot be rethrown, since the execution occurs within a lambda expression related to a functional interface from the java.util.function package. Therefore, in most cases a null will be returned, which, in turn, can lead to NullPointerException++s++.
=== With Sneaky Fun & Elegantly
Sneaky Fun library disables enforcement of checked exceptions handling within lambda expressions related to functional interfaces from the java.util.function package and of the Runnable so that those exceptions can be thrown within a lambda expression and propagated down the call stack. To achieve that, simply wrap implementation of a lambda expression into a static sneaky(...) method declared in the respective functional interface from Sneaky Fun library.
For example, in order to rewrite the above example with a Function, use a static sneaky(...) method declared in a SneakyFunction:
[source, java]
import static eu.ciechanowiec.sneakyfun.SneakyFunction.sneaky;
public static void main(String[] args) { SneakyFunction<String, URI, URISyntaxException> toURI = URI::new; <1> Function<String, URI> toURIAdapter = sneaky(toURI); <2> URI uri = toURIAdapter.apply("google.com"); <3> }
<1> Implementation of a lambda expression assigned to a SneakyFunction, being an analogue of a usual Function. It uses public URI(String str) constructor, which has a throws clause with a checked URISyntaxException. However, thanks to Sneaky Fun library, usage of that constructor doesn't enforce checked exception handling, hence is unsafe.
<2> A static sneaky(...) method declared in a SneakyFunction wraps the implementation of a lambda expression into an adapter, which is a usual Function. Now, the unsafe usage of public URI(String str) constructor, which has a throws clause with a checked URISyntaxException, is hidden inside the adapter being a usual Function.
<3> Execution of a functional method of the adapter being a usual Function can be performed normally and no prior checked exception handling was enforced.
=== In Streams
Sneaky Fun library is particularly useful in streams.
For example, this is how conversion of raw URIs to pure URIs might look like in usual Java code:
[source, java]
public static void main(String[] args) { List<String> rawURIs = List.of("google.com", "ciechanowiec.eu"); List<URI> pureURIs = rawURIs.stream() .map(rawURI -> { try { return new URI(rawURI); } catch (URISyntaxException exception) { log.error("Unable to create a URI", exception); return null; } }) .toList(); }
With Sneaky Fun library the code above can be significantly simplified and prettified:
[source, java]
import static eu.ciechanowiec.sneakyfun.SneakyFunction.sneaky;
public static void main(String[] args) { List<String> rawURIs = List.of("google.com", "ciechanowiec.eu"); List<URI> pureURIs = rawURIs.stream() .map(sneaky(URI::new)) .toList(); }
== Maven Dependency To use Sneaky Fun library, the following Maven dependency can be added to a project:
[source, xml]
<dependency> <groupId>eu.ciechanowiec</groupId> <artifactId>sneakyfun</artifactId> <version>1.0.0</version> </dependency> ----== API Documentation Full API documentation of Sneaky Fun library can be found at this link: https://www.ciechanowiec.eu/sneakyfun.
== OSGi Sneaky Fun library is built as an OSGi bundle, therefore it can be used in OSGi environment. Among others, it can be used within Adobe Experience Manager (AEM).
== Internal Mechanism This section describes the principles upon which the internal mechanism of Sneaky Fun library is based.
=== Sneaky Type Inference
During type inference, type variables denoted in a throws clause are treated as identifiers of an unchecked RuntimeException, even if the type variable actually identifies a checked Exception (see https://docs.oracle.com/javase/specs/jls/se17/html/jls-18.html[Chapter 18. Type Inference] of Java Language Specification). This allows to develop a sneakilyThrow(...) method that can throw a checked Exception as if it was an unchecked RuntimeException and to omit enforcement of checked exceptions handling:
[source, java]
class Thrower {
static<X extends Exception, T> T sneakilyThrow(Exception exceptionToThrow) throws X { <1>
throw (X) exceptionToThrow;
}
public static void main(String[] args) {
sneakilyThrow(new IOException()); <2>
}
}
<1> The type variable X in the throws clause identifies a checked Exception and any type that extends a checked Exception, i.a. an unchecked RuntimeException. However, regardless of what actual type the X type variable identifies, during type inference the X type variable will be treated as an unchecked RuntimeException.
<2> In this particular case, the actual type identified by the type variable X in the throws clause of the sneakilyThrow(...) method is a checked IOException, which normally must be handled. However, due to type inference specifics, that type variable is treated as if it was an unchecked RuntimeException, although actually that's not true. Therefore, regardless of the fact that in this particular case the sneakilyThrow(...) method throws a checked IOException, handling of that exception isn't enforced, because it is treated as an unchecked RuntimeException.
=== Sneaky Functional Interfaces
As mentioned above, functional methods of functional interfaces from the java.util.function package and of the Runnable don't have a throws clause specified. Therefore, implementations of those methods cannot propagate checked exceptions down the call stack and those exceptions must be handled within the implementation via a try-catch block. This leads to boilerplate and unsightly code.
Sneaky Fun library bypasses the enforcement of checked exceptions handling within lambda expressions via leveraging type inference specifics described in the section above. It is done in the following way:
[upperalpha]
. Sneaky Fun library provides analogues (sneaky interfaces) of all 44 functional interfaces from the java.util.function package and of the Runnable (original interfaces).
. Sneaky interfaces are named exactly as their counterparts, but have a word Sneaky prepended. For example, for the original interface Function, there is an analogous sneaky interface named SneakyFunction.
. Contrary to the original interfaces, declaration of functional methods of the sneaky interfaces all have a throws clause specified, that denotes a checked Exception and any type that extends a checked Exception. Therefore, implementations of functional methods of sneaky interfaces can throw and propagate checked exceptions down the call stack.
+
.Functional method declaration of a Function:
+
[source, java]
@FunctionalInterface public interface Function<T, R> {
R apply(T t);
}
.Functional method declaration of a SneakyFunction:
+
[source, java]
@FunctionalInterface public interface SneakyFunction<T, R, X extends Exception> {
R apply(T input) throws X;
}
.Usage comparison: + [source, java]
public static void main(String[] args) { Function<String, URI> originalToURI = URI::new; <1> S
