第8章: EL(式言語)とJSTL入門

ELとJSTLによるJSPの高度な活用

この章で学ぶこと
  • EL(Expression Language)の基本的な構文と使い方
  • ELを使用したスコープデータへのアクセス方法
  • JSTLタグライブラリの概要と設定方法
  • JSTLによる繰り返し処理と条件分岐の実装
  • ELとJSTLを組み合わせた実践的なJSP開発手法

8.1 EL(Expression Language)とは

EL(Expression Language)は、JSPページからJavaコードを排除し、より読みやすく保守しやすいページを作成するために導入された式言語です。${}の構文を使用して、スコープ内のデータに簡潔にアクセスできます。

flowchart TD A[従来のJSP] --> B["スクリプトレット
(<% ... %>)"] A --> C["式
(<%= ... %>)"] D[EL導入後のJSP] --> E["EL式
(\${...})"] D --> F[JSTL] B --> G[コードが複雑
保守が困難] C --> G E --> H[シンプルな構文
高い可読性] F --> H G --> I[Java知識必須
デザイナーが編集困難] H --> J[宣言的な記述
チーム開発に適している]

ELの利点

  • 簡潔な構文: スクリプトレットより短くて読みやすい
  • 自動型変換: 文字列、数値、ブール値の自動変換
  • null安全: nullポインタ例外が発生しにくい
  • スコープの自動検索: page → request → session → application の順で検索
  • デザイナーフレンドリー: Javaコードが含まれないため編集しやすい

ELの基本構文

構文 説明
${変数名} スコープ内の変数にアクセス ${username}
${object.property} オブジェクトのプロパティにアクセス ${user.name}
${object["property"]} ブラケット記法でのアクセス ${user["name"]}
${array[index]} 配列の要素にアクセス ${colors[0]}
${map.key} Mapのキーにアクセス ${config.maxSize}
実習 8-1: ELの基本使用方法

スクリプトレットとELの書き方を比較し、ELの基本的な使い方を学習します。

手順1: EL基本動作確認(elBasics.jsp)
<%@ page language="java" contentType="text/html; charset=UTF-8" 
    pageEncoding="UTF-8" import="java.util.*, com.example.beans.User" %>
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>EL(Expression Language)基本動作</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        .comparison-table { width: 100%; border-collapse: collapse; margin: 15px 0; }
        .comparison-table th, .comparison-table td { border: 1px solid #ddd; padding: 12px; text-align: left; }
        .comparison-table th { background-color: #f57c00; color: white; }
        .old-way { background-color: #ffebee; }
        .new-way { background-color: #e8f5e8; }
        .section { background-color: #f5f5f5; padding: 15px; margin: 15px 0; border-radius: 5px; }
    </style>
</head>
<body>
    <h1>EL(Expression Language)基本動作確認</h1>
    
    <%
        // テスト用のデータをセットアップ
        
        // 基本データ型
        pageContext.setAttribute("stringValue", "Hello EL!");
        pageContext.setAttribute("numberValue", 123);
        pageContext.setAttribute("doubleValue", 45.67);
        pageContext.setAttribute("booleanValue", true);
        
        // 配列
        String[] colors = {"赤", "青", "緑", "黄", "紫"};
        pageContext.setAttribute("colors", colors);
        
        // リスト
        List<String> fruits = Arrays.asList("りんご", "みかん", "バナナ", "ぶどう");
        pageContext.setAttribute("fruits", fruits);
        
        // Map
        Map<String, String> config = new HashMap<>();
        config.put("appName", "ELデモアプリ");
        config.put("version", "1.0.0");
        config.put("author", "開発チーム");
        pageContext.setAttribute("config", config);
        
        // JavaBeans(Userクラスを使用)
        User user = new User();
        user.setUsername("田中太郎");
        user.setEmail("tanaka@example.com");
        user.setAge(28);
        pageContext.setAttribute("user", user);
        
        // 異なるスコープにデータを設定
        pageContext.setAttribute("scope", "page");
        request.setAttribute("scope", "request");
        session.setAttribute("scope", "session");
        application.setAttribute("scope", "application");
    %>
    
    <div class="section">
        <h2>1. 基本的なデータ型のアクセス</h2>
        
        <table class="comparison-table">
            <tr>
                <th>データ型</th>
                <th>従来の方法(スクリプトレット)</th>
                <th>ELを使用した方法</th>
            </tr>
            <tr>
                <td>文字列</td>
                <td class="old-way"><%= pageContext.getAttribute("stringValue") %></td>
                <td class="new-way">${stringValue}</td>
            </tr>
            <tr>
                <td>整数</td>
                <td class="old-way"><%= pageContext.getAttribute("numberValue") %></td>
                <td class="new-way">${numberValue}</td>
            </tr>
            <tr>
                <td>小数</td>
                <td class="old-way"><%= pageContext.getAttribute("doubleValue") %></td>
                <td class="new-way">${doubleValue}</td>
            </tr>
            <tr>
                <td>ブール値</td>
                <td class="old-way"><%= pageContext.getAttribute("booleanValue") %></td>
                <td class="new-way">${booleanValue}</td>
            </tr>
        </table>
    </div>
    
    <div class="section">
        <h2>2. 配列とリストのアクセス</h2>
        
        <h3>配列の要素アクセス</h3>
        <ul>
            <li>最初の色: ${colors[0]}</li>
            <li>2番目の色: ${colors[1]}</li>
            <li>最後の色: ${colors[4]}</li>
        </ul>
        
        <h3>リストの要素アクセス</h3>
        <ul>
            <li>最初のフルーツ: ${fruits[0]}</li>
            <li>2番目のフルーツ: ${fruits[1]}</li>
            <li>3番目のフルーツ: ${fruits[2]}</li>
        </ul>
        
        <h3>配列・リストの長さ</h3>
        <ul>
            <li>配列の長さ: ${colors.length}</li>
            <li>リストのサイズ: ${fruits.size()}</li>
        </ul>
    </div>
    
    <div class="section">
        <h2>3. Mapのアクセス</h2>
        
        <table class="comparison-table">
            <tr>
                <th>アクセス方法</th>
                <th>記述</th>
                <th>結果</th>
            </tr>
            <tr>
                <td>ドット記法</td>
                <td>\${config.appName}</td>
                <td>${config.appName}</td>
            </tr>
            <tr>
                <td>ブラケット記法</td>
                <td>\${config["version"]}</td>
                <td>${config["version"]}</td>
            </tr>
            <tr>
                <td>ブラケット記法</td>
                <td>\${config["author"]}</td>
                <td>${config["author"]}</td>
            </tr>
        </table>
    </div>
    
    <div class="section">
        <h2>4. JavaBeansのプロパティアクセス</h2>
        
        <table class="comparison-table">
            <tr>
                <th>プロパティ</th>
                <th>従来の方法</th>
                <th>ELを使用した方法</th>
            </tr>
            <tr>
                <td>ユーザー名</td>
                <td class="old-way"><%= ((User)pageContext.getAttribute("user")).getUsername() %></td>
                <td class="new-way">${user.username}</td>
            </tr>
            <tr>
                <td>メールアドレス</td>
                <td class="old-way"><%= ((User)pageContext.getAttribute("user")).getEmail() %></td>
                <td class="new-way">${user.email}</td>
            </tr>
            <tr>
                <td>年齢</td>
                <td class="old-way"><%= ((User)pageContext.getAttribute("user")).getAge() %></td>
                <td class="new-way">${user.age}</td>
            </tr>
        </table>
    </div>
    
    <div class="section">
        <h2>5. スコープの自動検索</h2>
        <p>ELは page → request → session → application の順でスコープを検索します。</p>
        
        <ul>
            <li>自動検索結果: ${scope} (pageスコープの値が優先される)</li>
            <li>明示的にpageスコープ: ${pageScope.scope}</li>
            <li>明示的にrequestスコープ: ${requestScope.scope}</li>
            <li>明示的にsessionスコープ: ${sessionScope.scope}</li>
            <li>明示的にapplicationスコープ: ${applicationScope.scope}</li>
        </ul>
    </div>
    
    <div class="section">
        <h2>6. null値の安全な処理</h2>
        
        <%
            // null値をセット
            pageContext.setAttribute("nullValue", null);
        %>
        
        <p>null値へのアクセス: '${nullValue}' (空文字が表示される)</p>
        <p>存在しない属性: '${nonExistentValue}' (空文字が表示される)</p>
        <p>nullオブジェクトのプロパティ: '${nullValue.someProperty}' (エラーにならない)</p>
    </div>
    
    <div class="section">
        <h2>7. 算術演算と論理演算</h2>
        
        <h3>算術演算</h3>
        <ul>
            <li>加算: ${numberValue + 10} = <%= 123 + 10 %></li>
            <li>減算: ${numberValue - 20} = <%= 123 - 20 %></li>
            <li>乗算: ${numberValue * 2} = <%= 123 * 2 %></li>
            <li>除算: ${numberValue / 10} = <%= 123 / 10 %></li>
        </ul>
        
        <h3>比較演算</h3>
        <ul>
            <li>等しい: ${numberValue == 123} = <%= 123 == 123 %></li>
            <li>大きい: ${numberValue > 100} = <%= 123 > 100 %></li>
            <li>小さい: ${numberValue < 200} = <%= 123 < 200 %></li>
        </ul>
        
        <h3>論理演算</h3>
        <ul>
            <li>AND: ${booleanValue && (numberValue > 100)} = <%= true && (123 > 100) %></li>
            <li>OR: ${booleanValue || (numberValue < 100)} = <%= true || (123 < 100) %></li>
            <li>NOT: ${!booleanValue} = <%= !true %></li>
        </ul>
    </div>
</body>
</html>
期待される結果

従来のスクリプトレットとELの比較を通して、ELの簡潔さとnull安全性を確認できます。

8.2 JSTL(JavaServer Pages Standard Tag Library)入門

JSTLは、JSPでよく使用される処理(繰り返し、条件分岐、フォーマット等)を標準化したタグライブラリです。ELと組み合わせることで、Javaコードを含まない宣言的なJSPページを作成できます。

flowchart TD A[JSTL主要ライブラリ] --> B[Core
タグライブラリ] A --> C[Formatting
タグライブラリ] A --> D[Functions
タグライブラリ] A --> E[SQL
タグライブラリ] B --> F["条件分岐
(<c:if>, <c:choose>)"] B --> G["繰り返し
(<c:forEach>)"] B --> H["URL操作
(<c:url>, <c:redirect>)"] B --> I["変数操作
(<c:set>, <c:remove>)"] C --> J["日付フォーマット
(<fmt:formatDate>)"] C --> K["数値フォーマット
(<fmt:formatNumber>)"] C --> L["国際化
(<fmt:message>)"] D --> M["文字列操作
(fn:length, fn:substring)"] D --> N["コレクション操作
(fn:contains, fn:join)"]

JSTLライブラリの種類

ライブラリ プレフィックス URI 主な用途
Core c http://java.sun.com/jsp/jstl/core 条件分岐、繰り返し、変数操作
Formatting fmt http://java.sun.com/jsp/jstl/fmt 日付・数値フォーマット、国際化
Functions fn http://java.sun.com/jsp/jstl/functions 文字列操作、コレクション操作
SQL sql http://java.sun.com/jsp/jstl/sql データベース操作(非推奨)

8.3 JSTLによる制御構造の実装

JSTLのCoreライブラリを使用して、条件分岐と繰り返し処理を実装する方法を学習します。

実習 8-2: JSTLによる制御構造の実践

JSTL Coreタグを使用した条件分岐と繰り返し処理を実装します。

手順1: JSTL制御構造の実装(jstlControls.jsp)
<%@ page language="java" contentType="text/html; charset=UTF-8" 
    pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>

<%@ page import="java.util.*, com.example.beans.User" %>

<%
    // テストデータの準備
    
    // ユーザーリスト
    List<User> users = new ArrayList<>();
    users.add(new User("user001", "田中太郎", "tanaka@example.com"));
    users.add(new User("user002", "佐藤花子", "sato@example.com"));
    users.add(new User("user003", "山田次郎", "yamada@example.com"));
    users.add(new User("user004", "鈴木美咲", "suzuki@example.com"));
    
    // 各ユーザーの年齢を設定
    users.get(0).setAge(25);
    users.get(1).setAge(30);
    users.get(2).setAge(17);  // 未成年
    users.get(3).setAge(35);
    
    request.setAttribute("users", users);
    
    // 数値データ
    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 10, 15, 20);
    request.setAttribute("numbers", numbers);
    
    // 商品カテゴリMap
    Map<String, String> categories = new HashMap<>();
    categories.put("BOOK", "書籍");
    categories.put("ELECTRONIC", "電子機器");
    categories.put("CLOTHING", "衣類");
    categories.put("FOOD", "食品");
    request.setAttribute("categories", categories);
    
    // 設定値
    request.setAttribute("currentUser", "admin");
    request.setAttribute("debugMode", true);
    request.setAttribute("maxCount", 10);
    request.setAttribute("message", "JSTL制御構造のテストページ");
%>

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>JSTL制御構造の実践</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        .section { background-color: #f9f9f9; padding: 15px; margin: 15px 0; border-radius: 5px; border-left: 4px solid #f57c00; }
        .user-card { background: white; border: 1px solid #ddd; padding: 15px; margin: 10px 0; border-radius: 5px; }
        .adult { border-left: 4px solid #4caf50; }
        .minor { border-left: 4px solid #f44336; }
        .number-grid { display: grid; grid-template-columns: repeat(5, 1fr); gap: 10px; margin: 15px 0; }
        .number-cell { padding: 10px; text-align: center; border: 1px solid #ddd; border-radius: 3px; }
        .even { background-color: #e3f2fd; }
        .odd { background-color: #fff3e0; }
        .table { width: 100%; border-collapse: collapse; margin: 15px 0; }
        .table th, .table td { border: 1px solid #ddd; padding: 8px; text-align: left; }
        .table th { background-color: #f57c00; color: white; }
    </style>
</head>
<body>
    <h1>JSTL制御構造の実践</h1>
    
    <!-- 1. 変数の設定と出力 -->
    <div class="section">
        <h2>1. 変数の設定と出力(<c:set>, <c:out>)</h2>
        
        <!-- 変数の設定 -->
        <c:set var="siteName" value="JSTLデモサイト" />
        <c:set var="version" value="2.0" />
        <c:set var="currentYear" value="<%= java.time.Year.now().getValue() %>" />
        
        <p>サイト名: <c:out value="${siteName}" /></p>
        <p>バージョン: <c:out value="${version}" /></p>
        <p>現在年: <c:out value="${currentYear}" /></p>
        
        <!-- XSS対策のテスト -->
        <c:set var="userInput" value="&lt;script&gt;alert('test')&lt;/script&gt;" />
        <p>ユーザー入力(エスケープあり): <c:out value="${userInput}" /></p>
        <p>ユーザー入力(エスケープなし): <c:out value="${userInput}" escapeXml="false" /></p>
    </div>
    
    <!-- 2. 条件分岐(<c:if>) -->
    <div class="section">
        <h2>2. 条件分岐(<c:if>)</h2>
        
        <c:if test="${currentUser == 'admin'}">
            <p style="color: blue;">✅ 管理者としてログインしています</p>
        </c:if>
        
        <c:if test="${debugMode}">
            <div style="background: #ffffcc; padding: 10px; border: 1px solid #cccc00;">
                <strong>デバッグモード:</strong> デバッグ情報が表示されます<br>
                リクエストURI: ${pageContext.request.requestURI}<br>
                セッションID: ${pageContext.session.id}
            </div>
        </c:if>
        
        <c:if test="${empty message}">
            <p style="color: red;">メッセージが設定されていません</p>
        </c:if>
        
        <c:if test="${not empty message}">
            <p style="color: green;">メッセージ: ${message}</p>
        </c:if>
    </div>
    
    <!-- 3. 複数条件分岐(<c:choose>, <c:when>, <c:otherwise>) -->
    <div class="section">
        <h2>3. 複数条件分岐(<c:choose>, <c:when>, <c:otherwise>)</h2>
        
        <h3>時間帯による挨拶</h3>
        <c:set var="currentHour" value="<%= java.time.LocalTime.now().getHour() %>" />
        <c:choose>
            <c:when test="${currentHour < 6}">
                <p style="color: navy;">深夜ですね(${currentHour}時)</p>
            </c:when>
            <c:when test="${currentHour < 12}">
                <p style="color: orange;">おはようございます!(${currentHour}時)</p>
            </c:when>
            <c:when test="${currentHour < 18}">
                <p style="color: green;">こんにちは!(${currentHour}時)</p>
            </c:when>
            <c:otherwise>
                <p style="color: purple;">こんばんは!(${currentHour}時)</p>
            </c:otherwise>
        </c:choose>
        
        <h3>ユーザーの役割判定</h3>
        <c:choose>
            <c:when test="${currentUser == 'admin'}">
                <div style="background: #e8f5e8; padding: 10px; border-radius: 5px;">
                    <h4>管理者メニュー</h4>
                    <ul>
                        <li>ユーザー管理</li>
                        <li>システム設定</li>
                        <li>ログ閲覧</li>
                    </ul>
                </div>
            </c:when>
            <c:when test="${currentUser == 'user'}">
                <div style="background: #e3f2fd; padding: 10px; border-radius: 5px;">
                    <h4>一般ユーザーメニュー</h4>
                    <ul>
                        <li>プロフィール編集</li>
                        <li>パスワード変更</li>
                    </ul>
                </div>
            </c:when>
            <c:otherwise>
                <div style="background: #fff3e0; padding: 10px; border-radius: 5px;">
                    <h4>ゲストユーザー</h4>
                    <p>ログインしてください。</p>
                </div>
            </c:otherwise>
        </c:choose>
    </div>
    
    <!-- 4. 繰り返し処理(<c:forEach>) -->
    <div class="section">
        <h2>4. 繰り返し処理(<c:forEach>)</h2>
        
        <h3>数値の繰り返し表示</h3>
        <div class="number-grid">
            <c:forEach var="num" items="${numbers}" varStatus="status">
                <div class="number-cell ${num % 2 == 0 ? 'even' : 'odd'}">
                    ${num}
                    <small>(${status.index + 1}番目)</small>
                </div>
            </c:forEach>
        </div>
        
        <h3>カテゴリの表示</h3>
        <table class="table">
            <tr><th>コード</th><th>名前</th><th>インデックス</th></tr>
            <c:forEach var="category" items="${categories}" varStatus="loop">
                <tr>
                    <td>${category.key}</td>
                    <td>${category.value}</td>
                    <td>${loop.count}番目</td>
                </tr>
            </c:forEach>
        </table>
        
        <h3>範囲指定の繰り返し</h3>
        <p>1から10まで:
        <c:forEach var="i" begin="1" end="10" step="1">
            ${i}<c:if test="${i < 10}">, </c:if>
        </c:forEach>
        </p>
        
        <p>偶数のみ(2から20まで、ステップ2):
        <c:forEach var="i" begin="2" end="20" step="2">
            ${i}<c:if test="${i < 20}">, </c:if>
        </c:forEach>
        </p>
    </div>
    
    <!-- 5. ユーザー一覧表示(実践例) -->
    <div class="section">
        <h2>5. ユーザー一覧表示(実践例)</h2>
        
        <p>登録ユーザー数: ${fn:length(users)}人</p>
        
        <c:if test="${empty users}">
            <p style="color: red;">登録されているユーザーがいません。</p>
        </c:if>
        
        <c:if test="${not empty users}">
            <c:forEach var="user" items="${users}" varStatus="status">
                <div class="user-card ${user.age >= 20 ? 'adult' : 'minor'}">
                    <h4>${user.username}
                        <c:if test="${status.first}">
                            <span style="color: gold;">👑 最初のユーザー</span>
                        </c:if>
                        <c:if test="${status.last}">
                            <span style="color: blue;">🏁 最後のユーザー</span>
                        </c:if>
                    </h4>
                    
                    <p><strong>ID:</strong> ${user.userId}</p>
                    <p><strong>メール:</strong> ${user.email}</p>
                    <p><strong>年齢:</strong> ${user.age}歳
                        <c:choose>
                            <c:when test="${user.age >= 20}">
                                <span style="color: green;">(成人)</span>
                            </c:when>
                            <c:otherwise>
                                <span style="color: red;">(未成年)</span>
                            </c:otherwise>
                        </c:choose>
                    </p>
                    
                    <p><small>${status.count}/${fn:length(users)}番目のユーザー</small></p>
                </div>
            </c:forEach>
        </c:if>
    </div>
    
    <!-- 6. URL操作とリダイレクト -->
    <div class="section">
        <h2>6. URL操作(<c:url>)</h2>
        
        <c:url var="loginUrl" value="login.jsp">
            <c:param name="from" value="jstlControls" />
            <c:param name="time" value="${currentYear}" />
        </c:url>
        
        <p>ログインページ: <a href="${loginUrl}">${loginUrl}</a></p>
        
        <c:url var="dashboardUrl" value="dashboard.jsp" />
        <p>ダッシュボード: <a href="${dashboardUrl}">${dashboardUrl}</a></p>
    </div>
    
    <!-- 7. 変数の削除 -->
    <div class="section">
        <h2>7. 変数の削除(<c:remove>)</h2>
        
        <c:set var="tempVar" value="削除予定の変数" />
        <p>削除前: ${tempVar}</p>
        
        <c:remove var="tempVar" />
        <p>削除後: '${tempVar}' (空文字列が表示される)</p>
    </div>
</body>
</html>
期待される結果

JSTLの制御構造が正常に動作し、条件分岐と繰り返し処理による動的なコンテンツ生成を確認できます。

8.4 JSTLフォーマット機能の活用

JSTL Formattingライブラリを使用して、日付、数値、通貨の表示形式を適切にフォーマットする方法を学習します。

実習 8-3: JSTLフォーマット機能の実装

日付・数値・通貨のフォーマット機能と国際化対応を実装します。

手順1: フォーマット機能の実装(jstlFormatting.jsp)
<%@ page language="java" contentType="text/html; charset=UTF-8" 
    pageEncoding="UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<%@ taglib prefix="fn" uri="http://java.sun.com/jsp/jstl/functions" %>

<%@ page import="java.util.*, java.math.BigDecimal" %>

<%
    // テストデータの準備
    request.setAttribute("currentDate", new Date());
    request.setAttribute("price", 123456.789);
    request.setAttribute("discountRate", 0.15);
    request.setAttribute("largeNumber", 1234567890L);
    request.setAttribute("percentage", 0.856);
    
    // 商品リスト
    List<Map<String, Object>> products = new ArrayList<>();
    
    Map<String, Object> product1 = new HashMap<>();
    product1.put("name", "ノートパソコン");
    product1.put("price", 89800.0);
    product1.put("releaseDate", new Date(System.currentTimeMillis() - 86400000L * 30)); // 30日前
    products.add(product1);
    
    Map<String, Object> product2 = new HashMap<>();
    product2.put("name", "スマートフォン");
    product2.put("price", 54800.0);
    product2.put("releaseDate", new Date(System.currentTimeMillis() - 86400000L * 7)); // 7日前
    products.add(product2);
    
    Map<String, Object> product3 = new HashMap<>();
    product3.put("name", "タブレット");
    product3.put("price", 32800.0);
    product3.put("releaseDate", new Date());
    products.add(product3);
    
    request.setAttribute("products", products);
%>

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>JSTLフォーマット機能の実践</title>
    <style>
        body { font-family: Arial, sans-serif; margin: 20px; }
        .section { background-color: #f9f9f9; padding: 15px; margin: 15px 0; border-radius: 5px; border-left: 4px solid #f57c00; }
        .format-demo { background: white; padding: 10px; margin: 10px 0; border: 1px solid #ddd; border-radius: 3px; }
        .table { width: 100%; border-collapse: collapse; margin: 15px 0; }
        .table th, .table td { border: 1px solid #ddd; padding: 8px; text-align: left; }
        .table th { background-color: #f57c00; color: white; }
        .price { font-weight: bold; color: #2e7d32; }
        .date { color: #1976d2; }
        .number { color: #7b1fa2; }
    </style>
</head>
<body>
    <h1>JSTLフォーマット機能の実践</h1>
    
    <!-- 1. 日付フォーマット -->
    <div class="section">
        <h2>1. 日付フォーマット(<fmt:formatDate>)</h2>
        
        <div class="format-demo">
            <h3>基本的な日付フォーマット</h3>
            <table class="table">
                <tr><th>パターン</th><th>結果</th><th>説明</th></tr>
                <tr>
                    <td>デフォルト</td>
                    <td class="date"><fmt:formatDate value="${currentDate}" /></td>
                    <td>標準の日付フォーマット</td>
                </tr>
                <tr>
                    <td>type="date"</td>
                    <td class="date"><fmt:formatDate value="${currentDate}" type="date" /></td>
                    <td>日付のみ</td>
                </tr>
                <tr>
                    <td>type="time"</td>
                    <td class="date"><fmt:formatDate value="${currentDate}" type="time" /></td>
                    <td>時刻のみ</td>
                </tr>
                <tr>
                    <td>type="both"</td>
                    <td class="date"><fmt:formatDate value="${currentDate}" type="both" /></td>
                    <td>日付と時刻両方</td>
                </tr>
            </table>
        </div>
        
        <div class="format-demo">
            <h3>スタイル指定</h3>
            <table class="table">
                <tr><th>スタイル</th><th>結果</th><th>説明</th></tr>
                <tr>
                    <td>dateStyle="short"</td>
                    <td class="date"><fmt:formatDate value="${currentDate}" type="date" dateStyle="short" /></td>
                    <td>短い形式</td>
                </tr>
                <tr>
                    <td>dateStyle="medium"</td>
                    <td class="date"><fmt:formatDate value="${currentDate}" type="date" dateStyle="medium" /></td>
                    <td>標準形式</td>
                </tr>
                <tr>
                    <td>dateStyle="long"</td>
                    <td class="date"><fmt:formatDate value="${currentDate}" type="date" dateStyle="long" /></td>
                    <td>長い形式</td>
                </tr>
                <tr>
                    <td>dateStyle="full"</td>
                    <td class="date"><fmt:formatDate value="${currentDate}" type="date" dateStyle="full" /></td>
                    <td>完全形式</td>
                </tr>
            </table>
        </div>
        
        <div class="format-demo">
            <h3>カスタムパターン</h3>
            <table class="table">
                <tr><th>パターン</th><th>結果</th><th>用途</th></tr>
                <tr>
                    <td>yyyy-MM-dd</td>
                    <td class="date"><fmt:formatDate value="${currentDate}" pattern="yyyy-MM-dd" /></td>
                    <td>ISO日付形式</td>
                </tr>
                <tr>
                    <td>yyyy年MM月dd日</td>
                    <td class="date"><fmt:formatDate value="${currentDate}" pattern="yyyy年MM月dd日" /></td>
                    <td>日本語形式</td>
                </tr>
                <tr>
                    <td>E, MMM dd, yyyy</td>
                    <td class="date"><fmt:formatDate value="${currentDate}" pattern="E, MMM dd, yyyy" /></td>
                    <td>英語形式</td>
                </tr>
                <tr>
                    <td>HH:mm:ss</td>
                    <td class="date"><fmt:formatDate value="${currentDate}" pattern="HH:mm:ss" /></td>
                    <td>時刻(24時間表示)</td>
                </tr>
                <tr>
                    <td>yyyy/MM/dd HH:mm</td>
                    <td class="date"><fmt:formatDate value="${currentDate}" pattern="yyyy/MM/dd HH:mm" /></td>
                    <td>日時組み合わせ</td>
                </tr>
            </table>
        </div>
    </div>
    
    <!-- 2. 数値フォーマット -->
    <div class="section">
        <h2>2. 数値フォーマット(<fmt:formatNumber>)</h2>
        
        <div class="format-demo">
            <h3>基本的な数値フォーマット</h3>
            <table class="table">
                <tr><th>タイプ</th><th>元の値</th><th>フォーマット後</th><th>説明</th></tr>
                <tr>
                    <td>デフォルト</td>
                    <td>${price}</td>
                    <td class="number"><fmt:formatNumber value="${price}" /></td>
                    <td>標準数値形式</td>
                </tr>
                <tr>
                    <td>type="number"</td>
                    <td>${largeNumber}</td>
                    <td class="number"><fmt:formatNumber value="${largeNumber}" type="number" /></td>
                    <td>大きな数値の桁区切り</td>
                </tr>
                <tr>
                    <td>type="currency"</td>
                    <td>${price}</td>
                    <td class="price"><fmt:formatNumber value="${price}" type="currency" /></td>
                    <td>通貨形式</td>
                </tr>
                <tr>
                    <td>type="percent"</td>
                    <td>${percentage}</td>
                    <td class="number"><fmt:formatNumber value="${percentage}" type="percent" /></td>
                    <td>パーセント形式</td>
                </tr>
            </table>
        </div>
        
        <div class="format-demo">
            <h3>小数点制御</h3>
            <table class="table">
                <tr><th>設定</th><th>結果</th><th>説明</th></tr>
                <tr>
                    <td>maxFractionDigits="2"</td>
                    <td class="number"><fmt:formatNumber value="${price}" maxFractionDigits="2" /></td>
                    <td>最大小数点以下2桁</td>
                </tr>
                <tr>
                    <td>minFractionDigits="2"</td>
                    <td class="number"><fmt:formatNumber value="100" minFractionDigits="2" /></td>
                    <td>最小小数点以下2桁</td>
                </tr>
                <tr>
                    <td>maxIntegerDigits="5"</td>
                    <td class="number"><fmt:formatNumber value="${largeNumber}" maxIntegerDigits="5" /></td>
                    <td>最大整数部5桁</td>
                </tr>
                <tr>
                    <td>groupingUsed="false"</td>
                    <td class="number"><fmt:formatNumber value="${largeNumber}" groupingUsed="false" /></td>
                    <td>桁区切りなし</td>
                </tr>
            </table>
        </div>
        
        <div class="format-demo">
            <h3>カスタムパターン</h3>
            <table class="table">
                <tr><th>パターン</th><th>結果</th><th>説明</th></tr>
                <tr>
                    <td>¥#,##0</td>
                    <td class="price"><fmt:formatNumber value="${price}" pattern="¥#,##0" /></td>
                    <td>円マーク付き整数</td>
                </tr>
                <tr>
                    <td>#,##0.00</td>
                    <td class="number"><fmt:formatNumber value="${price}" pattern="#,##0.00" /></td>
                    <td>小数点以下2桁固定</td>
                </tr>
                <tr>
                    <td>000000</td>
                    <td class="number"><fmt:formatNumber value="123" pattern="000000" /></td>
                    <td>ゼロパディング</td>
                </tr>
            </table>
        </div>
    </div>
    
    <!-- 3. 商品リストでの実践例 -->
    <div class="section">
        <h2>3. 商品リストでの実践例</h2>
        
        <table class="table">
            <tr>
                <th>商品名</th>
                <th>価格</th>
                <th>税込価格</th>
                <th>割引後価格</th>
                <th>発売日</th>
                <th>経過日数</th>
            </tr>
            <c:forEach var="product" items="${products}" varStatus="status">
                <c:set var="taxIncludedPrice" value="${product.price * 1.10}" />
                <c:set var="discountedPrice" value="${product.price * (1 - discountRate)}" />
                <c:set var="daysSinceRelease" value="${(currentDate.time - product.releaseDate.time) / (1000 * 60 * 60 * 24)}" />
                
                <tr>
                    <td>${product.name}</td>
                    <td class="price"><fmt:formatNumber value="${product.price}" type="currency" /></td>
                    <td class="price"><fmt:formatNumber value="${taxIncludedPrice}" type="currency" /></td>
                    <td class="price"><fmt:formatNumber value="${discountedPrice}" type="currency" />
                        <small>(<fmt:formatNumber value="${discountRate}" type="percent" />OFF)</small>
                    </td>
                    <td class="date"><fmt:formatDate value="${product.releaseDate}" pattern="yyyy/MM/dd" /></td>
                    <td class="number"><fmt:formatNumber value="${daysSinceRelease}" maxFractionDigits="0" />日前</td>
                </tr>
            </c:forEach>
        </table>
    </div>
    
    <!-- 4. ロケール設定 -->
    <div class="section">
        <h2>4. ロケール設定(<fmt:setLocale>)</h2>
        
        <div class="format-demo">
            <h3>日本ロケール</h3>
            <fmt:setLocale value="ja_JP" />
            <p>通貨: <fmt:formatNumber value="${price}" type="currency" /></p>
            <p>日付: <fmt:formatDate value="${currentDate}" dateStyle="full" /></p>
        </div>
        
        <div class="format-demo">
            <h3>米国ロケール</h3>
            <fmt:setLocale value="en_US" />
            <p>Currency: <fmt:formatNumber value="${price}" type="currency" /></p>
            <p>Date: <fmt:formatDate value="${currentDate}" dateStyle="full" /></p>
        </div>
        
        <div class="format-demo">
            <h3>ドイツロケール</h3>
            <fmt:setLocale value="de_DE" />
            <p>Währung: <fmt:formatNumber value="${price}" type="currency" /></p>
            <p>Datum: <fmt:formatDate value="${currentDate}" dateStyle="full" /></p>
        </div>
        
        <!-- 日本ロケールに戻す -->
        <fmt:setLocale value="ja_JP" />
    </div>
    
    <!-- 5. Functions ライブラリの使用例 -->
    <div class="section">
        <h2>5. Functions ライブラリの使用例</h2>
        
        <c:set var="testString" value="Hello, JSTL World!" />
        <c:set var="testList" value="${products}" />
        
        <div class="format-demo">
            <h3>文字列操作</h3>
            <table class="table">
                <tr><th>関数</th><th>結果</th><th>説明</th></tr>
                <tr>
                    <td>fn:length()</td>
                    <td>${fn:length(testString)}</td>
                    <td>文字列の長さ</td>
                </tr>
                <tr>
                    <td>fn:toUpperCase()</td>
                    <td>${fn:toUpperCase(testString)}</td>
                    <td>大文字変換</td>
                </tr>
                <tr>
                    <td>fn:toLowerCase()</td>
                    <td>${fn:toLowerCase(testString)}</td>
                    <td>小文字変換</td>
                </tr>
                <tr>
                    <td>fn:substring(5, 9)</td>
                    <td>'${fn:substring(testString, 5, 9)}'</td>
                    <td>部分文字列抽出</td>
                </tr>
                <tr>
                    <td>fn:contains()</td>
                    <td>${fn:contains(testString, 'JSTL')}</td>
                    <td>文字列の包含チェック</td>
                </tr>
                <tr>
                    <td>fn:startsWith()</td>
                    <td>${fn:startsWith(testString, 'Hello')}</td>
                    <td>開始文字列チェック</td>
                </tr>
                <tr>
                    <td>fn:endsWith()</td>
                    <td>${fn:endsWith(testString, 'World!')}</td>
                    <td>終了文字列チェック</td>
                </tr>
            </table>
        </div>
        
        <div class="format-demo">
            <h3>コレクション操作</h3>
            <p>商品数: ${fn:length(products)}件</p>
            <p>商品名一覧:
                <c:forEach var="product" items="${products}" varStatus="status">
                    ${product.name}<c:if test="${!status.last}">, </c:if>
                </c:forEach>
            </p>
        </div>
    </div>
</body>
</html>
期待される結果

日付・数値・通貨の多様なフォーマット表示と、ロケールによる表示形式の違いを確認できます。

理解度確認クイズ
  1. ELとスクリプトレットの比較
    EL(Expression Language)を使用することで、従来のスクリプトレットと比較してどのような利点がありますか?具体例を挙げて説明してください。
  2. ELのスコープ検索
    ELで${username}と記述した場合、どの順番でスコープを検索しますか?また、特定のスコープを指定したい場合はどのように記述しますか?
  3. JSTLの条件分岐
    以下の要件を満たすJSTLコードを書いてください:
    • ユーザーの年齢が20歳以上なら「成人」と表示
    • 18歳以上20歳未満なら「準成人」と表示
    • 18歳未満なら「未成年」と表示
  4. JSTLの繰り返し処理
    商品リスト(products)を表示する際、1番目と最後の商品に特別なスタイルを適用したい場合、どのようなJSTLコードを使用しますか?
  5. フォーマット機能の活用
    価格(123456.789)を以下の形式で表示するJSTLコードを書いてください:
    • ¥123,457(円マーク付き、小数点以下切り上げ)
    • 123,456.79(小数点以下2桁固定、桁区切りあり)

8.5 まとめ

この章では、ELとJSTLを活用したJSPの高度な開発手法について学習しました。

重要なポイント
  • ELの活用: ${}構文による簡潔なデータアクセス
  • null安全: ELによるnullポインタ例外の回避
  • JSTL Core: 条件分岐、繰り返し処理の宣言的な実装
  • JSTL Formatting: 日付・数値・通貨の適切な表示形式
  • 保守性の向上: Javaコードの排除による可読性向上