-
-
Save stefanofago73/7fba3a60532507378ea316c0c1d57a27 to your computer and use it in GitHub Desktop.
| import java.lang.invoke.MethodHandle; | |
| import java.lang.invoke.MethodHandles; | |
| import java.lang.invoke.MethodType; | |
| import java.util.function.Function; | |
| /** | |
| * | |
| * This class was triggered form readyn the post: https://blog.frankel.ch/checked-exceptions-lambdas/ | |
| * Can be done better and works for unary invocation | |
| * | |
| **/ | |
| public final class SneakyFns { | |
| private SneakyFns() { | |
| } | |
| @SuppressWarnings("unchecked") | |
| private static <E extends Throwable> RuntimeException sneakyThrow(Throwable t) throws E { | |
| throw (E) t; | |
| } | |
| @SuppressWarnings("unused") | |
| private static Object callSneaky(MethodHandle mh, Object arg) { | |
| try { | |
| return mh.invoke(arg); | |
| } catch (Throwable t) { | |
| throw sneakyThrow(t); | |
| } | |
| } | |
| @SuppressWarnings("unchecked") | |
| public static <T, R> Function<T, R> asFunction( | |
| Object receiver, | |
| Class<?> declaringClass, | |
| String methodName, | |
| Class<R> returnType, | |
| Class<T> argType) { | |
| try { | |
| MethodHandles.Lookup lookup = MethodHandles.lookup(); | |
| MethodHandle mh = lookup.findVirtual(declaringClass, methodName, MethodType.methodType(returnType, argType)) | |
| .bindTo(receiver) // now (T) -> R | |
| .asType(MethodType.methodType(Object.class, Object.class)); // erase | |
| MethodHandle impl = lookup.findStatic(SneakyFns.class, "callSneaky", | |
| MethodType.methodType(Object.class, MethodHandle.class, Object.class)); | |
| var cs = java.lang.invoke.LambdaMetafactory.metafactory(lookup, "apply", | |
| MethodType.methodType(Function.class, MethodHandle.class), // capture mh | |
| MethodType.methodType(Object.class, Object.class), // erased SAM | |
| impl, // (mh,obj)->obj | |
| MethodType.methodType(Object.class, Object.class) // instantiated SAM (erased) | |
| ); | |
| Function<Object, Object> f = (Function<Object, Object>) cs.getTarget().invokeExact(mh); | |
| return (Function<T, R>) f; | |
| } catch (RuntimeException e) { | |
| throw e; | |
| } catch (Throwable t) { | |
| throw new IllegalStateException("Failed to build Function for " + declaringClass + "::" + methodName, t); | |
| } | |
| } | |
| } | |
| // | |
| // | |
| // EXAMPLE | |
| // | |
| // | |
| import java.io.IOException; | |
| import java.util.List; | |
| import java.util.function.Function; | |
| public class Foo { | |
| public String throwing(String input) throws IOException { | |
| return input; | |
| } | |
| public static void main(String[] args) throws Throwable { | |
| // can't compile cause checked exception | |
| // var foo = new Foo(); | |
| // List.of("One", "Two").stream() | |
| // .map(string -> foo.throwing(string)) | |
| // .toList(); | |
| Function<String, String> asFunction = | |
| SneakyFns | |
| .asFunction(foo, | |
| Foo.class, | |
| "throwing", | |
| String.class, | |
| String.class); | |
| var list = List.of("One", "Two") | |
| .stream() | |
| .map(asFunction) | |
| .toList(); | |
| System.out.println(list); | |
| } | |
| } |
I'll try to answer briefly, then elaborate on this later.
In short: I presented an educational example that offers an alternative to the topic, but it's not necessarily the optimal solution. It also provided a glimpse into aspects normally hidden in compilation mechanisms.
Some additional considerations:
- As many libraries do, it's possible to define ad hoc functional interfaces that allow "automatic" conversion into lambdas or reference methods; it's necessary to define reference interfaces because Java is strongly typed and because the JDK doesn't provide generic mappings for all cases, and normal functional interfaces aren't usable.
- Another point is that I don't consider MethodHandle and VarHandle elements of Reflection (given their implementation). The use of Reflection itself may lead to other answers.
At this point, there are other considerations that I'll state as purely personal and therefore opinionated:
-
Lambdas must not use methods with exceptions, because from a FP perspective, they should be free of side effects like exceptions. However, there are practical needs, and the pragmatic and effective response would be to have a method that doesn't throw exceptions using the one that does. This situation has different variations, but it's not convenient, requires work, and sometimes requires actual design decisions due to its implications.
-
The example shown isn't far from what can happen at the compilation level, given that lambdas and reference methods are the result of translations/substitutions/code generation. It's interesting what can be done within the context of the InvokeDynamic specification APIs, which demonstrate, despite its limitations, that Java is an evolvable language (after all, other JVM-based languages were made possible precisely thanks to these capabilities, in addition to the presence of bytecode). Funny the Sneaky Throw part! ;-)
-
As shown in the article, there are already many frameworks that have "solved" the pragmatic problem, but another issue remains: the use and design in the presence of exceptions, the use/design decisions of interfaces, and the use of overriding, which allows changing a method's signature. This also leads us to think about how much it might be worth evaluating some solutions: just looking at Deus-Ex-Java lib, it might come to mind whether such technically fascinating design and implementation are valuable in day-to-day life compared to more banal practical rules that can educate junior devs.
There are other aspects, but I tried to be concise to express a well-rounded view, hoping to also add value!
Of course, a big thank you for the post and the comment!
Cool!
Can't you pass the throwing function as a functional interface if you put it in a functional interface, instead of doing reflection?