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.
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:
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");
}
-
Here implementation of a lambda expression starts.
-
Inside the lambda expression, a
public URI(String str)
constructor is used, which has athrows
clause with a checkedURISyntaxException
. Since this is a checked exception within a lambda expression related to a functional interface from thejava.util.function
package, it must be handled within the same lambda expression, which causes boilerplate and unsightly code. -
In the
catch
block, the caught checkedURISyntaxException
cannot be rethrown, since the execution occurs within a lambda expression related to a functional interface from thejava.util.function
package. Therefore, in most cases anull
will be returned, which, in turn, can lead toNullPointerException
s.
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
:
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)
}
-
Implementation of a lambda expression assigned to a
SneakyFunction
, being an analogue of a usualFunction
. It usespublic URI(String str)
constructor, which has athrows
clause with a checkedURISyntaxException
. However, thanks to Sneaky Fun library, usage of that constructor doesn’t enforce checked exception handling, hence is unsafe. -
A static
sneaky(…)
method declared in aSneakyFunction
wraps the implementation of a lambda expression into an adapter, which is a usualFunction
. Now, the unsafe usage ofpublic URI(String str)
constructor, which has athrows
clause with a checkedURISyntaxException
, is hidden inside the adapter being a usualFunction
. -
Execution of a functional method of the adapter being a usual
Function
can be performed normally and no prior checked exception handling was enforced.
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:
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:
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();
}
To use Sneaky Fun library, the following Maven dependency can be added to a project:
<dependency>
<groupId>eu.ciechanowiec</groupId>
<artifactId>sneakyfun</artifactId>
<version>1.0.0</version>
</dependency>
Full API documentation of Sneaky Fun library can be found at this link: https://www.ciechanowiec.eu/sneakyfun.
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).
This section describes the principles upon which the internal mechanism of Sneaky Fun library is based.
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 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:
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)
}
}
-
The type variable
X
in thethrows
clause identifies a checkedException
and any type that extends a checkedException
, i.a. an uncheckedRuntimeException
. However, regardless of what actual type theX
type variable identifies, during type inference theX
type variable will be treated as an uncheckedRuntimeException
. -
In this particular case, the actual type identified by the type variable
X
in thethrows
clause of thesneakilyThrow(…)
method is a checkedIOException
, which normally must be handled. However, due to type inference specifics, that type variable is treated as if it was an uncheckedRuntimeException
, although actually that’s not true. Therefore, regardless of the fact that in this particular case thesneakilyThrow(…)
method throws a checkedIOException
, handling of that exception isn’t enforced, because it is treated as an uncheckedRuntimeException
.
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:
-
Sneaky Fun library provides analogues (sneaky interfaces) of all 44 functional interfaces from the
java.util.function
package and of theRunnable
(original interfaces). -
Sneaky interfaces are named exactly as their counterparts, but have a word
Sneaky
prepended. For example, for the original interfaceFunction
, there is an analogous sneaky interface namedSneakyFunction
. -
Contrary to the original interfaces, declaration of functional methods of the sneaky interfaces all have a
throws
clause specified, that denotes a checkedException
and any type that extends a checkedException
. Therefore, implementations of functional methods of sneaky interfaces can throw and propagate checked exceptions down the call stack.Functional method declaration of aFunction
:@FunctionalInterface public interface Function<T, R> { R apply(T t); }
Functional method declaration of aSneakyFunction
:@FunctionalInterface public interface SneakyFunction<T, R, X extends Exception> { R apply(T input) throws X; }
Usage comparison:public static void main(String[] args) { Function<String, URI> originalToURI = URI::new; (1) SneakyFunction<String, URI, URISyntaxException> sneakyToURI = URI::new; (2) }
-
public URI(String str)
constructor is used, which has athrows
clause with a checkedURISyntaxException
. This expression will not compile, because the declaration of a functional method of aFunction
does not have athrows
clause specified. -
public URI(String str)
constructor is used, which has athrows
clause with a checkedURISyntaxException
. This expression will compile, because the declaration of a functional method of aSneakyFunction
has athrows
clause specified, that denotes a checkedException
and any type that extends a checkedException
.
-
-
Every sneaky interface has a static
sneaky(…)
method. It wraps the passed sneaky interface into an adapter, which is an analogous original interface. The method performs the wrapping by delegating the execution to the analogous original interface and rethrowing an exception via asneakilyThrow(…)
method in case such exception occurs. That way a checked exception becomes hidden inside the adapter. Execution of a functional method of the adapter can be performed normally and no prior checked exception handling is enforced, even if such exception might occur.sneaky(…)
method of aSneakyFunction
:@FunctionalInterface public interface SneakyFunction<T, R, X extends Exception> { R apply(T input) throws X; static<T, R, X extends Exception> Function<T, R> sneaky(SneakyFunction<T, R, X> function) { Objects.requireNonNull(function); return input -> { try { return function.apply(input); } catch (Exception exception) { return Thrower.sneakilyThrow(exception); } }; } }
The program is subject to MIT No Attribution License
Copyright © 2023 Herman Ciechanowiec
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so.
The Software is provided 'as is', without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. In no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the Software or the use or other dealings in the Software.