[Java] Stream APIで例外処理を正しく行う方法
JavaのStream APIでは、ラムダ式内で例外が発生する場合、通常のチェック例外はキャッチまたはスローする必要があります。
しかし、Stream APIのメソッドはチェック例外をスローできないため、例外処理が複雑になります。
一般的な方法としては、ラムダ式内でtry-catchブロックを使用して例外をキャッチし、適切に処理するか、例外をラップしてRuntimeExceptionとして再スローする方法があります。
- Stream APIでの例外処理の基本
- カスタム例外の活用方法
- ユーティリティクラスの実装
- ファイルやDB操作の例外処理
- ネットワーク通信時の注意点
Stream APIでの例外処理の基本的な方法
JavaのStream APIを使用する際、例外処理は重要な要素です。
Streamの操作中に発生する可能性のある例外を適切に処理する方法を見ていきましょう。
try-catchブロックを使った例外処理
Streamの操作中に例外が発生する場合、通常のtry-catchブロックを使用して例外を捕捉することができます。
以下はその例です。
import java.util.Arrays;
import java.util.List;
public class App {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", null);
// Streamの操作中に例外が発生する可能性がある
try {
names.stream()
.map(name -> name.toUpperCase()) // nullがあるとNullPointerExceptionが発生
.forEach(System.out::println);
} catch (NullPointerException e) {
System.out.println("例外が発生しました: " + e.getMessage());
}
}
}
ALICE
BOB
CHARLIE
例外が発生しました: Cannot invoke "String.toUpperCase()" because "<parameter1>" is null
この例では、null
が含まれているため、NullPointerException
が発生します。
try-catchブロックを使用することで、例外を捕捉し、適切に処理することができます。
カスタム例外を使った例外処理
カスタム例外を作成することで、より具体的なエラーメッセージを提供することができます。
以下はカスタム例外を使用した例です。
import java.util.Arrays;
import java.util.List;
class CustomException extends Exception {
public CustomException(String message) {
super(message);
}
}
public class App {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", null);
names.stream()
.map(name -> {
try {
if (name == null) {
throw new CustomException("名前がnullです");
}
return name.toUpperCase();
} catch (CustomException e) {
// 例外をラップしてRuntimeExceptionとしてスローする
throw new RuntimeException(e);
}
})
.forEach(name -> {
try {
System.out.println(name);
} catch (RuntimeException e) {
// ラップされた例外をキャッチして処理する
Throwable cause = e.getCause();
if (cause instanceof CustomException) {
System.out.println("カスタム例外が発生しました: " + cause.getMessage());
} else {
throw e; // 他の例外は再スロー
}
}
});
}
}
カスタム例外が発生しました: 名前がnullです
この例では、CustomException
を作成し、null
が検出された場合にスローしています。
これにより、エラーメッセージをカスタマイズできます。
RuntimeExceptionでラップする方法
チェック例外を使用せず、RuntimeException
でラップする方法もあります。
これにより、例外処理が簡潔になります。
以下はその例です。
import java.util.Arrays;
import java.util.List;
public class App {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", null);
names.stream()
.map(name -> {
try {
return name.toUpperCase();
} catch (NullPointerException e) {
throw new RuntimeException("名前がnullです", e);
}
})
.forEach(System.out::println);
}
}
ALICE
BOB
CHARLIE
Exception in thread "main" java.lang.RuntimeException: CustomException: 名前がnullです
at App.lambda$main$0(App.java:22)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:215)
at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:1024)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:570)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:560)
at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:265)
at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:636)
at App.main(App.java:25)
この方法では、RuntimeException
を使用して例外をラップし、Streamの外で処理することができます。
これにより、例外処理が簡潔になります。
例外処理を行うユーティリティメソッドの作成
例外処理を行うユーティリティメソッドを作成することで、コードの再利用性を高めることができます。
以下はその例です。
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
public class App {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", null);
names.stream()
.map(handleException(name -> name.toUpperCase()))
.forEach(System.out::println);
}
// 例外処理を行うユーティリティメソッド
private static <T, R> Function<T, R> handleException(Function<T, R> function) {
return t -> {
try {
return function.apply(t);
} catch (NullPointerException e) {
throw new RuntimeException("名前がnullです", e);
}
};
}
}
ALICE
BOB
CHARLIE
Exception in thread "main" java.lang.RuntimeException: 名前がnullです
at App.lambda$handleException$1(App.java:19)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:215)
at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:1024)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:570)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:560)
at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:265)
at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:636)
at App.main(App.java:10)
Caused by: java.lang.NullPointerException: Cannot invoke "String.toUpperCase()" because "<parameter1>" is null
at App.lambda$main$0(App.java:9)
at App.lambda$handleException$1(App.java:17)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:215)
at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:1024)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:570)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:560)
at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:265)
at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:636)
at App.main(App.java:10)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at jdk.compiler/com.sun.tools.javac.launcher.SourceLauncher.execute(SourceLauncher.java:268)
at jdk.compiler/com.sun.tools.javac.launcher.SourceLauncher.run(SourceLauncher.java:153)
at jdk.compiler/com.sun.tools.javac.launcher.SourceLauncher.main(SourceLauncher.java:78)
この例では、handleExceptionメソッド
を作成し、Stream内での例外処理を簡潔に行えるようにしています。
これにより、コードの可読性が向上します。
例外処理を行うためのユーティリティクラスの実装
Stream APIを使用する際に、例外処理を簡潔に行うためのユーティリティクラスを実装することができます。
これにより、コードの可読性と再利用性が向上します。
以下では、Function
、Consumer
、Supplier
インターフェースを拡張した例外処理の方法を見ていきます。
Functionインターフェースを拡張した例外処理
Function
インターフェースを拡張して、例外を処理するユーティリティメソッドを作成します。
以下はその実装例です。
import java.util.function.Function;
public class ExceptionHandling {
// 例外処理を行うFunctionインターフェースの拡張
public static <T, R> Function<T, R> wrapFunction(CheckedFunction<T, R> function) {
return t -> {
try {
return function.apply(t);
} catch (Exception e) {
throw new RuntimeException("例外が発生しました: " + e.getMessage(), e);
}
};
}
// チェックされた例外をスローするFunctionインターフェース
@FunctionalInterface
public interface CheckedFunction<T, R> {
R apply(T t) throws Exception;
}
}
このユーティリティメソッドを使用することで、Stream内での例外処理が簡単になります。
Consumerインターフェースを拡張した例外処理
次に、Consumer
インターフェースを拡張して、例外処理を行う方法を見てみましょう。
以下はその実装例です。
import java.util.function.Consumer;
public class ExceptionHandling {
// 例外処理を行うConsumerインターフェースの拡張
public static <T> Consumer<T> wrapConsumer(CheckedConsumer<T> consumer) {
return t -> {
try {
consumer.accept(t);
} catch (Exception e) {
throw new RuntimeException("例外が発生しました: " + e.getMessage(), e);
}
};
}
// チェックされた例外をスローするConsumerインターフェース
@FunctionalInterface
public interface CheckedConsumer<T> {
void accept(T t) throws Exception;
}
}
このように、Consumer
インターフェースを拡張することで、例外処理を簡潔に行うことができます。
Supplierインターフェースを拡張した例外処理
Supplier
インターフェースを拡張して、例外処理を行う方法もあります。
以下はその実装例です。
import java.util.function.Supplier;
public class ExceptionHandling {
// 例外処理を行うSupplierインターフェースの拡張
public static <T> Supplier<T> wrapSupplier(CheckedSupplier<T> supplier) {
return () -> {
try {
return supplier.get();
} catch (Exception e) {
throw new RuntimeException("例外が発生しました: " + e.getMessage(), e);
}
};
}
// チェックされた例外をスローするSupplierインターフェース
@FunctionalInterface
public interface CheckedSupplier<T> {
T get() throws Exception;
}
}
このユーティリティメソッドを使用することで、例外を処理しながら値を供給することができます。
例外をラップするメソッドの実装例
最後に、例外をラップするメソッドの実装例を示します。
以下は、これまでのユーティリティメソッドを使用した例です。
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.function.Function;
class ExceptionHandling {
// 例外処理を行うFunctionインターフェースの拡張
public static <T, R> Function<T, R> wrapFunction(CheckedFunction<T, R> function) {
return t -> {
try {
return function.apply(t);
} catch (Exception e) {
throw new RuntimeException("例外が発生しました: " + e.getMessage(), e);
}
};
}
// チェックされた例外をスローするFunctionインターフェース
@FunctionalInterface
public interface CheckedFunction<T, R> {
R apply(T t) throws Exception;
}
}
public class App {
public static void main(String[] args) {
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", null);
List<String> upperCaseNames = names.stream()
.map(ExceptionHandling.wrapFunction(name -> {
if (name == null) {
throw new Exception("名前がnullです");
}
return name.toUpperCase();
}))
.collect(Collectors.toList());
upperCaseNames.forEach(System.out::println);
}
}
Exception in thread "main" java.lang.RuntimeException: 例外が発生しました: 名前がnullです
at ExceptionHandling.lambda$wrapFunction$0(App.java:13)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:215)
at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:1024)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:570)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:560)
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:265)
at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:727)
at App.main(App.java:35)
Caused by: java.lang.Exception: 名前がnullです
at App.lambda$main$0(App.java:31)
at ExceptionHandling.lambda$wrapFunction$0(App.java:11)
at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:215)
at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:1024)
at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:570)
at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:560)
at java.base/java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:921)
at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:265)
at java.base/java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:727)
at App.main(App.java:35)
at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
at java.base/java.lang.reflect.Method.invoke(Method.java:580)
at jdk.compiler/com.sun.tools.javac.launcher.SourceLauncher.execute(SourceLauncher.java:268)
at jdk.compiler/com.sun.tools.javac.launcher.SourceLauncher.run(SourceLauncher.java:153)
at jdk.compiler/com.sun.tools.javac.launcher.SourceLauncher.main(SourceLauncher.java:78)
この例では、wrapFunctionメソッド
を使用して、null
が含まれている場合に例外をラップしています。
これにより、Stream内での例外処理が簡潔に行えます。
例外処理を行うStream APIの応用例
Stream APIを使用する際、さまざまな場面で例外処理が必要になります。
ここでは、ファイル操作、データベースアクセス、ネットワーク通信、そして複数の例外が発生する場合の処理方法について解説します。
ファイル操作での例外処理
ファイル操作を行う際には、IOException
が発生する可能性があります。
以下は、ファイルから行を読み込み、各行を大文字に変換する例です。
例外処理を行うために、先ほど作成したユーティリティメソッドを使用します。
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;
public class App {
public static void main(String[] args) {
try (BufferedReader reader = new BufferedReader(new FileReader("sample.txt"))) {
List<String> upperCaseLines = reader.lines()
.map(ExceptionHandling.wrapFunction(line -> {
if (line == null) {
throw new IOException("行がnullです");
}
return line.toUpperCase();
}))
.collect(Collectors.toList());
upperCaseLines.forEach(System.out::println);
} catch (IOException e) {
System.out.println("ファイル操作中に例外が発生しました: " + e.getMessage());
}
}
}
この例では、ファイルから読み込んだ各行を大文字に変換し、IOException
を適切に処理しています。
データベースアクセスでの例外処理
データベースアクセス時にも例外が発生する可能性があります。
以下は、JDBCを使用してデータベースからデータを取得し、Stream APIで処理する例です。
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
public class App {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
try (Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "user", "password");
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery("SELECT name FROM users")) {
while (resultSet.next()) {
names.add(resultSet.getString("name"));
}
names.stream()
.map(ExceptionHandling.wrapFunction(name -> {
if (name == null) {
throw new SQLException("名前がnullです");
}
return name.toUpperCase();
}))
.forEach(System.out::println);
} catch (SQLException e) {
System.out.println("データベース操作中に例外が発生しました: " + e.getMessage());
}
}
}
この例では、データベースから取得した名前を大文字に変換し、SQLException
を適切に処理しています。
ネットワーク通信での例外処理
ネットワーク通信を行う際にも、例外が発生する可能性があります。
以下は、HTTPリクエストを送信し、レスポンスを処理する例です。
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.List;
import java.util.stream.Collectors;
public class App {
public static void main(String[] args) {
try {
URL url = new URL("https://api.example.com/data");
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
List<String> lines = reader.lines()
.map(ExceptionHandling.wrapFunction(line -> {
if (line == null) {
throw new Exception("行がnullです");
}
return line.toUpperCase();
}))
.collect(Collectors.toList());
lines.forEach(System.out::println);
reader.close();
} catch (Exception e) {
System.out.println("ネットワーク通信中に例外が発生しました: " + e.getMessage());
}
}
}
この例では、HTTPリクエストを送信し、レスポンスを大文字に変換しています。
例外が発生した場合は、適切に処理しています。
複数の例外が発生する場合の処理方法
複数の例外が発生する可能性がある場合、try-catchブロックを使用してそれぞれの例外を処理することが重要です。
以下は、ファイル操作とデータベースアクセスを同時に行う例です。
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
public class App {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
// データベースから名前を取得
try (Connection connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "user", "password");
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery("SELECT name FROM users")) {
while (resultSet.next()) {
names.add(resultSet.getString("name"));
}
} catch (SQLException e) {
System.out.println("データベース操作中に例外が発生しました: " + e.getMessage());
}
// ファイルから行を読み込み
try (BufferedReader reader = new BufferedReader(new FileReader("sample.txt"))) {
List<String> upperCaseLines = reader.lines()
.map(ExceptionHandling.wrapFunction(line -> {
if (line == null) {
throw new IOException("行がnullです");
}
return line.toUpperCase();
}))
.collect(Collectors.toList());
upperCaseLines.forEach(System.out::println);
} catch (IOException e) {
System.out.println("ファイル操作中に例外が発生しました: " + e.getMessage());
}
}
}
この例では、データベースから名前を取得した後、ファイルから行を読み込んでいます。
それぞれの操作で発生する可能性のある例外を適切に処理しています。
これにより、複数の例外が発生する場合でも、コードが明確で保守しやすくなります。
よくある質問
まとめ
この記事では、JavaのStream APIにおける例外処理の基本的な方法から、ファイル操作、データベースアクセス、ネットワーク通信などの具体的な応用例までを詳しく解説しました。
特に、例外処理を行うためのユーティリティクラスの実装や、複数の例外が発生する場合の処理方法についても触れ、実践的な知識を提供しました。
これを機に、Stream APIを使用する際には、例外処理を適切に行い、より堅牢なアプリケーションを構築することを心がけてみてください。