grep

① 概要

grep は、入力データから指定したパターンに一致する行を抽出するコマンドである。

ファイル指定時だけでなく、標準入力から与えられたデータも処理対象とし、
入力元に依存しない共通の処理モデルを持つ。


② 主要オプション

-f   : 検索パターンをファイルから読み込む
-F   : 固定文字列として検索する
-E   : 拡張正規表現を使用する

-c   : マッチした行数を表示する
-q   : 出力を行わず、終了コードのみ返す
-m X : X 回マッチした時点で処理を終了する

-A X : マッチした行の後 X 行を表示
-B X : マッチした行の前 X 行を表示
-C X : マッチした行の前後 X 行を表示

-r   : ディレクトリを再帰的に検索する

③ 動作

入力

grep は、ファイル指定時・標準入力指定時のいずれの場合も、 ファイルディスクリプタから入力を受け取る

以下は、ファイル指定時に grep が入力を取得する際の例である。

$ strace grep foo bigfile
openat(AT_FDCWD, "bigfile", O_RDONLY) = 3
...

読み取り

入力データは read() により 一定サイズずつ読み取られる

grep は行単位で入力を受け取っているわけではなく、 改行の解釈および行分割はユーザー空間で行われる。

$ strace grep foo bigfile
read(3, "...", 32768) = 32768
...

一致判定

読み取られたデータに対するパターン一致判定は、 すべてユーザー空間で実行される。

一致判定処理はユーザー空間で完結するため、 正規表現処理や文字比較は strace には現れない。


バイナリ判定

入力データ中に NUL バイトが検出された場合、 grep はその入力を バイナリデータ として扱う。

$ grep foo bigfile
grep: bigfile: binary file matches
...

デフォルト設定では、一致検出時点で処理を終了する。


再帰検索

-r 指定時、grepディレクトリを再帰的に探索する。

この場合、ディレクトリの列挙と各ファイルの読み取りを繰り返す。

$ strace grep -r foo /
getdents64(...)
...

④ 性能・制約(③の検証結果より)

バイナリファイルに対する早期終了

バイナリファイルと判定された入力に対しては、 内容の出力を行わず処理を終了する。

このため、NUL バイトが比較的早い位置に存在する場合、 入力全体を読み切る前に処理が完了する。


I/O 支配の処理特性

strace -c の結果から、read() が実行時間の大部分を占めることが確認できる。

これは、検索処理そのものよりも 入力データの読み取りが支配的であることを示している。


正規表現と固定文字列の差異

-E 指定時は正規表現エンジンによる評価が行われる一方、 -F 指定時は固定文字列として単純比較が行われる。

この違いは一致判定の計算コストに影響し、 大量データを対象とする場合には処理時間差として現れる。


早期終了による処理削減

-m 指定時、指定回数のマッチが検出された時点で grep は処理を終了する。

これは、不要な入力読み取りを抑制する手段として機能し、 I/O コスト削減に直結する。


入力経路に依存しない動作

ファイル指定・標準入力のいずれの場合でも、 grep の挙動は変化しない。

これは、grep が入力元ではなく ファイルディスクリプタからのバイト列のみを処理対象としている ことによる。


再帰検索時のコスト

再帰検索では、検索処理よりも ディレクトリ探索およびファイルオープン処理が支配的となる。

探索範囲が広い場合、大量のファイルアクセスが発生する。


⑤ 参考資料

  • man grep
  • https://ja.manpages.org/grep
  • man 2 open
  • man strace

ESP32 BLE広告を「状態」として扱う(SwitchBot温度計の観測)

確認したこと

BLE広告は単発イベントではなく周期的に到達する。 ESP32側では広告を逐次処理するのではなく、1デバイスの状態として保持する構造が必要になる。 本記事では、SwitchBot温度計のBLE広告を対象に、状態として扱える最小構成を実機で確認した。


対象

  • ESP32-WROOM
  • BLEスキャン(Arduino環境)
  • SwitchBot温度計

前提条件

BLEスキャンでは周囲の多数のデバイスが検出される。 本検証では、Service UUID が以下に一致するデバイスのみを対象とした。

0000fe9f-0000-1000-8000-00805f9b34fb

※値はダミー


実装概要

  • BLEコールバックで広告を受信
  • 対象デバイスであれば状態を更新
  • loop 側では状態のみを参照

広告イベントを直接利用せず、状態を一元管理する構成とした。


状態定義

struct SwitchBotState {
  bool seen;
  char address[18];
  int rssi;
  unsigned long lastSeen;
};

SwitchBotState bot = {false, "", 0, 0};

この構造体は以下を保持する。

  • 検出済みフラグ
  • バイス識別子
  • 最新RSSI
  • 最終受信時刻

状態更新

if (!d.haveServiceUUID()) return;
if (d.getServiceUUID().toString()
    != "0000fe9f-0000-1000-8000-00805f9b34fb") return;

strncpy(bot.address, "AA:BB:CC:DD:EE:01", sizeof(bot.address));
bot.address[sizeof(bot.address) - 1] = '\0';
bot.rssi = d.getRSSI();
bot.lastSeen = millis();
bot.seen = true;

BLE広告を受信するたびに、状態を上書き更新する。


状態参照

if (bot.seen) {
  unsigned long age = millis() - bot.lastSeen;
  Serial.print("SwitchBot state ");
  Serial.print(bot.address);
  Serial.print(" rssi=");
  Serial.print(bot.rssi);
  Serial.print(" age_ms=");
  Serial.println(age);
}

loop 側では以下を確認できる。

  • 現在のRSSI
  • 最後に広告を受信してからの経過時間

実行結果

SwitchBot state AA:BB:CC:DD:EE:01 rssi=-87 age_ms=198
SwitchBot state AA:BB:CC:DD:EE:01 rssi=-86 age_ms=187
SwitchBot state AA:BB:CC:DD:EE:01 rssi=-86 age_ms=227
SwitchBot state AA:BB:CC:DD:EE:01 rssi=-86 age_ms=508

同一アドレスの広告が周期的に到達し、 RSSI が変動し、age_ms が経過時間として更新されることを確認した。


まとめ

本検証で確認できた点は以下である。

  • BLE広告は同一デバイスから繰り返し送信される
  • 広告イベントを逐次処理する構成ではHub実装に適さない
  • 状態として集約することで、

    • 最新値参照
    • 消失判定
    • 閾値判定 が可能になる

ESP32 BLE受信データの設計

目的

ESP32でBLEスキャンを行う際、 onResult() コールバックで取得したデータが、

  • どこまで生きているのか
  • loop() から使えるのか
  • どのように置くと挙動が変わるのか

実機で順に確認する


結論

  • BLEコールバックで取得したデータは、そのままでは loop() では安全に使えない
  • データの置き方によって「使える範囲」と「挙動」が明確に変わる
  • 最終的に、コールバック側で完成させたデータをスナップショットとして渡す形であれば、挙動が安定する

環境


実験一覧

  • 実験1:コールバック内ローカル変数
  • 実験2:グローバル変数
  • 実験3:構造体によるスナップショット

実験1:コールバック内ローカル変数

コード

class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
  void onResult(BLEAdvertisedDevice d) {
    String localData = d.toString().c_str();
    Serial.print("onResult localData: ");
    Serial.println(localData);
  }
};

ログ

onResult localData: Name: , Address: **:**:**:**:**:**, rssi: -39
onResult localData: Name: , Address: **:**:**:**:**:**, rssi: -87

確認結果

  • BLEイベント自体は問題なく受信できる
  • 取得したデータはコールバック内でのみ有効
  • loop() から参照する手段はない

実験2:グローバル変数

コード

String globalData = "";

class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
  void onResult(BLEAdvertisedDevice d) {
    globalData = d.toString().c_str();
    Serial.print("onResult globalData: ");
    Serial.println(globalData);
  }
};

void loop() {
  Serial.print("loop sees globalData: ");
  Serial.println(globalData);
  delay(5000);
}

ログ

onResult globalData: Name: , Address: **:**:**:**:**:**, rssi: -42
onResult globalData: Name: , Address: **:**:**:**:**:**, rssi: -55
loop sees globalData: Name: , Address: **:**:**:**:**:**, rssi: -55

確認結果

  • loop() からデータを参照できる
  • BLEイベントが来るたびに値が更新される
  • 更新タイミングは制御できない

実験3:構造体によるスナップショット

コード

struct BleSnapshot {
  char address[18];
  int rssi;
};

BleSnapshot g_snapshot;
volatile bool g_snapshot_valid = false;

class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
  void onResult(BLEAdvertisedDevice d) {

    BleSnapshot local;
    memset(&local, 0, sizeof(local));

    snprintf(local.address, sizeof(local.address),
             "%s", d.getAddress().toString().c_str());
    local.rssi = d.getRSSI();

    g_snapshot = local;
    g_snapshot_valid = true;

    Serial.print("onResult committed: ");
    Serial.print(local.address);
    Serial.print(" rssi=");
    Serial.println(local.rssi);
  }
};

void loop() {
  if (g_snapshot_valid) {
    BleSnapshot snap = g_snapshot;
    Serial.print("loop sees: ");
    Serial.print(snap.address);
    Serial.print(" rssi=");
    Serial.println(snap.rssi);
  }
  delay(5000);
}

ログ

onResult committed: **:**:**:**:**:** rssi=-42
onResult committed: **:**:**:**:**:** rssi=-90
onResult committed: **:**:**:**:**:** rssi=-40
loop sees: **:**:**:**:**:** rssi=-40

確認結果

  • コールバック内で完成させたデータを保持できる
  • loop() 側では常に一貫した内容を参照できる
  • BLEイベントの発生頻度に影響されにくい

ESP32 × BLE スキャン入門

環境


今回のゴール

ESP32 が周囲の BLE デバイスをスキャンし、 見つけたデバイス情報をシリアルモニタに表示できること


実際に動かした BLE スキャンコード

以下は、ESP32 で BLE スキャンを行う最小構成のコード。

#include <BLEDevice.h>
#include <BLEUtils.h>
#include <BLEScan.h>
#include <BLEAdvertisedDevice.h>

BLEScan* pBLEScan;

class MyAdvertisedDeviceCallbacks : public BLEAdvertisedDeviceCallbacks {
  void onResult(BLEAdvertisedDevice advertisedDevice) {
    Serial.print("Found: ");
    Serial.println(advertisedDevice.toString().c_str());
  }
};

void setup() {
  Serial.begin(115200);
  Serial.println("Starting BLE scan...");

  BLEDevice::init("");
  pBLEScan = BLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(
      new MyAdvertisedDeviceCallbacks(), true);
  pBLEScan->setActiveScan(true);
  pBLEScan->setInterval(100);
  pBLEScan->setWindow(99);
}

void loop() {
  pBLEScan->start(5, false);
  Serial.println("Scan done.");
  delay(5000);
}

このコードを書き込み、 シリアルモニタを開く。


実際に出たログ

シリアルモニタには次のようなログが出力される。

Found: Name: , Address: **:**:**:**:**:**, manufacturer data: xxxxxxxxxxx, rssi: -86
Found: Name: , Address: **:**:**:**:**:**, manufacturer data: xxxxxxxxxxx, rssi: -31
Scan done.

ここから確認できることは以下。

  • バイス名が設定されていない BLE 機器も検出される
  • アドレス情報が取得できている
  • RSSI が出力され、距離変化に応じて値が変わる

つまり、

ESP32 が BLE 広告を受信できている

状態である。


このコードが行っている処理

このプログラムが行っている処理は、大きく分けて4つ。


① BLE 関連ライブラリの読み込み

#include <BLEDevice.h>
#include <BLEScan.h>

BLE 機能を使うための準備。 この段階ではスキャンは始まらない。


② BLE 機能の初期化とスキャナ取得

BLEDevice::init("");
pBLEScan = BLEDevice::getScan();
  • ESP32 の BLE 機能を初期化
  • BLE スキャンを担当するオブジェクトを取得

③ デバイス検出時の処理を登録

class MyAdvertisedDeviceCallbacks
  : public BLEAdvertisedDeviceCallbacks {

BLE デバイスが検出されるたびに呼び出される処理を定義する。

スキャン中、 検出イベント発生時に自動で実行される。


④ BLE スキャンの実行

pBLEScan->start(5, false);
  • スキャン時間:5秒
  • この間に検出された BLE デバイスごとに③の処理が実行される

全体の流れ

このプログラムは、

ESP32 で BLE を有効化し、 一定時間周囲をスキャンし、 見つかった BLE デバイス情報を出力する

という処理のみを行っている。


今回できたこと

まずは、 ESP32 が周囲の BLE デバイスを認識できる状態になった。


「ESP32でスマートホーム」シリーズの第二回として、

BLE を用いた家電制御に進む前段階として、 ESP32 が周囲の BLE デバイスを正しく検出できるか

を確認した


参考資料

ESP32-WROOM 初期セットアップで詰まったポイントまとめ(Arduino IDE 2.x / Windows)

環境


Arduino IDE 2.x のインストール(Windows

  1. Arduino公式サイトから Windows Win 10 or newer (64-bit) を選択してダウンロード

  2. ダウンロードしたインストーラを起動

  3. ライセンスに同意
  4. 「このコンピュータを使用しているすべてのユーザー用にインストールする」を選択
  5. インストール先フォルダを指定して実行
  6. 完了

ESP32 ボードマネージャの追加

Arduino IDE 起動後、 「追加のボードマネージャの URL」に以下を設定した。

https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json

ボードマネージャから esp32 by Espressif Systems をインストールし、 ボードは ESP32 Dev Module を選択。


最初の動作確認コード

void setup() {
  Serial.begin(115200);
}

void loop() {
  Serial.println("ESP32 alive");
  delay(1000);
}

書き込みログ(成功パターン)

Writing at 0x00033ee7 [===========>                  ]  42.9% 65536/152895 bytes...
Writing at 0x00038ff0 [===============>              ]  53.6% 81920/152895 bytes...
Writing at 0x0003e7c0 [==================>           ]  64.3% 98304/152895 bytes...
Writing at 0x00043e21 [=====================>        ]  75.0% 114688/152895 bytes...
Writing at 0x0004c534 [========================>     ]  85.7% 131072/152895 bytes...
Writing at 0x0005205d [===========================>  ]  96.4% 147456/152895 bytes...
Writing at 0x00054060 [==============================] 100.0% 152895 bytes...
Wrote 278624 bytes (152895 compressed) at 0x00010000 in 2.4 seconds (924.8 kbit/s).
Hash of data verified.

Hard resetting via RTS pin...

一見すると不安になるが、これは完全な成功ログだった。

  • 書き込み完了
  • ハッシュ検証 OK
  • 自動リセット実行

問題はない。


①:Debug を押してしまう

Arduino IDE 2.x には以下のアイコンが並ぶ。

最初に Debug を押してしまい、次のログが出た。

Error: unable to open ftdi device with description '*', serial '*'
gdb-server session closed

これは 自分が使用するESP32-WROOM で発生する問題だった

  • Debug は JTAG 前提
  • 一般的な ESP32-WROOM DevKit には JTAG が無い
  • そのため Debug は成立しない

ESP32-WROOM では Upload だけ使うのが正解。


②:Upload ボタンが分かりにくい

Arduino IDE 2.x では「Upload」という文字が表示されない。 そのため Debug と区別しづらい。

実際に使うのは 真ん中の「→」アイコンだった。


③:シリアルモニタの文字化け

書き込み後、以下のような表示になった。

JiJ�Jt�����Jq�1��1qo�jJ1�1�J�JtJ1joq11rtv�J���11

原因は ボーレート不一致

  • 起動直後のブートログ:74880 baud
  • Serial.begin(115200) 後:115200 baud

シリアルモニタを 115200 に合わせると解消する。


最終的な正常状態

ESP32 alive
ESP32 alive
ESP32 alive
ESP32 alive
  • 書き込み成功
  • setup() / loop() 正常動作
  • シリアル通信 OK

終わりに

初めてESP32を使用したが何とか動かすことができた


参考資料

https://www.arduino.cc/en/software/

DTOとEntity

1. 概要

データベースから複数件のレコードを取得する場合、 1件ずつ文字列で返す方法では扱いにくくなる。 そこで、検索結果を保持するための DTO(Data Transfer Object) を用意し、 DAO(Data Access Object)で取得したレコードを DTO のリストに詰めて返す構成にする。

DTO ・・・データ転送用のオブジェクト Entity ・・・テーブル構造をそのまま表すクラス


2. サンプルコード

  • DTO(データ転送専用)
  • DAO(DBアクセス専用)
  • ServletDTOリストを受け取り JSP に渡す)

DTO(Data Transfer Object)

public class StudentDTO {
    private int number;
    private String name;

    public StudentDTO(int number, String name) {
        this.number = number;
        this.name = name;
    }

    public int getNumber() { return number; }
    public String getName() { return name; }
}

ポイント:

  • DTO は「DB から取得した値をアプリ側に渡すための入れ物」
  • 必要な項目だけ持つ
  • Entity ではないため、テーブルと完全一致している必要はない

■ DAO(全件取得を DTO に詰める)

public class StudentDAO {

    private final String URL  = "jdbc:mysql://localhost/sampledb";
    private final String USER = "root";
    private final String PASS = "pass";
    private Connection con = null;

    public void connect() {
        try {
            con = DriverManager.getConnection(URL, USER, PASS);
        } catch(Exception e) {
            e.printStackTrace();
        }
    }

    public ArrayList<StudentDTO> findAll() {

        ArrayList<StudentDTO> list = new ArrayList<>();
        Statement stmt = null;
        ResultSet rs   = null;

        String sql = "SELECT no, name FROM student";

        try {
            stmt = con.createStatement();
            rs   = stmt.executeQuery(sql);

            while(rs.next()) {
                int no = rs.getInt("no");
                String name = rs.getString("name");

                StudentDTO dto = new StudentDTO(no, name);
                list.add(dto);
            }

        } catch(Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if(rs   != null) rs.close();
                if(stmt != null) stmt.close();
            } catch(Exception e) {
                e.printStackTrace();
            }
        }

        return list;
    }

    public void disconnect() {
        try {
            if(con != null) con.close();
        } catch(Exception e) {
            e.printStackTrace();
        }
    }
}

ServletDTOリストを JSP に渡す)

@WebServlet("/showAll")
public class ShowAllServlet extends HttpServlet {

    public void doPost(HttpServletRequest req, HttpServletResponse res)
            throws IOException, ServletException {

        StudentDAO dao = new StudentDAO();
        dao.connect();

        ArrayList<StudentDTO> list = dao.findAll();

        dao.disconnect();

        req.setAttribute("students", list);

        RequestDispatcher rd =
            req.getRequestDispatcher("/showAll.jsp");

        rd.forward(req, res);
    }

    public void doGet(HttpServletRequest req, HttpServletResponse res)
            throws IOException, ServletException {
        doPost(req, res);
    }
}

3. コードの解説

DTO は「検索結果1件分のデータの入れ物」

DTO は画面層・サービス層・DAO層間のデータ受け渡しに使う。 名前の通り Data Transfer Object であり、


◆ DAO の役割は DB 処理を一箇所にまとめること

DAO は接続・SQL実行・結果処理をすべて内部で処理し、 外からは findAll() を呼ぶだけで値が取れる。

StudentDTO dto = new StudentDTO(no, name);
list.add(dto);

DAO は DTO を作成し、リストに詰めるだけに集中する。


◆ while(rs.next()) で全件取得できる

検索結果を1行ずつ読み取るため、 while(rs.next()) を使って繰り返し DTO を生成する。


ServletDTO の受け渡しに専念できる

Servlet は DB処理を一切書かず、 DAO を呼ぶ → DTOリストを request に入れる → JSP へ渡す だけになるため保守性が向上する。


4. ポイント

  • DTO は「転送用データオブジェクト」であり Entity とは別概念
  • Entity はテーブル構造を表すが、DTO は必要な情報だけを持つ
  • 複数件取得は DTOArrayList に詰めると扱いやすい
  • DAO が DB 処理を一元管理し、Servlet の肥大化を防ぐ
  • DTO を返す構造にすると JSP 側で for 文による一覧表示が容易になる

5. まとめ

  • DTO はレコードを受け渡すための入れ物であり、Entity とは役割が異なる
  • DAO と DTO を組み合わせることで、Servlet の責務が明確になりコードが整理される
  • 全件取得では DTO のリストを返すのが定石
  • 一覧表示や後続処理が書きやすくなるため、実践的な実装方法として有効

参考資料

https://youtu.be/Ey2gbUUP-RA?si=EWF9PPQY7I6W4Ep0

【Java】try-catch の中に return を書かないほうがいい理由

結論

JDBC などのリソース管理を伴う処理では、
try / catch の中で return を書かない設計が望ましい。

理由は、制御フローと例外設計が不明瞭になり、
保守時に意図しない挙動や設計事故を招きやすくなるためである。

ただし、try-with-resources を使用している場合に限り、
try 内で return を書いても設計上の問題は生じない。

Java 言語仕様の前提

Java では、try ブロック内で return が実行された場合でも、
finally ブロックは必ず実行される。

try {
    return count;
} finally {
    closeResources();
}

この挙動は Java Language Specification に明記されている。

したがって、 「try 内で return すると finally が実行されない」 という説明は仕様上誤りである。

問題は、仕様ではなく設計と可読性にある。

制御フローの不透明化

try / catch 内に return が存在すると、 メソッドの終了条件が分散する。

try {
    count = ps.executeUpdate();
    return count;
} catch (Exception e) {
    return count;
}

この構造では、

  • 正常終了と異常終了の区別
  • どの条件でメソッドが終了するのか
  • 戻り値の意味

を、コード全体を読まなければ判断できない。

制御フローが暗黙化し、 保守時の理解コストが増大する。

例外設計の破綻

DAO における基本的な責務は次の二点である。

  • 成功時に結果を返す
  • 失敗時に例外を呼び出し元へ伝播する

catch 内で return を行う設計では、この分離が崩れる。

catch (Exception e) {
    return 0;
}

この戻り値が、

  • 正常に 0 件更新だった結果
  • 例外発生後の代替値

のどちらなのかを、呼び出し元は判断できない。

これは業務アプリケーションにおいて致命的である。

JDBC リソース管理との非親和性

JDBC では、次のリソースを明示的に管理する必要がある。

  • Connection
  • PreparedStatement
  • ResultSet

try の途中に return が存在すると、

  • すべての終了経路で close されるか
  • リソース追加時にも安全か

を構造的に確認しづらくなる。

そのため、return を try の外に出し、 リソース管理と制御フローを分離する設計が好まれる。

return を try の外に出した例(完全ダミー)

int count = 0;

try {
    Connection con = DatabaseConnection.getConnection();
    PreparedStatement ps =
        con.prepareStatement("UPDATE sample_table SET col1=?, col2=?, col3=? WHERE id=?");

    ps.setString(1, "dummy_value_1");
    ps.setString(2, "dummy_value_2");
    ps.setString(3, "dummy_value_3");
    ps.setInt(4, 1);

    count = ps.executeUpdate();

    ps.close();
    con.close();

} catch (SQLException e) {
    throw new RuntimeException(e);
}

return count;

成功時は戻り値、 失敗時は例外という役割分担が明確になる。

try-with-resources における言語仕様上の保証

try-with-resources 文では、 try に指定されたリソースはすべて AutoCloseable として扱われる。

try (Resource r = ...) {
    return result;
}

この構文は、言語仕様上、 try ブロックの終了時(return・例外を含む)に r.close() が必ず呼び出される ことが保証されている。

これは finally の省略形ではなく、 コンパイル時に次のような構造へ展開される。

  • try の終了を検知
  • 例外の有無に関係なく close を実行
  • close 中の例外は抑制例外として管理

そのため、

  • return による早期終了
  • 例外による異常終了

のいずれの場合でも、 リソース管理の安全性が言語仕様で担保される。

try-with-resources 使用例

try (Connection con = DatabaseConnection.getConnection();
     PreparedStatement ps =
         con.prepareStatement("UPDATE sample_table SET col1=?, col2=?, col3=? WHERE id=?")) {

    ps.setString(1, "dummy_value_1");
    ps.setString(2, "dummy_value_2");
    ps.setString(3, "dummy_value_3");
    ps.setInt(4, 1);

    return ps.executeUpdate();
}

この構造では、 return が try 内に存在しても、 リソース管理と制御フローの安全性が損なわれない。

実務上の判断基準

  • 手動で close を行う構造では try 内 return を避ける
  • catch 内で return を行わない
  • try-with-resources 使用時のみ try 内 return を許容する

まとめ

try-catch 内で return を避けるべき理由は、 Java の仕様ではなく設計と保守性にある。

try-with-resources は例外であり、 その安全性は言語仕様によって明確に保証されている。

参考資料