1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【初心者向け】AIが書くJavaコードで「ファイルが閉じられない」問題が起きる理由

Last updated at Posted at 2025-12-21

この記事を読むと分かること

  • AIツール(ChatGPT、GitHub Copilotなど)が生成するJavaコードの注意点
  • 「リソース解放」って何?なぜ大切なの?
  • try-with-resourcesの正しい使い方と落とし穴
  • 実際にどんな問題が起きるのか、どう防げばいいのか

対象読者: プログラミングを学び始めた高校生・大学生、Java初心者の方

はじめに:AIでコードを書く時代

最近、ChatGPTやGitHub Copilotを使ってプログラミングする人が増えていますよね。「ファイルを読むコード書いて」とお願いすると、すぐにコードを生成してくれて便利です。

でも実は、AIが生成するコードには「よくある見落とし」 があるんです。

それが今回のテーマ、「リソース解放の忘れ」 です。

リソース解放って何?

日常生活で例えると

図書館で本を借りたら、読み終わったら必ず返却しますよね?

返却しないと

  • 他の人が借りられない
  • 延滞料金がかかる
  • 図書館が「この本は貸出中」と思い込んだまま

プログラミングでも同じことが起きます!

プログラミングでのリソース

コンピュータの中の「借りたら返さないといけないもの」をリソースと呼びます:

リソースの種類 やるべきこと
ファイル 開いたら閉じる
ネットワーク接続 繋いだら切断する
データベース接続 使ったら返却する
メモリ 確保したら解放する

これらをちゃんと返却する(解放する) のが、プログラマーの責任です。

AIが書くコードの問題点

問題例1: ファイルを開きっぱなし

// ❌ AIがよく生成する問題のあるコード
public void readFile(String fileName) throws IOException {
    FileReader reader = new FileReader(fileName);
    BufferedReader br = new BufferedReader(reader);
    
    String line;
    while ((line = br.readLine()) != null) {
        System.out.println(line);
    }
    
    // あれ?ファイルを閉じていない!
}

このコードの問題

  • ❌ ファイルを開いたまま終了している
  • ❌ 図書館の本を返さないのと同じ
  • ❌ 何度も実行すると、エラーになる

古い書き方:finallyブロックで閉じる

// ⚠️ 古い書き方(Java 7より前)
public void readFileOldWay(String fileName) throws IOException {
    FileReader reader = null;
    BufferedReader br = null;
    
    try {
        reader = new FileReader(fileName);
        br = new BufferedReader(reader);
        
        String line;
        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }
    } finally {
        if (br != null) {
            br.close();  // 必ず閉じる
        }
        if (reader != null) {
            reader.close();
        }
    }
}

この書き方の問題

  • コードが長くて読みにくい
  • close()自体がエラーを起こす可能性がある
  • 複数のリソースを管理するのが大変

try-with-resources:現代の正しい書き方

基本の使い方

Java 7(2011年)から、try-with-resourcesという便利な機能が追加されました!

// ✅ 現代の正しいコード
public void readFile(String fileName) throws IOException {
    try (BufferedReader br = new BufferedReader(new FileReader(fileName))) {
        String line;
        while ((line = br.readLine()) != null) {
            System.out.println(line);
        }
    } // ← ここで自動的にファイルが閉じられる!
}

ポイント

  • try (リソースの作成) の形で書く
  • tryブロックを抜けると、自動的にリソースが閉じられる
  • エラーが起きても、必ず閉じられる
  • コードが短くて読みやすい

仕組み:AutoCloseableインターフェース

try-with-resourcesで自動的に閉じられるのは、AutoCloseableというインターフェースを実装しているクラスだけです。

// 自動的に閉じられるクラスの例
public class BufferedReader implements AutoCloseable {
    // ...
    
    @Override
    public void close() throws IOException {
        // ファイルを閉じる処理
    }
}

Javaの標準ライブラリで自動的に閉じられる主なクラス

  • FileReader, FileWriter
  • FileInputStream, FileOutputStream
  • BufferedReader, BufferedWriter
  • Scanner
  • Connection, Statement, ResultSet(データベース)
  • Stream(ファイルやコレクション)

複数のリソースを使う場合

// ✅ 複数のリソースも同時に管理できる
public void copyFile(String from, String to) throws IOException {
    try (
        FileInputStream in = new FileInputStream(from);
        FileOutputStream out = new FileOutputStream(to)
    ) {
        byte[] buffer = new byte[1024];
        int length;
        while ((length = in.read(buffer)) > 0) {
            out.write(buffer, 0, length);
        }
    } // ← outとinの両方が自動的に閉じられる(逆順で)
}

重要:複数のリソースは逆順で閉じられます

  1. outが先に閉じられる
  2. 次にinが閉じられる

try-with-resourcesの落とし穴

落とし穴1: リソースをtryの外で作る

// ❌ 危険なコード
BufferedReader reader = new BufferedReader(new FileReader("file.txt"));
try (reader) {  // ← リソース作成とtryが分離している
    String line = reader.readLine();
}

何が問題?

// もっと明確な問題例
BufferedReader reader = new BufferedReader(new FileReader("file.txt"));
doSomething();  // ← ここでエラーが起きたら?
try (reader) {
    // ここまで到達しない = ファイルが閉じられない!
}

✅ 正しい書き方:リソースは必ずtry()の中で作る

try (BufferedReader reader = new BufferedReader(new FileReader("file.txt"))) {
    String line = reader.readLine();
}

落とし穴2: Streamを閉じ忘れる

Java 8以降、Streamというデータの流れを扱うクラスがあります。
Streamもリソースなので、閉じる必要があります!

// ❌ 危険なコード:Streamが閉じられない
public long countLines(String fileName) throws IOException {
    return Files.lines(Paths.get(fileName)).count();
    // ← Streamが閉じられていない!
}

// ❌ 危険なコード:フォルダ内のファイル一覧
public void listFiles(String folderName) throws IOException {
    Files.walk(Paths.get(folderName))
         .forEach(System.out::println);
    // ← Streamが閉じられていない!
}

✅ 正しい書き方:Streamもtry-with-resourcesで管理

// ✅ 正しいコード
public long countLines(String fileName) throws IOException {
    try (Stream<String> lines = Files.lines(Paths.get(fileName))) {
        return lines.count();
    }
}

// ✅ 正しいコード
public void listFiles(String folderName) throws IOException {
    try (Stream<Path> paths = Files.walk(Paths.get(folderName))) {
        paths.forEach(System.out::println);
    }
}

覚え方

  • Files.lines() → try-with-resourcesで囲む
  • Files.walk() → try-with-resourcesで囲む
  • stream()でファイルを扱う → try-with-resourcesで囲む

落とし穴3: 一部のリソースだけtryに入れる

// ❌ 危険なコード
public void copyFile(String from, String to) throws IOException {
    FileInputStream in = new FileInputStream(from);  // ← これが閉じられない!
    
    try (FileOutputStream out = new FileOutputStream(to)) {
        byte[] buffer = new byte[1024];
        int length;
        while ((length = in.read(buffer)) > 0) {
            out.write(buffer, 0, length);
        }
    }
    // outは閉じられるけど、inは閉じられない!
}

✅ 正しい書き方:全てのリソースをtry()に入れる

public void copyFile(String from, String to) throws IOException {
    try (
        FileInputStream in = new FileInputStream(from);
        FileOutputStream out = new FileOutputStream(to)
    ) {
        byte[] buffer = new byte[1024];
        int length;
        while ((length = in.read(buffer)) > 0) {
            out.write(buffer, 0, length);
        }
    }
}

落とし穴4: データベースで一部のリソースを忘れる

データベースを使う時は、3つのリソースを管理する必要があります:

  1. Connection(接続)
  2. StatementまたはPreparedStatement(SQL文)
  3. ResultSet(結果)
// ❌ 危険なコード:StatementとResultSetが閉じられない
public User getUser(int id) throws SQLException {
    try (Connection conn = dataSource.getConnection()) {
        PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE id = ?");
        stmt.setInt(1, id);
        ResultSet rs = stmt.executeQuery();
        
        if (rs.next()) {
            return new User(rs.getString("name"));
        }
    }  // ← Connectionは閉じられるが、stmtとrsは閉じられない!
    return null;
}

✅ 正しい書き方:全部のリソースを管理する

public User getUser(int id) throws SQLException {
    try (
        Connection conn = dataSource.getConnection();
        PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE id = ?")
    ) {
        stmt.setInt(1, id);
        try (ResultSet rs = stmt.executeQuery()) {
            if (rs.next()) {
                return new User(rs.getString("name"));
            }
        }
    }
    return null;
}

落とし穴5: リソースを返り値として返す

// ❌ これは絶対ダメ!
public BufferedReader openFile(String fileName) throws IOException {
    try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
        return reader;  // ← tryを抜けた瞬間に閉じられる!
    }
}

// 使う側
BufferedReader reader = openFile("file.txt");
String line = reader.readLine();  // ← エラー発生!Stream closed

何が起きた?

  1. openFile()の中でファイルを開く
  2. tryブロックを抜ける → 自動的に閉じられる
  3. 閉じられたreaderが返される
  4. 使おうとする → 既に閉じられているのでエラー

✅ 正しい設計パターン

パターン1: データを読んでから返す

// リソースではなく、読み込んだデータを返す
public List<String> readAllLines(String fileName) throws IOException {
    try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
        List<String> lines = new ArrayList<>();
        String line;
        while ((line = reader.readLine()) != null) {
            lines.add(line);
        }
        return lines;  // データなのでOK
    }
}

パターン2: 処理を受け取る(少し難しい)

// ファイルを開いて、処理を実行して、自動的に閉じる
public void withFile(String fileName, Consumer<BufferedReader> processor) throws IOException {
    try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
        processor.accept(reader);
    }
}

// 使う側
withFile("file.txt", reader -> {
    String line = reader.readLine();
    System.out.println(line);
});

自分で作るクラスをAutoCloseableにする

自分でファイルを扱うクラスを作る時は、AutoCloseableを実装しましょう。

悪い例:AutoCloseableを実装していない

// ❌ これではtry-with-resourcesで使えない
public class MyFileReader {
    private BufferedReader reader;
    
    public MyFileReader(String fileName) throws IOException {
        this.reader = new BufferedReader(new FileReader(fileName));
    }
    
    public String readLine() throws IOException {
        return reader.readLine();
    }
    
    // close()メソッドがない!
}

// 使おうとすると...
try (MyFileReader reader = new MyFileReader("file.txt")) {  // ← コンパイルエラー!
    // ...
}

良い例:AutoCloseableを実装する

// ✅ 正しい実装
public class MyFileReader implements AutoCloseable {
    private BufferedReader reader;
    
    public MyFileReader(String fileName) throws IOException {
        this.reader = new BufferedReader(new FileReader(fileName));
    }
    
    public String readLine() throws IOException {
        return reader.readLine();
    }
    
    @Override
    public void close() throws IOException {
        if (reader != null) {
            reader.close();  // 内部のリソースを閉じる
        }
    }
}

// try-with-resourcesで使える!
try (MyFileReader reader = new MyFileReader("file.txt")) {
    String line = reader.readLine();
    System.out.println(line);
}  // ← 自動的にclose()が呼ばれる

ポイント

  • implements AutoCloseableを書く
  • close()メソッドを実装する
  • close()の中で、内部のリソースを閉じる
  • nullチェックを忘れずに

なぜAIはこの間違いをするの?

理由1: 古いコードで学習している

AIは過去のプログラムコードを大量に読んで学習しています。
でも、インターネット上には古い書き方のコードがたくさんあります。

  • Java 7より前(2011年以前): try-with-resourcesがなかった
  • 古いチュートリアル: finallyブロックで閉じる方法を教えている
  • GitHubの古いプロジェクト: 昔のやり方で書かれたまま

AIはこれらを「よくあるパターン」として覚えてしまっています。

理由2: 「簡単に見せたい」と思っている

AIにこう聞いたとします:
「ファイルを読むコードを書いて」

AIは「わかりやすく、短く」しようとして:

  1. ファイルを読む部分だけを書く
  2. エラー処理は省略
  3. ファイルを閉じる処理も省略

→ 結果、不完全なコードができてしまいます。

理由3: コードの「つながり」が分かりにくい

FileReader reader = new FileReader("file.txt");
// ...100行の処理...
reader.close();

AIから見ると:

  • new FileReaderを見る → ファイル読み込みパターン!
  • 100行先のclose()を見る → 閉じる処理パターン!
  • でも、この2つが対になっていることに気づきにくい

人間なら「開いたら閉じる」とセットで覚えますが、AIはそれぞれ別のパターンとして見てしまうことがあります。

理由4: Streamの特殊性を理解していない

Files.lines(Paths.get("file.txt"))  // これもリソース!

一見、ただのメソッド呼び出しに見えますが、実は内部でファイルを開いています
AIはこの「見えないリソース」に気づきにくいのです。

実際に起きる問題

問題1: プログラムが動かなくなる

// ❌ 危険なコード例
public void processLotsOfFiles() {
    for (int i = 0; i < 10000; i++) {
        try {
            FileInputStream file = new FileInputStream("data" + i + ".txt");
            // 処理...
            // file.close() を忘れている
        } catch (Exception e) {
            System.out.println("エラー: " + e);
        }
    }
}

何が起きるか:

  1. 最初の数百ファイルは問題なく動く
  2. だんだん遅くなる
  3. 突然「Too many open files」というエラーが出る
  4. プログラムが止まる

原因: OSには「同時に開けるファイル数」に上限がある(例: 1024個)

問題2: メモリが足りなくなる

閉じられていないリソースは、メモリを占有し続けます。

開いているファイル: 1個 → メモリ使用量: 100MB
開いているファイル: 10個 → メモリ使用量: 1GB
開いているファイル: 100個 → メモリ使用量: 10GB ← メモリ不足!

問題3: 他の人が困る(Webアプリの場合)

学園祭のWebサイトを作ったとします。

// ❌ 危険なコード
public Student getStudent(int id) {
    try {
        Connection db = dataSource.getConnection();
        // 処理...
        // 接続を閉じていない!
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

何が起きるか:

  • アクセス1人目: 動く
  • アクセス2人目: 動く
  • アクセス10人目: 動く
  • ...
  • アクセス100人目: 「接続できません」エラー

原因: データベース接続プールの上限(例: 100個)を超えた

AIに正しいコードを書いてもらうコツ

コツ1: 具体的に指示する

悪い例:

ファイルを読むコードを書いて

良い例:

ファイルを読むJavaコードを書いてください。
以下の条件を守ってください:
- try-with-resourcesを使う
- エラーハンドリングを含める
- Java 17の書き方で
- リソースリークがないようにする

コツ2: 「最新の書き方で」と伝える

最新のJavaのベストプラクティスに従って、
try-with-resourcesを使ってファイルを読むコードを書いてください

コツ3: Streamを扱う時は特に注意

Files.lines()を使ってファイルを読むコードを書いてください。
Streamは必ずtry-with-resourcesで閉じてください。

コツ4: レビューを依頼する

このコードをレビューして、リソースリークがないか確認してください:
[コードを貼り付け]

自分でチェックする方法

簡単チェックリスト

AIが生成したコードを受け取ったら、以下を確認しましょう:

レベル1: 基本のチェック

  • ファイルを開いている?(FileReader、FileInputStreamなど)
  • try-with-resourcesを使っている?
  • tryブロックのでリソースを作っている?

レベル2: Stream のチェック

  • Files.lines()を使っている?
  • Files.walk()を使っている?
  • これらをtry-with-resourcesで囲んでいる?

レベル3: データベースのチェック

  • データベース接続を使っている?
  • Connection、Statement、ResultSetの全てを閉じている?

レベル4: 複雑なケースのチェック

  • リソースを返り値として返していない?
  • 複数のリソースを全てtry()の中に入れている?
  • 自作クラスがAutoCloseableを実装している?

危険なコードパターン

こんなコードを見たら要注意:

// ⚠️ パターン1: tryの外でリソース作成
BufferedReader reader = new BufferedReader(...);
try (reader) { ... }

// ⚠️ パターン2: Streamを閉じていない
Files.lines(Paths.get("file.txt")).forEach(...);

// ⚠️ パターン3: 一部だけtryに入れている
FileInputStream in = new FileInputStream(...);  // 閉じられない
try (FileOutputStream out = ...) { ... }

// ⚠️ パターン4: リソースを返している
public BufferedReader getReader() {
    try (BufferedReader r = ...) {
        return r;  // 危険!
    }
}

ツールに助けてもらう

IntelliJ IDEAの警告機能

IntelliJ IDEA(人気のJava開発ツール)は、リソースの閉じ忘れを自動的に警告してくれます。

FileReader reader = new FileReader("file.txt"); // ← 黄色い波線が出る
// マウスを乗せると「Resource 'reader' is never closed」と表示される

警告が出たら

  1. 電球アイコンをクリック
  2. 「Surround with try-with-resources」を選択
  3. 自動的に正しいコードに修正される!

その他の便利ツール(無料)

ツール名 何をしてくれる?
SpotBugs コードの問題を自動検出
SonarLint リアルタイムでコードを分析
Checkstyle コーディング規約をチェック
Error Prone Googleが作った静的解析ツール

これらをインストールすると、コードを書いている最中にリソースの閉じ忘れを教えてくれます

実践:良いコードと悪いコードの比較

例1: ファイル読み込み

// ❌ 悪いコード
public void readFileBad(String fileName) throws IOException {
    FileReader fr = new FileReader(fileName);
    BufferedReader br = new BufferedReader(fr);
    String line = br.readLine();
    System.out.println(line);
    // 閉じていない
}

// ✅ 良いコード
public void readFileGood(String fileName) throws IOException {
    try (BufferedReader br = new BufferedReader(new FileReader(fileName))) {
        String line = br.readLine();
        System.out.println(line);
    }
}

例2: ファイルコピー

// ❌ 悪いコード
public void copyFileBad(String from, String to) throws IOException {
    FileInputStream in = new FileInputStream(from);
    try (FileOutputStream out = new FileOutputStream(to)) {
        byte[] buffer = new byte[1024];
        int length;
        while ((length = in.read(buffer)) > 0) {
            out.write(buffer, 0, length);
        }
    }
    // inが閉じられていない
}

// ✅ 良いコード
public void copyFileGood(String from, String to) throws IOException {
    try (
        FileInputStream in = new FileInputStream(from);
        FileOutputStream out = new FileOutputStream(to)
    ) {
        byte[] buffer = new byte[1024];
        int length;
        while ((length = in.read(buffer)) > 0) {
            out.write(buffer, 0, length);
        }
    }
}

例3: ファイルの行数カウント

// ❌ 悪いコード
public long countLinesBad(String fileName) throws IOException {
    return Files.lines(Paths.get(fileName)).count();
    // Streamが閉じられていない
}

// ✅ 良いコード
public long countLinesGood(String fileName) throws IOException {
    try (Stream<String> lines = Files.lines(Paths.get(fileName))) {
        return lines.count();
    }
}

例4: データベース検索

// ❌ 悪いコード
public User getUserBad(int id) throws SQLException {
    try (Connection conn = dataSource.getConnection()) {
        PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE id = ?");
        stmt.setInt(1, id);
        ResultSet rs = stmt.executeQuery();
        if (rs.next()) {
            return new User(rs.getString("name"));
        }
    }
    return null;
    // stmtとrsが閉じられていない
}

// ✅ 良いコード
public User getUserGood(int id) throws SQLException {
    try (
        Connection conn = dataSource.getConnection();
        PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE id = ?")
    ) {
        stmt.setInt(1, id);
        try (ResultSet rs = stmt.executeQuery()) {
            if (rs.next()) {
                return new User(rs.getString("name"));
            }
        }
    }
    return null;
}

まとめ

覚えておきたいポイント

  1. リソースは「借りたら返す」: ファイル、データベース接続などは必ず閉じる
  2. try-with-resourcesを使おう: 自動的に閉じてくれて安全
  3. リソースはtry()の中で作る: 外で作ってはいけない
  4. Streamも忘れずに: Files.lines()、Files.walk()も閉じる
  5. 全部のリソースを管理: 一部だけでは不十分
  6. リソースを返り値にしない: 閉じられてしまう
  7. AutoCloseableを実装: 自作クラスも自動で閉じられるように
  8. AIのコードは完璧じゃない: 必ずチェックする
  9. ツールを活用: IDEの警告機能が助けてくれる

try-with-resources の基本ルール

// ✅ これが基本形!
try (リソースの作成) {
    // リソースを使った処理
}  // ← ここで自動的に閉じられる

守るべきルール

  • ✅ リソースは必ずtry()ので作る
  • ✅ 複数のリソースは全てtry()に入れる
  • ✅ Streamも必ずtry-with-resourcesで囲む
  • ✅ データベースはConnection、Statement、ResultSet全てを管理
  • ❌ リソースを返り値として返さない

AIとの上手な付き合い方

AIは素晴らしい「アシスタント」です。でも、最終的な責任はプログラマー(あなた)にあります

  • ✅ AIが生成したコードは必ず確認する
  • ✅ 分からない部分は調べる・質問する
  • ✅ 動いたからOKではなく、正しいか考える
  • ✅ 小さなプログラムでも丁寧に書く習慣をつける
  • ✅ リソース管理は特に注意深くチェックする

これらを意識すれば、AIを使いこなしながら、良いプログラマーになれます!

もっと学びたい人へ

おすすめの学習リソース

  • 📘 Oracle Java Tutorials - try-with-resources
  • 📕 書籍『スッキリわかるJava入門』
  • 📗 書籍『Effective Java 第3版』(少し難しいけど名著)
  • 🎥 YouTube: Java入門講座(検索してみてください)

次のステップ

  1. 自分の過去のコードを見直してみる
  2. try-with-resourcesを使って書き直してみる
  3. 友達のコードをレビューしあう
  4. GitHubで有名なプロジェクトのコードを読んでみる
  5. IntelliJ IDEAやEclipseの警告機能を活用する

最後まで読んでくださり、ありがとうございました!

質問や感想があれば、コメント欄で教えてください 😊

いいね👍やストック📚もお願いします!


参考文献

  • Oracle公式ドキュメント
  • Effective Java, Third Edition
  • Java言語仕様書
  • JLS 14.20.3 - try-with-resources

#Java #初心者向け #プログラミング #AI #ChatGPT #学習メモ #try-with-resources #リソース管理

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?