import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
User(String name) {}
record
public class VanillaCode {
public static Optional<User> lookupUser(Connection db, int id) throws SQLException {
= db.prepareStatement("SELECT name FROM USER where id = ?");
var statement .setInt(1, id);
statement= statement.executeQuery();
var resultSet if (resultSet.next()) {
return Optional.of(new User(resultSet.getString(1)));
}
else {
return Optional.empty();
}
}
public static List<Optional<User>> lookupMultipleUsers(Connection db, List<Integer> ids) throws SQLException {
List<Optional<User>> users = new ArrayList<>();
for (int id : ids) {
.add(lookupUser(db, id));
users}
return users;
}
public static void exampleUsage(Connection db) {
try {
lookupUser(db, 123).ifPresentOrElse(
-> System.out.println("FOUND USER: " + user),
user () -> System.out.println("NO SUCH USER")
);
} catch (SQLException sqlException) {
System.out.println("ERROR RUNNING QUERY: " + sqlException);
}
}
}
import java.sql.Connection;
import java.sql.SQLException;
import java.util.List;
User(String name) {}
record
interface UserLookupResult {
sealed FoundUser(User user) implements UserLookupResult {}
record NoSuchUser() implements UserLookupResult {}
record ErrorRunningQuery(SQLException sqlException) implements UserLookupResult {}
record }
public class SealedTypes {
public static UserLookupResult lookupUser(Connection db, int id) {
try {
= db.prepareStatement("SELECT name FROM USER where id = ?");
var statement .setInt(1, id);
statement= statement.executeQuery();
var resultSet if (resultSet.next()) {
return new UserLookupResult.FoundUser(new User(resultSet.getString(1)));
}
else {
return new UserLookupResult.NoSuchUser();
}
}
catch (SQLException e) {
return new UserLookupResult.ErrorRunningQuery(e);
}
}
public static List<UserLookupResult> lookupMultipleUsers(Connection db, List<Integer> ids) {
return ids
.stream()
.map(id -> lookupUser(db, id))
.toList();
}
public static void exampleUsage(Connection db) {
switch (lookupUser(db, 123)) {
case UserLookupResult.FoundUser foundUser ->
System.out.println("FOUND USER: " + foundUser.user());
case UserLookupResult.NoSuchUser __ ->
System.out.println("NO SUCH USER");
case UserLookupResult.ErrorRunningQuery errorRunningQuery ->
System.out.println("ERROR RUNNING QUERY: " + errorRunningQuery.sqlException());
}
}
}
Sealed interfaces let you properly represent "sum types". This thing is either "A" or "B".
In this case we have a Stream<Integer>
that we want to turn into a Stream<User>
, but our method that takes Integer -> User
throws a SQLException
.
Your options before sealed interfaces were to
Stream<User>
by re-throwing any SQLException
as a RuntimeException
. As other comments have mentioned, this can be an issue depending on your type of stream. Also it requires that you fail the whole procedure if getting any one User
failsStream<Object>
by returning any SQLException
as a value, then cast it back at the end with instanceof
checks.Stream<Try<User>>
like with vavr
. This works if you don't care about the exception and just care that it failed in some way, but you won't be able to call methods particular to SQLException
like getSQLState
without casting. Also you lose documentation of why something can fail.default -> ... error ...
branches on all your switches would require using the visitor pattern.The new option is what you see, you can properly represent a function from Integer
-> User | SQLException
by wrapping each of the values in a sealed hierarchy.
sealed interface UserLookupResult {
record FoundUser(User user) implements UserLookupResult {}
record ErrorRunningQuery(SQLException sqlException) implements UserLookupResult {}
}
So it becomes Stream<Integer>
-> Stream<UserLookupResult>
, which is effectively Stream<FoundUser | ErrorRunningQuery>
. This gives you the most flexibility in how to interpret the result of the Stream without any casting or assumptions in usage.
Also if you had another possibility like UserIsBanned
, you can add it to the sealed hierarchy and all our switches would force you to fix them.