Skip to content

ciechanowiec/sneakyfun

Repository files navigation

🥳 Sneaky Fun

1. 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.

2. Usage

2.1. 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:

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 NullPointerExceptions.

2.2. 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:

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.

2.3. 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:

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();
}

3. Maven Dependency

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>

4. API Documentation

Full API documentation of Sneaky Fun library can be found at this link: https://www.ciechanowiec.eu/sneakyfun.

5. 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).

6. Internal Mechanism

This section describes the principles upon which the internal mechanism of Sneaky Fun library is based.

6.1. 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 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)
    }
}
  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.

6.2. 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:

  1. Sneaky Fun library provides analogues (sneaky interfaces) of all 44 functional interfaces from the java.util.function package and of the Runnable (original interfaces).

  2. 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.

  3. 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:
    @FunctionalInterface
    public interface Function<T, R> {
    
        R apply(T t);
    }
    Functional method declaration of a SneakyFunction:
    @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)
    }
    1. public URI(String str) constructor is used, which has a throws clause with a checked URISyntaxException. This expression will not compile, because the declaration of a functional method of a Function does not have a throws clause specified.

    2. public URI(String str) constructor is used, which has a throws clause with a checked URISyntaxException. This expression will compile, because the declaration of a functional method of a SneakyFunction has a throws clause specified, that denotes a checked Exception and any type that extends a checked Exception.

  4. 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 a sneakilyThrow(…​) 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 a SneakyFunction:
    @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);
                }
            };
        }
    }

7. License

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.

About

Java utility-library that makes lambda expressions look elegant via disabling enforcement of checked exceptions handling

Resources

License

Stars

Watchers

Forks