> IOException.
Rather than which alternative? There are tonnes of IO errors that can occur and are captured in Java that you may not ever even consider. IO is exceptionally tough and programmers should be aware that there could be 1 of a possible million reasons for failure, even if they don't care exactly why.
It's not a massive ask either:
try{
/* Perform some IO that 99.999999% of the time */
}catch(Exception e){
/* Something went wrong and we don't know the state of the disk */
}
Personally I believe applications should have their own wrapper around IO and handle it accordingly. I.e. not caring, re-trying, show-stopper, etc, etc.A lot of code I've written will have the following if I really don't care if it works or not:
try{
/* IO code */
}catch(Exception e){
/* Do nothing */ // <-- Let others know that this was on purpose
}That said, even if typed checked exception handling is important...it quickly becomes untenable.
Thing thing = create();
try {
...
} finally {
cleanup(thing);
}
You might try to abstract this withThing(thing -> ...)
But what if "..." potentially throws IOException? What if it potentially throws IOException or AWTException?You have to give up all your exception type safety to make an abstraction like withThing. It's just not composable.
interface ThrowingConsumer<T, E extends Exception> {
void accept(T value) throws E;
}
// ...
<E extends Exception> void withThing(ThrowingConsumer<Thing,E> callback) throws E { ... }
The problems are that (a) you need to add an extra type parameter for exceptions all over the place, (b) exception unification (sum type) doesn't work generically - you can say E1 | E2 in a catch block but it's not a real type, and (c) it composes poorly with existing libraries that expect Consumer<T> throughout.For one level deep callbacks (for resource handling and the like) it works reasonably well though.
try {
...
} except (IOException|NoSuchElementException e) {
throw new RuntimeException(e);
}
If you decide you don't want checked exceptions in your code, wrapping the exceptions at the boundaries is not a huge deal.Java was designed for "production" software, which in the late 90s and early 00s meant software that ran for long periods of time as a server, servicing thousands of mission-critical customers. Crashing was not acceptable behavior for that. But now a lot of production software often requires a lot of ancillary one-off tasks that are just done by the developers - testing, data-munging, exploratory code, migrations, demos, etc. That's a very different environment from where you code to spec, the spec never changes, and once the software is done it's supposed to run for years without crashing.
But some Java SDK exceptions drive me crazy. For example `URL.parse("http://example.com")` over hard-coded strings that I know 100% won't throw exceptions, but I still to catch 3 of them.
https://docs.oracle.com/javase/7/docs/api/java/net/URI.html#...
After URI appeared in 1.4, the only reason to use URL was to create a URLConnection from a URI. Since openConnection() throws IOException, it's not a big deal that toURL() throws a MalformedURLException - just catch it along with all the other IOExceptions.
Since 11, there's no reason to use URL at all, because you can use HttpClient to actually do HTTP.
The JDK is filled with a ton of dumb "once upon a time we thought this was okay..." things that don't properly encode the correct modern idiom
Newer languages definitely have an advantage here, in that they haven't been around long enough for people to have figured out which bits of them are dumb.
Would probably have gone unoticed at most AWS/GCP/Azure shops today.
At that point, URLs resolving to the same IP were considered equal, even if the host names were different. Even now, there’s no real difference between say “http://example.com” and “http://example.com:80”; it would have been reasonable to consider these equal.
google.com and maps.google.com resolve to the same address but are not equivalent. Even if your example and many others are equivalent, there are many that aren't. A general library should not make assumptions like that, unless they hold 100% of the time.
The spec I am mostly familiar with is, however, rather new, and whatever standard controlled URLs at the time (if any) might have made more assumptions.
Helluva way to thread starve your application. :(
https://docs.oracle.com/javase/7/docs/api/java/net/URL.html#...
How would you implement them?
One example of this: there is no way to encode the type of a checked exception in a generic. I would like to be able to express something like the following:
interface ExceptionHandler<E extends Exception, S, T> {
T wrap(
Function<S, T, throwing E> fn,
Function<E, T> exceptionMapper
);
}
But there is no way to express that 'throwing E' part. The type of a checked exception is firmly embedded in the interface. So I can't supply, say, an IO method as a Function<S, T> parameter, since the IOException causes a mismatch, and I'd have to write a handler specifically for methods that throw IOException, another for those that throw TimeoutException, a third for those that throw IOException AND TimeoutException, etc; a fairly fruitless goal without automatic code generation. interface FunctionThrows1<S, T, E1 extends Throwable> {
T apply(S in) throws E1
}
interface FunctionThrows2<S, T, E1 extends Throwable, E2 extends Throwable> {
T apply(S in) throws E1, E2
}
...
But you still have to write one version of your method for each arity of throwing that you want to be able to wrap.That's partly because there's another problem. Potentially, you could have streams of the form, say, `...map(A::foo).map(A::bar).map(A::baz).collect(...)`, and each of foo, bar, baz adds another exception type to the set that can be thrown by collect.
In practice, you would end up with the stream throwing an exception of some general type (in 95% of cases, IOException!), but you could still write specific catch blocks for each possible subtype.
I wish Kotlin had, instead of ignoring the existence of checked exceptions, instead translated them into part of the return type. I use Kotlin a lot these days, and one annoyance for me is dealing with code that throws exceptions. They fixed the annoying "(almost) anything can be null" problem of java and replaced it with an equivalent problem. Why can't nullability and failure results both be part of the static type?
(The workaround it to manually use an Either type yourself, but it doesn't help you with calling anyone else's code, since virtually everything throws exceptions on failure.)
How do you declare a method that generically takes a function that in turn takes a K, returns a V, and can throw whatever checked exceptions it wants, and you'll rethrow them? Last I checked, this wasn't possible in Java.
If checked exceptions were instead replaced with sum type return values, then it becomes trivial.
@FunctionalInterface
interface ThrowingFunction<T, R, E extends Exception> {
R apply(T argument) throws E;
}
static <T, R, E extends Exception> R apply(T argument, ThrowingFunction<T, R, E> function) throws E {
return function.apply(argument);
}
But not catch and rethrow it, because throw and catch aren't generic, and the type parameter isn't reified. You can emulate reified generics with the usual trick of passing a Class object: static <T, R, E extends Exception> R apply(T argument, ThrowingFunction<T, R, E> function, Class<E> witness) throws E {
try {
return function.apply(argument);
} catch (Exception e) {
throw witness.cast(e);
}
}
But this is pretty horrid.This thread stemmed from https://news.ycombinator.com/item?id=21808522 :
> Java's implementation of checked exceptions looks pretty minimal and sound to me. > > How would you implement them?
Java's checked exceptions aren't "minimal" and are problematic because they behave unlike the rest of the types system. I'm not saying that they should have ditched checked exceptions and left it at that, though. What they should have done was make sum types a first-class part of the type system, and then checked exceptions become redundant.
In languages that actually have general sum types rather than special-casing the way Java's exceptions do, you can get multiple types of errors with an Either by making a union of the different types of errors. You can alternatively use an N-way sum type as your return value in place of the 2-way Either.
Kotlin has sealed classes, which can be used to create a sum type, and you can even do the same in Java with enough boilerplate, but in either of those languages you run into another problem if you try this: it doesn't interoperate well with the vast majority of existing library code that throws exceptions.
1) They follow a different code path (which is what makes them a superior way to handle errors over return values).
2) They cannot be emulated by sum types when all you want to do is not handle the exception and let it bubble up the stack frames.
It also allows the exceptional behavior to be defined and to be controlled by the developer, one thing that you don't really have today when throwing RuntimeExceptions with say Stream APIs.
Checked exceptions were an excellent hack, but an ergonomic failure, despite the almost-pattern-matching of the `catch` clauses.
in reality though aside from the rock/hardplace of language conservatism/fanatical back-compat i have to imagine a real reason modern java has yet to fully embrace Optional/Either over null/exceptions is lack of value types - yeah escape analysis lets you ~mostly~ not have to worry about the IdentityObjects you're returning everywhere, until you hit something it can't/won't inline or try to stuff those Optionals on heap and wind up making our already painful pointer chasing situation ten times worse. value types are also well on their way via project valhalla but have yet to actually land.
maybe ironically one of the things i recently got bitten by was the fact that MethodHandle .invoke* methods throw Throwable when used directly in plain java - the very machinery that enables the efficient composition of these kinds of functional programming styles is gated behind having to catch literally anything in the language itself. i guess the recurring theme here is java putting the horse before the cart, and that's frankly my favorite thing about the ecosystem.
Either/Result has often been found to be untenable without these mechanisms, as well. Rust first added the try! macro and then the ? operator because it was so tedious to deal with raw Results otherwise.