Closed cbrt-x closed 1 year ago
There's no way to stick an annotation on a lambda, and lombok can't invent new syntax (or, at least, we draw the line there).
See This Stack Overflow answer that goes into some depth.
I'd love to, and I completely understand the need for it, but, I don't see how to deliver on the premise. At best something hacky like:
import static lombok.Lombok.sneaky;
public class Example {
private final Runnable r = sneaky(IOException.class, () -> throw new IOException());
private final Runnable r2 = sneaky(() -> throw new IOException()); // sneakily throws all the things.
private final Runnable r3 = sneaky(IOException.class, SQLException.class, () -> throw new IOException());
}
I'm not sure that's really gonna work out; @SneakyThrows
doesn't see much use as is, and this is... kinda weird:
sneaky(() -> {... long lambda here .... }, IOException.class, SQLException.class);
veto-level unwieldy: Nobody wants to, or expects to, have to read the relevant exceptions at the end. It clashes with throws
(and @SneakyThrows
, the annotation), which go before the code. We can hack it (as long as it's syntactically legal we can do whatever we want if we put in the effort), but it's unnatural, and unless we also fix e.g. eclipse's auto-complete, the guidance provided by e.g. auto-complete dialogs is gonna suck. It's gonna suck in general, there is no java type that represents 'a lambda'. We'd have a signature of sneaky(Class<Throwable>, Class<Throwable>, Object lambda)
. If that's what it has to be, fine - but, it's not a good start.@SneakyThrows
-ed method is supposed to do, although they might be waylaid by the mistaken belief that what lombok does is impossible. We can't fix that. I'm far less certain that r = sneaky(() -> ...)
enjoys lombokless readability.javac
, and we have to sit in the middle of it. I expect it'll take a lot of effort to deliver on this proposal if we go this route (and what other route is there, really?) - and it'll be a major maintenance headache, with tons of corner cases that break and we need to fix, and more risk than most lombok features have for future breaks and conflicts with new java language features.
@SneakyThrows(IOException.class)
public void sneakyThrowsIO() { throw new IOException(); }
public void example() {
try {
sneakyThrowsIO();
} catch (IOException e) {}
}
does not compile - you cannot catch checked exceptions in java unless at least one line in the try
block is capable of theoretically (as per signature) throwing it. With @SneakyThrows
this isn't a particularly important problem - if you intended to write catch blocks you'd generally just write throws IOException
as normal. It does come up when you're using @SneakyThrows
to dance around requirements imposed by supertypes (e.g. you are writing an actual run()
method on a class that implements Runnable
), but this gets to a crucial distinction:
This is not what @SneakyThrows
is for!
We never said you should use it for dancing around throws restrictions, and we didn't design the feature for it, and we don't think throw-it-silently is the right solution if it comes up. @SneakyThrows
is for two things and only these two:
new String(byteArr, "UTF-8")
cannot possibly happen - UTF-8 is guaranteed. (This is less relevant now, but @SneakyThrows
was released when StandardCharsets.UTF_8
did not exist!)SQLException
and wrapping it in a ServletException
, as the servlet framework deals with all exceptions in the same way. Although, really, the best 'lombok solution' would be a hypothetical @CatchAndRewrap(catch = IOException.clas, as = ServletException.class)
instead of @SneakyThrows
, so this one is already dubious as is.I expect that folks using sneaky(() -> lambda)
will initially love it, but then realize that they can't do:
try {
listOfPaths.forEach(x -> myStringBuilder.append(Files.readFully(x));
} catch (IOException e) {
// do something
}
And that they'll start filing a few thousand issues. Unless you get me a notaried contract that says you're going to watch for em and explain the problem for the next 10 years in your free time, you're.. asking me to do that. And I don't wanna.
Specifically, most lambda-taking methods are just broken. For example, the .forEach
method of list is just badly written. It should have been:
package java.util.function;
public interface Consumer<T, EX extends Throwable> {
void accept(T t) throws EX;
}
....
package java.util;
public interface List<E> {
public <EX extends Throwable> void forEach(Consumer<E, EX> consumer) throws EX {
for (E e : this) consumer.accept(e);
}
}
Seriously - try that, it'll work great. You can throw whatever you please, it all inferences:
void example() throws IOException {
List<Path> files = ....;
StringBuilder out = new StringBuilder();
files.forEach(x -> out.append(Files.readString(x)));
}
that just.. works. fine.
Now, in practice, nobody does this, and it really infected java. For example, eclipse in particular, and javac and intellij to a lesser extent, get real confused when your code is broken, more than without the Throwable generics stuff. They start complaining about not being able to 'infer EX'. Also, of course, that EX IS part of the story. You can't write:
Consumer<String> printer = System.out::println;
You have to hack it with e.g. Consumer<String, RuntimeException> printer = ..;
instead. Nevertheless, certain APIs chose to do it this way, so by introducing this feature we're splitting the community in twain.
I'm tempted to throw in the towel on that rule as Team OpenJDK seems hell bent on breaking the community apart with stuff like this (latest example of this kind of careless disregard for the community: the getters produced by the record
feature do not use get
prefixes. It's not about what's prettiest. It's about the fact that e.g. LocalDate
has getYear()
and not year()
. It's about what makes existing stuff not feel at odds with a new feature. Generics got it right. record
messed it up). I'm willing to let this one go as a reason to veto the idea, but it's.. not helping.
I'm not quite ready to fully veto all takes, and perhaps I'm missing an interesting idea to mitigate some of these concerns, so I'll leave the issue open for a week or 3 to gather inputs. But then, unless someone comes up with one heck of an argument or a better implementation plan, I'm closing it - if a good idea comes along later I'm sure someone will open another issue for it.
PARKED: Awaiting amazing genius until Feb 20th, close afterwards.
First of all I want to thank you for the quick and elaborate answer! This makes a lot of sense and I understand the issues at hand. Waiting for another input is fine and I have no objections to the veto.
Besides that, thanks for teaching me a lot about this stuff!
It would seem more correct to do:
Runnable something = () ->
@SneakyThrows
methodThatThrowsSomething();
This could possibly add hidden wrapper code e.g. a created private static or instance method, as a non-throwing wrapper caller. @Getter and other Lombok annotations create methods, so I don't expect this to be be hard to do.
We shouldn't need hacks like @SneakyThrows at, all. Oracle must be very strongly persuaded that javac must provide a selective opt-out annotation for the nasty legacy design mistake called checked exception(s)! Appendable, AutoClosable, Closeable, and Files caused pointlessly ugly code too!
@rwperrott That is unfortunately not a place annotations can legally be written. It wouldn't even get past the syntax-parsing phase (and lombok has a rule: We don't want to get involved until the code as written has been parsed and is now a tree structure. We then jump in. This would require that we jump in earlier). Given that no ideas on how to get past the problems has popped up, I concur with @Jadefalke256 's choice to drop this issue for now. Maybe one day we'll figure out a neat way to do this :)
@Celestial-Gemstone , as a workaround you can consider using Sneaky Fun library: https://github.com/ciechanowiec/sneakyfun.
It does exactly what you need, i.e. disables enforcement of checked exceptions handling.
Description At the moment you can only annotate methods and constructors with this annotation. For functional interfaces you can only use it by expanding the lambda-body to it's full form.
This is how I would imagine it to look like. converted to java code this would look like
This is possible at the current version of Lombok by expanding the lambda body to
which is very verbose.
Target audience This would be a universal feature and would benefit a lot of people, as this is a very general problem. I personally encountered this quite a few times the last few weeks and thus thought it could be a nice addition.
Problems I'm unsure of how to handle it so that this can only be applied to lambda expressions.