Throwing checked exceptions from lambdas – Functional style programming – extending API
196. Throwing checked exceptions from lambdas
Let’s suppose that we have the following lambda:
static void readFiles(List<Path> paths) {
paths.forEach(p -> {
try {
readFile(p);
} catch (IOException e) {
… // what can we throw here?
}
});
}
What can we throw in the catch block? Everybody knows the answer … we can throw an unchecked exception such as RuntimeException:
static void readFiles(List<Path> paths) {
paths.forEach(p -> {
try {
readFile(p);
} catch (IOException e) {
throw new RuntimeException(e);
}
});
}
Also, everybody knows that we cannot throw a checked exception such as IOException. The following snippet of code will not compile:
static void readFiles(List<Path> paths) {
paths.forEach(p -> {
try {
readFile(p);
} catch (IOException e) {
throw new IOException(e);
}
});
}
Can we change this rule? Can we come up with a hack that allows us to throw checked exceptions from lambdas? Short answer: sure, we can!Long answer: sure, we can if we simply hide the checked exception for the compiler as follows:
public final class Exceptions {
private Exceptions() {
throw new AssertionError(“Cannot be instantiated”);
}
public static void throwChecked(Throwable t) {
Exceptions.<RuntimeException>throwIt(t);
}
@SuppressWarnings({“unchecked”})
private static <X extends Throwable> void throwIt(
Throwable t) throws X {
throw (X) t;
}
}
That’s all! Now, we can throw any checked exception. Here, we throw an IOException:
static void readFiles(List<Path> paths) throws IOException {
paths.forEach(p -> {
try {
readFile(p);
} catch (IOException e) {
Exceptions.throwChecked(new IOException(
“Some files are corrupted”, e));
}
});
}
And, we can catch it as follows:
List<Path> paths = List.of(…);
try {
readFiles(paths);
} catch (IOException e) {
System.out.println(e + ” \n ” + e.getCause());
}
If a certain path was not found then the reported error message will be:
java.io.IOException: Some files are corrupted
java.io.FileNotFoundException: …
(The system cannot find the path specified)
Cool, right?!
197. Implementing distinctBy() for Stream API
Let’s suppose that we have the following model and data:
public class Car {
private final String brand;
private final String fuel;
private final int horsepower;
…
}
List<Car> cars = List.of(
new Car(“Chevrolet”, “diesel”, 350),
…
new Car(“Lexus”, “diesel”, 300)
);
We know that the Stream API contains the distinct() intermediate operation that is capable to keep only the distinct elements based on the equals() method:
cars.stream()
.distinct()
.forEach(System.out::println);
While this code prints the distinct cars, we may want a distinctBy() intermediate operation capable to keep only the distinct elements based on a given property/key. For instance, we may need all the cars distinct by brand. For this, we can rely on the toMap() collector and the identity function as follows:
cars.stream()
.collect(Collectors.toMap(Car::getBrand,
Function.identity(), (c1, c2) -> c1))
.values()
.forEach(System.out::println);
We can extract this idea into a helper method as follows:
public static <K, T> Collector<T, ?, Map<K, T>>
distinctByKey(Function<? super T, ? extends K> function) {
return Collectors.toMap(
function, Function.identity(), (t1, t2) -> t1);
}
And, use it as here:
cars.stream()
.collect(Streams.distinctByKey(Car::getBrand))
.values()
.forEach(System.out::println);
While this is a nice job that works for null values as well, we can come up with other ideas that don’t work for null values. For instance, we can rely on ConcurrentHashMap and putIfAbsent() as follows (again, this doesn’t work for null values):
public static <T> Predicate<T> distinctByKey(
Function<? super T, ?> function) {
Map<Object, Boolean> seen = new ConcurrentHashMap<>();
return t -> seen.putIfAbsent(function.apply(t),
Boolean.TRUE) == null;
}
Or, we can optimize this approach a little bit and use a Set:
public static <T> Predicate<T> distinctByKey(
Function<? super T, ?> function) {
Set<Object> seen = ConcurrentHashMap.newKeySet();
return t -> seen.add(function.apply(t));
}
We can use these two approaches as in the following examples:
cars.stream()
.filter(Streams.distinctByKey(Car::getBrand))
.forEach(System.out::println);
cars.stream()
.filter(Streams.distinctByKey(Car::getFuel))
.forEach(System.out::println);
Challenge yourself to implement a distinctByKeys() operation, so by multiple keys.