CODE Polyglot / よく使う書き方 / Java

Java のよく使う書き方(7本)

開発でよく書くコードの型と小さなツールの、Java の版をまとめました。標準の機能だけで書いていて、毎朝、実際に動かして答えを確かめています。

01. スネークケースとキャメルケースを変換する

user_id と userId を行き来させます。データベースの列名と JSON のキー、定数と変数のように、名前の書き方をそろえるときに使います。HTTPServer のように大文字が続く名前で、よく見かける書き方はほとんどの言語で崩れます。 ほかの言語・正解の例・解説 →

CaseConvert.java21.0.12 で 14 / 14 件の出力が正解と一致

// スネークケース(user_id)とキャメルケース(userId)を、指定した向きに変換する
import java.util.Locale;

public class CaseConvert {
    public static void main(String[] args) {
        String mode = args[0], s = args[1];
        // matches は文字列全体が合うかを見る(find は一部が合えば通ってしまう)
        if (!s.matches("[A-Za-z0-9_]*")) {
            System.out.println("NG: 半角の英数字と _ だけにしてください");
            return;
        }
        if (mode.equals("snake")) {
            // 切れ目は2種類。小文字か数字の次の大文字(userId)と、大文字の続きの最後で次が小文字のもの(HTTP|Server)
            String out = s.replaceAll("(?<=[a-z0-9])(?=[A-Z])", "_")
                          .replaceAll("(?<=[A-Z])(?=[A-Z][a-z])", "_");
            // toLowerCase() は動かす場所の言語の設定を使う。トルコ語だと I が「ı」になるので Locale.ROOT を渡す
            System.out.println(out.toLowerCase(Locale.ROOT));
            return;
        }
        StringBuilder sb = new StringBuilder();
        // split("_") は「__」や先頭の「_」で空の部分を作る。空の部分に substring(0, 1) をすると落ちるので飛ばす
        for (String p : s.split("_")) {
            if (p.isEmpty()) continue;
            if (sb.length() == 0) {
                sb.append(p.toLowerCase(Locale.ROOT));
            } else {
                sb.append(p.substring(0, 1).toUpperCase(Locale.ROOT)).append(p.substring(1).toLowerCase(Locale.ROOT));
            }
        }
        System.out.println(sb);
    }
}

02. HTML の特殊文字をエスケープする

利用者が入力した文字を HTML に埋め込む前に、< > & " ' を &lt; などに置き換えます。標準の関数は言語ごとに ' の書き方が違い、この決まりの形とそのまま一致したのは Ruby だけでした。 ほかの言語・正解の例・解説 →

Escape.java21.0.12 で 8 / 8 件の出力が正解と一致

// 受け取った文字列の & < > " ' を、HTML の書き方(&amp; &lt; &gt; &quot; &#39;)に変える
public class Escape {
    public static void main(String[] args) {
        String s = args[0];
        // Java の標準にはエスケープの関数がないので、1文字ずつ見て置き換える。
        // 5文字はどれも1つの char に収まるので、絵文字(char 2つ)はそのまま書き写される
        StringBuilder out = new StringBuilder();
        for (char c : s.toCharArray()) {
            switch (c) {
                case '&' -> out.append("&amp;");
                case '<' -> out.append("&lt;");
                case '>' -> out.append("&gt;");
                case '"' -> out.append("&quot;");
                case '\'' -> out.append("&#39;");
                default -> out.append(c);
            }
        }
        System.out.println(out);
    }
}

03. 順番を保って重複を消す

同じ値の2回目以降を消し、初めて出た順に残します。タグの一覧や、選んだ項目の一覧でよく使います。集合(Set)に入れて戻す書き方では、言語によって順番が変わったり、実行するたびに変わったりします。 ほかの言語・正解の例・解説 →

Unique.java21.0.12 で 9 / 9 件の出力が正解と一致

// 受け取った文字列から、2回目以降に出たものを消し、初めて出た順に出す
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;

public class Unique {
    public static void main(String[] args) {
        // HashSet は並びがハッシュ値で決まる。入れた順を覚えるのは LinkedHashSet。
        // String の equals は char を1つずつ比べるので、「が」1文字と「か」+濁点は別になる
        Set<String> out = new LinkedHashSet<>(Arrays.asList(args));
        for (String s : out) {
            System.out.println(s.isEmpty() ? "(空)" : s);
        }
        System.out.println(out.size() + "件(" + (args.length - out.size()) + "件を消した)");
    }
}

04. 日付と時刻を ISO 8601 で読み書きする

API やログでよく使う 2026-10-30T09:00:00+09:00 の形を読み、UTC と日本時間で書き直します。標準のパーサーにそのまま渡すと、2月30日を3月2日として読んだり、タイムゾーンの無い日時を動かす場所の時間帯で読んだりします。 ほかの言語・正解の例・解説 →

Iso8601.java21.0.12 で 15 / 15 件の出力が正解と一致

// ISO 8601 の日時を受け取って、UTC と日本時間で書き出す
import java.time.LocalDateTime;
import java.time.OffsetDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.regex.Pattern;

public class Iso8601 {
    // 標準の ISO の書式は大文字小文字を区別せず(「t」「z」も通る)、「+09:00:00」も読む。形は先に正規表現で決める
    static final Pattern ISO = Pattern.compile("[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(\\.[0-9]{1,9})?(Z|[+-][0-9]{2}:[0-9]{2})?");

    public static void main(String[] args) {
        var m = ISO.matcher(args[0]);
        if (!m.matches()) {
            System.out.println("NG: 日時の形ではありません");
            return;
        }
        try {
            if (m.group(2) == null) {
                // タイムゾーンが無くても、2月30日なら「形が違う」を返したいので、日時としては読んでみる
                LocalDateTime.parse(args[0]);
                System.out.println("NG: タイムゾーン(Z か +09:00 など)を付けてください");
                return;
            }
            // ISO の書式は STRICT で読むので、2月30日・25時はエラーになる(自分で作る ofPattern は既定が SMART で、2月30日を2月28日にする)
            OffsetDateTime t = OffsetDateTime.parse(args[0]);
            // ZonedDateTime や ZoneId.systemDefault() を使わず、時差だけ(ZoneOffset)で変える
            var utc = t.withOffsetSameInstant(ZoneOffset.UTC);
            var jst = t.withOffsetSameInstant(ZoneOffset.ofHours(9));
            // 年は yyyy(紀元の年)ではなく uuuu で書く。秒の小数は書式に入れないので切り捨てになる
            System.out.println("UTC " + utc.format(DateTimeFormatter.ofPattern("uuuu-MM-dd'T'HH:mm:ss'Z'")));
            System.out.println("日本時間 " + jst.format(DateTimeFormatter.ofPattern("uuuu年MM月dd日 HH:mm:ss")));
        } catch (DateTimeParseException e) {
            System.out.println("NG: 日時の形ではありません");
        }
    }
}

05. アクセスログを集計する

Web サーバーのアクセスログを標準入力から読み、ステータスごとの件数と、応答の遅いリクエストの上位3件を出します。空白で区切って何番目かを取る書き方は手軽ですが、壊れた行を数えたり、応答秒を文字のまま並べたりします。 ほかの言語・正解の例・解説 →

AccessLog.java21.0.12 で 8 / 8 件の出力が正解と一致

// 標準入力のアクセスログを読んで、件数・ステータスごとの件数・遅い順の上位3件を出す
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class AccessLog {
    // 日時やHTTPの版には空白が入るので、空白で区切って何番目と数えず、行の形そのものを正規表現で確かめる
    static final Pattern LOG = Pattern.compile(
        "\\S+ \\S+ \\S+ \\[[^\\]]+\\] \"(\\S+) (\\S+) [^\"]+\" ([0-9]{3}) ([0-9]+|-) ([0-9]+\\.[0-9]+)");

    record Row(String sec, double secNum, String method, String path, String status) {}

    public static void main(String[] args) throws IOException {
        List<Row> rows = new ArrayList<>();
        int bad = 0;
        // readLine は \n でも \r\n でも行を区切り、行の終わりの記号を取って返す
        BufferedReader in = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8));
        String line;
        while ((line = in.readLine()) != null) {
            if (line.isEmpty()) {
                continue;
            }
            // find は行のどこかに合えば通る。行全体が形どおりか確かめるのは matches
            Matcher m = LOG.matcher(line);
            if (!m.matches()) {
                bad++;
                continue;
            }
            rows.add(new Row(m.group(5), Double.parseDouble(m.group(5)), m.group(1), m.group(2), m.group(3)));
        }

        System.out.println("件数 " + rows.size());
        if (bad > 0) {
            System.out.println("読めない行 " + bad);
        }

        // HashMap は取り出す順番が決まっていない。TreeMap はキーの小さい順に取り出せる
        Map<String, Integer> counts = new TreeMap<>();
        for (Row r : rows) {
            counts.merge(r.status(), 1, Integer::sum);
        }
        System.out.println("ステータス");
        for (Map.Entry<String, Integer> e : counts.entrySet()) {
            System.out.println(e.getKey() + " " + e.getValue());
        }

        System.out.println("遅い順");
        // List.sort は安定(同じ秒なら先に出た行が先)。応答秒は文字ではなく double で比べる
        rows.sort(Comparator.comparingDouble(Row::secNum).reversed());
        for (Row r : rows.subList(0, Math.min(3, rows.size()))) {
            System.out.println(r.sec() + " " + r.method() + " " + r.path());
        }
    }
}

06. URL 用の文字列(slug)を作る

記事のタイトルから「hello-world」のような URL の一部を作ります。ブログや商品ページのアドレスを、見て意味が分かる形にするときに使います。よく見かける書き方は、日本語のタイトルをそのまま URL に入れたり、空の slug を黙って返したりします。 ほかの言語・正解の例・解説 →

Slug.java21.0.12 で 14 / 14 件の出力が正解と一致

// 文字列を URL に使える形(slug)に変える
import java.util.Locale;
import java.util.regex.Pattern;

public class Slug {
    static final int MAX = 32;

    public static void main(String[] args) {
        // toLowerCase() は動かす場所の言語の設定を使い、İ は2文字になる。A〜Z だけを変える
        String s = Pattern.compile("[A-Z]").matcher(args[0])
                          .replaceAll(m -> m.group().toLowerCase(Locale.ROOT));
        // \w は既定では ASCII の [A-Za-z0-9_]。残す文字を [a-z0-9] と書く
        s = s.replaceAll("[^a-z0-9]+", "-").replaceAll("^-+|-+$", "");
        if (s.length() > MAX) {
            s = s.substring(0, MAX).replaceAll("-+$", "");
        }
        System.out.println(s.isEmpty() ? "NG: URL に使える文字がありません" : s);
    }
}

07. URL エンコードする

検索語や日本語の名前を、URL のクエリの値(`?q=...`)に入れられる形に変えます。よく見かける書き方は、空白を「+」にしたり、`!` や `~` を変えずに残したりして、受け取る側で別の文字列になります。 ほかの言語・正解の例・解説 →

UrlEncode.java21.0.12 で 15 / 15 件の出力が正解と一致

// 文字列を URL のクエリの値に入れられる形(パーセントエンコード)に変える
import java.nio.charset.StandardCharsets;

public class UrlEncode {
    static final String KEEP = "-_.~";

    public static void main(String[] args) {
        // URLEncoder.encode は空白を「+」、「~」を %7E にして「*」を残す(フォームの形で、URL の形ではない)
        StringBuilder sb = new StringBuilder();
        for (byte b : args[0].getBytes(StandardCharsets.UTF_8)) {
            int v = b & 0xFF;  // byte は符号付きなので、そのまま使うと負になる
            char c = (char) v;
            if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || KEEP.indexOf(c) >= 0) {
                sb.append(c);
            } else {
                sb.append(String.format("%%%02X", v));
            }
        }
        System.out.println(sb);
    }
}

ほかの言語