プログラミング: 2026年8月アーカイブ

Java のラムダ式について考える」ってエントリーで悟空ばりに「オラっ、ラムダ式わかってきたぞ!」とか言ってたのに、参考にした ItemProcessor のコードにあった「return item -> {...}」って記述を「無名クラスですか?」って Gemini に聞いて「違います。それはラムダ式です」とか言われてしまった。

ラムダ式には必ず変数名がつくのかと思ってて、例えば「return HogeDto getItem = item -> {...}」とか書かれていたら「お、ラムダ式」って思ったんだけど、この「getItem =」って変数名の部分が無いだけでパッと見てラムダ式に見えないという(^^;;; Java 素人でごめん。

今まで、Java の修正案件は何度も受けてきたけど、今までの経験から「そんなに Java よく知らないけど、何となくこなしてこれた」のよ。こういうのをベテランの知識と経験という(笑)。いや、とか言ってる場合じゃないんだけど(笑)。Spring Batch というフレームワークを使ってとはいえ、今回初めて Java の「新規開発案件」に参加して、Java の言語仕様の知識の乏しさに苦労してますわ。へっへっへ(笑ってる場合か)

でも、まあ、Java 云々の前に、return と組み合わせたら、そりゃどんなプログラムでも一旦変数にセットするような書き方せんからな。

int a = b + c;
return a;

は、

return b + c;

だからな。Java だろうがなんだろうが。だから、

HogeDto getItem = item -> {...};
return getItem;

は、

return item -> {...};

だよな。オラ、またひとつ偉くなったぞ!

<追記>
おい、このエントリーを見て「はっ?こんなレベルで悩んでるようなジジィがいっぱしのプログラマ騙ってんじゃねえよ、このボケが。」って思ったお前、お前のことだよ。やかましいわ。呪ってやる。つーか、もう呪ってやった。お前は 3ヶ月以内に自動車事故にあって脳に障害が残り、「ラムダ式」って聞くたびに盛大に小便と大便を同時に撒き散らす身体になるからな。つーか、もうなった。ついでにお前の家族全員そうなった。人生お疲れ様でした。
ラムダ式がよくわからない。

「Java8で導入された新しい文法。ローカルクラスと無名クラスを利用して記述する内容を省略して処理記述を簡潔にする。」ということだが、そもそも無名クラスもよくわからない(笑)。そんなものを使わなくてもなんとかなってきたからな。

無名クラス(匿名クラス / Anonymous Class)とは「名前を持たない使い捨てのクラスのことです。」と言われても、なんで無名クラスを使わないといけないのか、その状況がパッと頭に浮かばない。
多分、無名クラス(からさらに発展してラムダ式)を使った方がすっきりしたコードが書けるんだろうが、無名クラスでの処理が唯一の解ではないので、上に書いたように「別の書き方」で凌いできたのが俺である(笑)

ラムダ式はインターフェース(関数型インターフェース)を実装したオブジェクトを生成するもの。「ラムダ式を、関数型インターフェースの型として扱う」という言い方もあるようだが、「インタフェースの実装」の方が俺的にはしっくりくる。

インターフェースはわかる。標準インタフェースはよく使うし。
インタフェースとは「クラスが持つべき『メソッドの型(名前、引数、戻り値)』だけを定義した設計図のようなもの」である。

ラムダ式は「抽象メソッドが 1つだけ」のインタフェースの「その 1つだけの抽象メソッドを規定して」実装するものだ。

また新しい言葉が出てきたぞ。「抽象メソッド」とは「中身の無い名前だけのメソッド」だ。

例えば、以下のようなインタフェース。

interface Greeting {
    void sayHello();
}

sayHello が「抽象メソッド」である。「引数の無い、戻り値もない sayHello という名前のメソッド」が存在しているだけのインタフェースが。
これを実装するラムダ式が、

Greeting greeting = () -> System.out.println("こんにちは!");

となる。

greeting というのが、このラムダ式の変数名である。
()というのは引数がないということ。Greeting インタフェースの sayHello メソッドには引数がないからな。

System.out.println("こんにちは!"); というのが、sayHello という「中身の無いメソッド」に与える「中身」である。sayHello メソッドは「こんにちは!」という文字列を標準出力に出力する処理であると規定している。

これでこのラムダ式の変数 greeting に、上記内容のメソッド sayHello が設定される。なのでこの変数.メソッドを

greeting.sayHello();

このように書いてやれば、標準出力(画面など)に「こんにちわ!」という文字列が表示される。

引数、戻り値がある例だと、

public interface BiFunction<T, U, R> {
    R apply(T t, U u);
}

こういう「2つの引数(T型, U型)を受け取り、R型の結果を返す抽象メソッド apply」を実装するラムダ式は、例えば

BiFunction<String, Integer, Integer> stringAdder = (strNum, num) -> {
    return Integer.parseInt(strNum) + num;
};

このようになる。引数として文字列と数値を渡して、引数の文字列を数値変換(Integer.parseInt(strNum))して引数の数値と足し、数値の戻り値を返すラムダ式だ。

これで、stringAdder という変数に実装した apply メソッドを

int sum = stringAdder.apply("100", 50);

このようにして呼べば、変数 sum には 150 がセットされる。

おお、なんかラムダ式わかってきたぞ。なるほど、なるほど。

無名クラスで書くと

BiFunction<String, Integer, Integer> stringAdder = new BiFunction<String, Integer, Integer>() {
    @Override
    public Integer apply(String strNum, Integer num) {
        return Integer.parseInt(strNum) + num;
    }
};

こうなるので、確かにラムダ式の方がすっきりはするな。

ちなみに <T> だ <R> という引数は、ジェネリクス(Generics / 型引数)というもの。俺は C#.NET の開発のときに初めて遭遇したが、ほんま最初は「何、これ?」だったな(笑)
引数や戻り値の型を指定しないインタフェースで使われる仮の名前だ。<T>じゃなく<X>とか<Z>って書いても良い。仮の名前だから。でも、まあ、<T>が一般的な引数の仮の名前、<R>は戻り値の仮の名前絵として使われることが多い。

BiFunction<T, U, R> というのは、引数<T><U>と戻り値<R>の型はなんでもいいので、呼ぶとき指定してねってことである。

ラムダ式の勉強をするだけで、色々な勉強ができるな(笑)
Spring Boot 4 で作成したファイルダウンロード画面。「ダウンロード」ボタン押下で DB を読んで CSV ファイルを生成し、そのファイルがクライアントPCにダウンロードされる。

Controller はこんな感じ。

    // ボタン押下で実行される処理
    @PostMapping("/createFile")
    public ResponseEntity<Resource> downloadFile() throws Exception {
        Path path    = HogeService.make_file_path(); // ダウンロードされるファイルを作成するパス
        HogeService.logic_make(path.toString());     // ダウンロードファイルを作成

        Resource resource    = new FileSystemResource(path);

        HttpHeaders headers    = new HttpHeaders();
        headers.add(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + resource.getFilename() + "\"");
        headers.add(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.CONTENT_DISPOSITION);

        return ResponseEntity.ok()
                .contentType(getContentType(path))
                .contentLength(resource.contentLength())
                .headers(headers)
                .body(resource);
    }

    // ContentType の取得
    private MediaType getContentType(Path path) throws IOException {
        try {
            return MediaType.parseMediaType(Files.probeContentType(path));
        }
        catch (IOException e) {
            return MediaType.APPLICATION_OCTET_STREAM;
        }
    }

これで、例えば

    <form action="/createFile" method="post" id="form" name="form">
        <input type="submit" name="download-btn" class="btn-primary" id="download-btn" value="ダウンロード">

みたいな形にすれば、「ダウンロード」ボタン押下でブラウザのダウンロード窓が開いてダウンロードは行われる。

しかし、例えばダウンロードに失敗したときは Web 画面上にエラーメッセージを表示したいとか、成功したら画面に「正常にダウンロードできました」などと表示したい場合はこれではダメだ。

Controller の各メソッドで返却できる値は 1種類である。
上の例のようにデータ(ダウンロードファイル)を出力するか(ダウンロード)、HTML を出力するか(画面表示)、生のテキストや JSON データなどを出力するか(API など)・・・のいずれかひとつのことしかできない。
「先にファイルを出力して、その次に結果を伝える HTML を出力する」という二段構えの出力はできない。ファイルを出力すれば画面(HTML)は表示できないし、画面(HTML)を表示(出力)すればファイルは出力できない。

・・・が、ファイルダウンロードをして結果を Web 画面に表示したい要件ってあるよねえ。

そういうときは、直接ファイルダウンロードの画面を呼び出すのではなく、JavaScript の中でファイルダウンロード画面を呼び出し一旦ファイルを受信しするようにする。そして、そのデータをクライアントに出力し、その後画面の編集をすればいい。JavaScript で処理すれば「ファイル出力か HTML 画面表示かどちらかしかできない」なんて制限ないからね。

具体的には、↓こんな感じの JavaScript。

    document.getElementById('download-btn').addEventListener('click', async (e) => {

        e.preventDefault();

        const btn    = document.getElementById('download-btn');
        const errMesg = document.getElementById('error_msg');
        const sucMesg = document.getElementById('success_msg');

        errMesg.style.display = 'none';  // メッセージ非表示
        sucMesg.style.display = 'none';  // メッセージ非表示

        try {
            btn.disabled = true;  // ボタンが押されたら、二度押し防止のため非活性化

            // ダウンロード処理呼び出し
            const action_url    = /*[[@{/createFile}]]*/ '';
            const response = await fetch(action_url, {  // fetch(非同期通信(HTTPリクエスト))でダウンロードデータ取得
                method: 'POST'
            });

            if (!response.ok) {  // ダウンロード画面からデータが取れなければ例外発生
                throw new Error()
            }

            const blob = await response.blob();  // データをBlob(バイナリの塊)として取得

            const disposition  = response.headers.get('Content-Disposition') ;  // レスポンスヘッダーからファイル名取得
            let filename = 'download.csv';  // ファイル名が取れなかったときのディフォルト値

            if (disposition && disposition.indexOf('attachment'!== -1)) {
                const fileMatch        = /filename="?([^; \n"]*)"?/.exec(disposition);  // 正規表現でファイル名取得
                if (fileMatch && fileMatch[1]) {
                    filename    = fileMatch[1];
                }
            }

            const url  = window.URL.createObjectURL(blob);  // ダウンロード用の疑似リンク作成
            const a    = document.createElement('a');     // 疑似アンカータグ作成

            a.href     = url;  // アンカータグの href 属性に疑似URLを紐づける
            a.download = filename;  // アンカータグの download 属性にファイル名をセット
            document.body.appendChild(a);  // 作成した疑似アンカータグを一時的に(操作のため)実体化
            a.click();  // 実体化したアンカータグを画面の前の人間に代わってクリック(ダウンロードスタート)

            a.remove();  // もう必要ないのでアンカータグ削除
            window.URL.revokeObjectURL(url);  // メモリに残った疑似リンク(blobデータ含む)を削除

            btn.disabled = false;  // ボタン使用可能に
            sucMesg.textContent = "正常にダウンロードが終了しました。";
            sucMesg.style.display = 'block';  // メッセージをセットして表示

        }
        catch(error) {
            btn.disabled    = false;  // ボタン使用可能に
            errMesg.textContent = "ダウンロードに失敗しました。";
            errMesg.style.display = 'block';  // メッセージをセットして表示
        }
        finally {

        }
    });

ダウンロードボタンが押されたらイベントリスナーによりこの処理が実行される。
コメントに書いているとおり、JavaScript 内で(画面の前の人間には見えないところで)実際のダウンロード画面にアクセスしデータを取得。そのデータをダウンロードするための疑似リンクを作って、そのリンクに(画面の前の人に代わって)アクセスして画面の前の人のパソコンにデータがダウンロードされるという動き。

なんでこういうことをするかというと、JavaScript 内でダウンロードの成否を判断して処理を行うため。

ダウンロードに失敗したときは例外を発生させ「失敗」メッセージを表示。最後まで問題なく処理が終われば「正常」メッセージを表示・・・となる。(直接ブラウザでダウンロードをした場合は、成否の情報は画面(JavaScript)には伝わってこないので何もできない。)

Controller のファイルダウンロードのメソッド(上の例では downloadFile メソッド)を修正する必要はない。

このアーカイブについて

このページには、2026年8月以降に書かれたブログ記事のうちプログラミングカテゴリに属しているものが含まれています。

前のアーカイブはプログラミング: 2026年7月です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

月別 アーカイブ

電気ウナギ的○○ mobile ver.

携帯版「電気ウナギ的○○」はこちら