はじめに
Javaでの文字列操作は、アプリケーション開発において最も頻繁に行われる処理の一つです。特に文字列置換は、データの整形やバリデーション、セキュリティ対策など、様々な場面で必要となる重要な操作です。
本記事では、以下のような内容を詳しく解説します。
- 基本的な文字列置換メソッドの使い分け
- パフォーマンスを考慮した実装方法
- セキュリティを確保した置換処理
- 実務で使える具体的なユースケース
それでは、Javaでの文字列置換について、基礎から実践的なテクニックまで、段階的に解説していきましょう。
1.Javaでの文字列置換の基礎知識
Javaでの文字列置換は、テキスト処理の基本的かつ重要な操作の一つです。この章では、基本的な置換方法から、より高度な使い方まで、実践的な例を交えて解説します。
1.1 String.replaceとString.replaceAllの違いを理解しよう
Stringクラスには、文字列置換のための主要な2つのメソッドがあります。replace()とreplaceAll()です。これらは一見似ていますが、重要な違いがあります。
replace()メソッド
replace()メソッドには2つのバージョンがあります。
// 文字(char)の置換 public String replace(char oldChar, char newChar) // 文字列(String)の置換 public String replace(CharSequence target, CharSequence replacement)
使用例:
String text = "Hello World";
// 文字の置換
String result1 = text.replace('l', 'L'); // "HeLLo WorLd"
// 文字列の置換
String result2 = text.replace("World", "Java"); // "Hello Java"
replaceAll()メソッド
public String replaceAll(String regex, String replacement)
replaceAll()は正規表現を使用した置換が可能です。
String text = "Hello123World456";
// 数字を全て'X'に置換
String result = text.replaceFirst("\\d+", "X"); // "HelloXWorld456"
主な違いの比較:
| 機能 | replace() | replaceAll() |
|---|---|---|
| 正規表現対応 | × | ○ |
| リテラル文字列置換 | ○ | ○ |
| パフォーマンス | 高速 | 正規表現処理が必要なため比較的遅い |
| 使用シーン | 単純な置換 | パターンマッチングが必要な置換 |
1.2 正規表現を使用した置換の基本
正規表現を使用することで、より柔軟な文字列置換が可能になります。以下に主要な正規表現パターンと使用例を示します。
基本的な正規表現パターン
String text = "User123 has 456 points";
// 数字の置換
String result1 = text.replaceAll("\\d+", "XXX");
System.out.println(result1); // "UserXXX has XXX points"
// 空白文字の置換
String result2 = text.replaceAll("\\s+", "_");
System.out.println(result2); // "User123_has_456_points"
// 文字列の先頭と末尾のマッチング
String result3 = text.replaceAll("^User", "Player");
System.out.println(result3); // "Player123 has 456 points"
よく使用する正規表現パターン一覧
| パターン | 説明 | 例 |
|---|---|---|
\\d | 任意の数字 | “abc123” → “abcXXX” |
\\s | 空白文字 | “a b c” → “a_b_c” |
\\w | 単語文字 | “Hello!” → “XXXXX!” |
^ | 行の先頭 | “^Hello” で先頭のHelloのみマッチ |
$ | 行の末尾 | “end$” で末尾のendのみマッチ |
.* | 任意の文字列 | “a.*c” で “abc”, “ac”, “abbc” などにマッチ |
実践的な使用例:
// メールアドレスのドメイン部分を置換
String email = "user@example.com";
String masked = email.replaceAll("@.*$", "@masked.com");
System.out.println(masked); // "user@masked.com"
// 電話番号の一部をマスク
String phone = "090-1234-5678";
String maskedPhone = phone.replaceAll("\\d(?=\\d{4})", "*");
System.out.println(maskedPhone); // "090-****-5678"
これらの基本を理解することで、より複雑な文字列置換処理も実装できるようになります。次章では、これらの基本知識を活用した実践的なテクニックを紹介していきます。
2.実践で使える7つの文字列置換テクニック
プロダクション環境で使える、実践的な文字列置換テクニックを紹介します。これらのテクニックは、実際の開発現場でよく遭遇する課題に対応できるように設計されています。
2.1 単純な文字列置換:replaceメソッドの活用法
最もシンプルな置換処理から、実践的な使い方を見ていきましょう。
public class SimpleReplacementExample {
public static String sanitizeFileName(String fileName) {
// 禁止文字を安全な文字に置換
return fileName
.replace('\\', '_')
.replace('/', '_')
.replace(':', '_')
.replace('*', '_')
.replace('?', '_')
.replace('"', '_')
.replace('<', '_')
.replace('>', '_')
.replace('|', '_');
}
public static void main(String[] args) {
String fileName = "report:2023/12*.txt";
System.out.println(sanitizeFileName(fileName)); // "report_2023_12_.txt"
}
}
2.2 複数箇所の一括置換:replaceAllの効率的な使い方
正規表現を使用して、複数のパターンを効率的に置換する方法です。
public class BatchReplacementExample {
public static String formatPhoneNumber(String phone) {
// 不要な文字を除去し、一定のフォーマットに整形
return phone
.replaceAll("[^0-9]", "") // 数字以外を除去
.replaceAll("(\\d{3})(\\d{4})(\\d{4})", "$1-$2-$3");
}
public static void main(String[] args) {
String phone = "090 1234 5678";
System.out.println(formatPhoneNumber(phone)); // "090-1234-5678"
}
}
2.3 大文字小文字を区別しない置換処理の実装
検索時の大文字小文字を無視する実装方法です。
public class CaseInsensitiveReplacementExample {
public static String replaceIgnoreCase(String text, String target, String replacement) {
// 大文字小文字を区別せずに置換するための正規表現パターンを作成
StringBuilder pattern = new StringBuilder();
for (char c : target.toCharArray()) {
pattern.append("[")
.append(Character.toLowerCase(c))
.append(Character.toUpperCase(c))
.append("]");
}
return text.replaceAll(pattern.toString(), replacement);
}
public static void main(String[] args) {
String text = "Hello HELLO hello";
System.out.println(replaceIgnoreCase(text, "hello", "Hi")); // "Hi Hi Hi"
}
}
2.4 正規表現を使用した高度な置換処理
複雑なパターンマッチングを必要とする置換処理の実装例です。
public class AdvancedRegexReplacementExample {
public static String maskSensitiveData(String text) {
// クレジットカード番号をマスク
String maskedCC = text.replaceAll("\\b\\d{4}[-\\s]?\\d{4}[-\\s]?\\d{4}[-\\s]?\\d{4}\\b", "****-****-****-****");
// メールアドレスを部分的にマスク
return maskedCC.replaceAll("([a-zA-Z0-9._-]+)@([a-zA-Z0-9._-]+\\.([a-zA-Z]{2,5}))", "$1@***.$3");
}
public static void main(String[] args) {
String text = "Contact: john.doe@example.com, Card: 1234-5678-9012-3456";
System.out.println(maskSensitiveData(text));
// 出力: "Contact: john.doe@***.com, Card: ****-****-****-****"
}
}
2.5 StringBuilder/StringBufferを使用したパフォーマンス最適化
大量のテキスト処理や繰り返し置換が必要な場合の最適化テクニックです。
public class OptimizedReplacementExample {
public static String replaceMultipleOptimized(String text, Map<String, String> replacements) {
StringBuilder result = new StringBuilder(text);
for (Map.Entry<String, String> entry : replacements.entrySet()) {
String target = entry.getKey();
String replacement = entry.getValue();
int start = 0;
while ((start = result.indexOf(target, start)) != -1) {
result.replace(start, start + target.length(), replacement);
start += replacement.length();
}
}
return result.toString();
}
public static void main(String[] args) {
Map<String, String> replacements = new HashMap<>();
replacements.put("cat", "dog");
replacements.put("black", "white");
String text = "The black cat saw another black cat.";
System.out.println(replaceMultipleOptimized(text, replacements));
// 出力: "The white dog saw another white dog."
}
}
2.6 特殊文字の置換におけるエスケープ処理
正規表現の特殊文字を扱う際の安全な置換処理方法です。
public class SpecialCharacterReplacementExample {
public static String escapeRegexSpecialChars(String text) {
return text.replaceAll("[\\\\\\[\\]{}()*+?^$|.]", "\\\\$0");
}
public static String replaceSpecialChars(String text, String target, String replacement) {
// 特殊文字をエスケープして安全に置換
String escapedTarget = escapeRegexSpecialChars(target);
return text.replaceAll(escapedTarget, replacement);
}
public static void main(String[] args) {
String text = "This (text) contains [special] characters.";
System.out.println(replaceSpecialChars(text, "[special]", "normal"));
// 出力: "This (text) contains normal characters."
}
}
2.7 文字列置換の連鎖処理テクニック
複数の置換処理を効率的に連鎖させる方法です。
public class ChainedReplacementExample {
public static class TextProcessor {
private String text;
public TextProcessor(String text) {
this.text = text;
}
public TextProcessor replaceIfMatches(String regex, String replacement) {
if (text.matches(".*" + regex + ".*")) {
text = text.replaceAll(regex, replacement);
}
return this;
}
public TextProcessor replaceAll(String target, String replacement) {
text = text.replaceAll(target, replacement);
return this;
}
public String getText() {
return text;
}
}
public static void main(String[] args) {
String processed = new TextProcessor("Hello, World! 123")
.replaceAll("World", "Java")
.replaceIfMatches("\\d+", "***")
.replaceAll("!", "!!")
.getText();
System.out.println(processed); // "Hello, Java!! ***"
}
}
これらのテクニックを組み合わせることで、より効率的で保守性の高い文字列置換処理を実装できます。次章では、これらのテクニックを使用する際のパフォーマンスとベストプラクティスについて詳しく解説します。
3.パフォーマンスとベストプラクティス
文字列置換処理は頻繁に使用される操作であり、適切な実装方法を選択することでアプリケーションの全体的なパフォーマンスに大きな影響を与えます。
3.1 メモリ効率を考慮した文字列置換の実装方法
Javaの文字列操作におけるメモリ効率について、実装方法ごとの特徴を見ていきましょう。
1. String vs StringBuilder の使い分け
public class StringReplacementPerformance {
public static String replaceWithString(String text, String target, String replacement, int times) {
String result = text;
for (int i = 0; i < times; i++) {
result = result.replace(target, replacement); // 新しいStringオブジェクトが生成される
}
return result;
}
public static String replaceWithStringBuilder(String text, String target, String replacement, int times) {
StringBuilder result = new StringBuilder(text);
for (int i = 0; i < times; i++) {
int index = result.indexOf(target);
if (index != -1) {
result.replace(index, index + target.length(), replacement);
}
}
return result.toString();
}
// ベンチマーク用のメイン処理
public static void main(String[] args) {
String text = "Hello World Hello World Hello World";
long startTime = System.nanoTime();
replaceWithString(text, "World", "Java", 1000);
long stringTime = System.nanoTime() - startTime;
startTime = System.nanoTime();
replaceWithStringBuilder(text, "World", "Java", 1000);
long stringBuilderTime = System.nanoTime() - startTime;
System.out.printf("String時間: %dms%n", stringTime / 1000000);
System.out.printf("StringBuilder時間: %dms%n", stringBuilderTime / 1000000);
}
}
メモリ使用量の比較表:
| 実装方法 | メモリ効率 | 使用推奨シーン |
|---|---|---|
String#replace | 低 | 単発の置換、小規模テキスト |
StringBuilder | 高 | 連続した置換、大規模テキスト |
StringBuffer | 中 | マルチスレッド環境での置換 |
3.2 大量データ処理時の最適化テクニック
大量のテキストデータを処理する際の最適化方法について解説します。
public class LargeTextProcessing {
public static class ChunkedTextProcessor {
private static final int CHUNK_SIZE = 8192; // 8KBのチャンク
public static String processLargeText(String input, Function<String, String> replacementFunc) {
if (input.length() < CHUNK_SIZE) {
return replacementFunc.apply(input);
}
StringBuilder result = new StringBuilder(input.length());
for (int i = 0; i < input.length(); i += CHUNK_SIZE) {
int end = Math.min(i + CHUNK_SIZE, input.length());
String chunk = input.substring(i, end);
result.append(replacementFunc.apply(chunk));
}
return result.toString();
}
}
// 使用例
public static void main(String[] args) {
String largeText = "大量のテキストデータ..."; // 実際の大量データ
String processed = ChunkedTextProcessor.processLargeText(
largeText,
chunk -> chunk.replaceAll("pattern", "replacement")
);
}
}
- 入力テキストのサイズに応じた実装方法の選択
- メモリ使用量のモニタリング
- チャンク処理の適用
- キャッシュの活用
- 正規表現パターンのプリコンパイル
3.3 文字列置換処理のユニットテスト作成方法
効果的なユニットテストの作成方法について、JUnitを使用した例を示します。
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import static org.junit.jupiter.api.Assertions.*;
public class StringReplacementTest {
private final StringReplacer replacer = new StringReplacer();
@Test
void testBasicReplacement() {
String input = "Hello World";
String expected = "Hello Java";
String actual = replacer.replace(input, "World", "Java");
assertEquals(expected, actual, "Basic replacement failed");
}
@ParameterizedTest
@CsvSource({
"Hello World, World, Java, Hello Java",
"Hello, World, Java, Hello",
"'', World, Java, ''",
"WorldWorld, World, Java, JavaJava"
})
void testVariousReplacements(String input, String target,
String replacement, String expected) {
String actual = replacer.replace(input, target, replacement);
assertEquals(expected, actual);
}
@Test
void testPerformanceWithLargeText() {
String largeText = "a".repeat(1000000);
long startTime = System.nanoTime();
replacer.replace(largeText, "a", "b");
long duration = (System.nanoTime() - startTime) / 1000000; // ミリ秒に変換
assertTrue(duration < 1000, "Performance test failed: took " + duration + "ms");
}
@Test
void testMemoryEfficiency() {
String largeText = "a".repeat(1000000);
Runtime runtime = Runtime.getRuntime();
long beforeMemory = runtime.totalMemory() - runtime.freeMemory();
replacer.replace(largeText, "a", "b");
long afterMemory = runtime.totalMemory() - runtime.freeMemory();
long memoryUsed = (afterMemory - beforeMemory) / (1024 * 1024); // MBに変換
assertTrue(memoryUsed < 100, "Memory usage too high: " + memoryUsed + "MB");
}
}
- 基本的な置換機能
- エッジケース(空文字列、null、特殊文字など)
- パフォーマンス要件
- メモリ使用量
- 大規模データでの動作
- 正規表現パターンの正確性
- マルチスレッド環境での安全性
これらのベストプラクティスと最適化テクニックを適切に組み合わせることで、効率的で信頼性の高い文字列置換処理を実装できます。次章では、実装時によく遭遇するエラーとその対処法について解説します。
4.よくあるエラーと対処法
文字列置換処理を実装する際によく遭遇するエラーとその対処法について、実践的な例を交えて解説します。
4.1 NullPointerExceptionを防ぐ安全な実装
NullPointerExceptionは文字列処理で最も頻繁に遭遇するエラーの一つです。以下に、安全な実装パターンを示します。
public class SafeStringReplacement {
public static String safeReplace(String text, String target, String replacement) {
// null チェックと早期リターン
if (text == null) return "";
if (target == null || replacement == null) return text;
try {
return text.replace(target, replacement);
} catch (Exception e) {
// エラーログの記録
logger.error("文字列置換処理でエラーが発生しました", e);
return text; // 元のテキストを返す
}
}
// より堅牢な実装例
public static class StringReplacer {
private final String defaultValue;
public StringReplacer(String defaultValue) {
this.defaultValue = defaultValue;
}
public String replace(String text, String target, String replacement) {
if (text == null) return defaultValue;
if (target == null || target.isEmpty()) return text;
return text.replace(target,
replacement != null ? replacement : "");
}
}
public static void main(String[] args) {
StringReplacer replacer = new StringReplacer("");
// 安全な置換処理の例
System.out.println(replacer.replace(null, "old", "new")); // ""
System.out.println(replacer.replace("text", null, "new")); // "text"
System.out.println(replacer.replace("text", "t", null)); // "ex"
}
}
- 入力値のnullチェック
- 空文字列のハンドリング
- デフォルト値の設定
- 例外処理の実装
- エラーログの記録
4.2 正規表現のパターンマッチング失敗への対応
正規表現使用時の一般的な問題と対処方法について解説します。
public class RegexReplacementErrorHandler {
private static final Pattern UNSAFE_PATTERN = Pattern.compile("[a-zA-Z]+");
public static String handleRegexReplacement(String text, String regex, String replacement) {
try {
Pattern pattern = Pattern.compile(regex);
return pattern.matcher(text).replaceAll(replacement);
} catch (PatternSyntaxException e) {
// 正規表現パターンが無効な場合の処理
logger.error("無効な正規表現パターン: " + regex, e);
return text;
} catch (IllegalArgumentException e) {
// replacementに無効な参照が含まれる場合の処理
logger.error("無効な置換文字列: " + replacement, e);
return text;
}
}
// 正規表現のバリデーション
public static boolean isValidRegex(String regex) {
try {
Pattern.compile(regex);
return true;
} catch (PatternSyntaxException e) {
return false;
}
}
// よくある正規表現エラーのデモ
public static void main(String[] args) {
// 1. 無効な正規表現
System.out.println(handleRegexReplacement("test", "[", "replacement"));
// 2. 不正な後方参照
System.out.println(handleRegexReplacement("test", "(\\w+)", "$2"));
// 3. 大きな入力に対する制御不能なバックトラッキング
String input = "a".repeat(100);
System.out.println(handleRegexReplacement(input, "(a+)*b", "x"));
}
}
正規表現使用時の注意点:
| エラーパターン | 原因 | 対処方法 |
|---|---|---|
PatternSyntaxException | 不正な正規表現構文 | パターンのバリデーション実施 |
StackOverflowError | 過度なバックトラッキング | パターンの最適化、入力制限 |
IllegalArgumentException | 不正な置換文字列 | 置換文字列のエスケープ処理 |
4.3 文字エンコーディングに関する問題の解決方法
文字エンコーディングに関連する問題と対処方法について説明します。
public class EncodingErrorHandler {
public static String handleEncodingReplacement(String text, String target,
String replacement, String encoding) {
try {
// 入力テキストのエンコーディングを明示的に処理
byte[] bytes = text.getBytes(encoding);
String encodedText = new String(bytes, encoding);
// エンコーディングを考慮した置換処理
return encodedText.replace(target, replacement);
} catch (UnsupportedEncodingException e) {
logger.error("サポートされていないエンコーディング: " + encoding, e);
return text;
}
}
// エンコーディング関連の一般的な問題に対する対処
public static class EncodingAwareReplacer {
private final String defaultEncoding;
private final CharsetDecoder decoder;
public EncodingAwareReplacer(String encoding) {
this.defaultEncoding = encoding;
this.decoder = Charset.forName(encoding).newDecoder()
.onMalformedInput(CodingErrorAction.REPLACE)
.onUnmappableCharacter(CodingErrorAction.REPLACE);
}
public String replaceWithEncoding(String text, String target,
String replacement) {
try {
// 文字エンコーディングを考慮した置換処理
ByteBuffer bytes = ByteBuffer.wrap(
text.getBytes(defaultEncoding));
CharBuffer chars = decoder.decode(bytes);
return chars.toString().replace(target, replacement);
} catch (CharacterCodingException e) {
logger.error("文字エンコーディングエラー", e);
return text;
}
}
}
public static void main(String[] args) {
EncodingAwareReplacer replacer = new EncodingAwareReplacer("UTF-8");
// 異なるエンコーディングのテキストを処理
String text = "こんにちは世界";
String result = replacer.replaceWithEncoding(text, "世界", "World");
System.out.println(result); // "こんにちはWorld"
}
}
エンコーディング関連の問題解決のためのベストプラクティス:
1. 明示的なエンコーディング指定
● 常にエンコーディングを明示的に指定する
● プロジェクト全体で一貫したエンコーディングを使用する
2. エラー処理の実装
● エンコーディング変換エラーの適切な処理
● 代替文字の設定
● エラーログの記録
3. 入力検証
● バイトオーダーマークの処理
● 不正な文字シーケンスのチェック
● エンコーディング互換性の確認
これらのエラー対策を適切に実装することで、より堅牢な文字列置換処理を実現できます。次章では、これらの知識を活用した実践的なユースケースについて解説します。
5.実践的なユースケース集
実際の開発現場でよく遭遇する文字列置換の実践的なユースケースについて、具体的な実装例と共に解説します。
5.1 CSVファイル処理での文字列置換活用例
CSVファイルの処理は業務システムでよく使用されるケースです。以下に、安全で効率的な実装例を示します。
public class CsvProcessor {
private static final String CSV_DELIMITER = ",";
private static final String QUOTE = "\"";
public static class CsvLine {
private final List<String> values;
public CsvLine(String line) {
this.values = parseCsvLine(line);
}
private List<String> parseCsvLine(String line) {
List<String> result = new ArrayList<>();
boolean inQuotes = false;
StringBuilder field = new StringBuilder();
for (char c : line.toCharArray()) {
if (c == '"') {
inQuotes = !inQuotes;
} else if (c == ',' && !inQuotes) {
result.add(field.toString().trim());
field.setLength(0);
} else {
field.append(c);
}
}
result.add(field.toString().trim());
return result;
}
public String sanitizeField(int index) {
if (index >= values.size()) return "";
return values.get(index)
.replace(QUOTE, QUOTE + QUOTE) // エスケープ処理
.replaceAll("[\\r\\n]+", " ") // 改行を空白に置換
.trim();
}
public String toString() {
return values.stream()
.map(value -> QUOTE + value + QUOTE)
.collect(Collectors.joining(CSV_DELIMITER));
}
}
public static void processCsvFile(Path inputPath, Path outputPath) {
try (BufferedReader reader = Files.newBufferedReader(inputPath);
BufferedWriter writer = Files.newBufferedWriter(outputPath)) {
String line;
while ((line = reader.readLine()) != null) {
CsvLine csvLine = new CsvLine(line);
// 各フィールドの処理
String processed = IntStream.range(0, csvLine.values.size())
.mapToObj(csvLine::sanitizeField)
.collect(Collectors.joining(CSV_DELIMITER));
writer.write(processed);
writer.newLine();
}
} catch (IOException e) {
logger.error("CSVファイル処理でエラーが発生しました", e);
}
}
}
5.2 ログ処理における機密情報の置換テクニック
セキュリティを考慮したログ処理の実装例です。
public class SecureLogProcessor {
private static final Pattern CREDIT_CARD_PATTERN =
Pattern.compile("\\b\\d{4}[- ]?\\d{4}[- ]?\\d{4}[- ]?\\d{4}\\b");
private static final Pattern EMAIL_PATTERN =
Pattern.compile("\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,6}\\b");
private static final Pattern PASSWORD_PATTERN =
Pattern.compile("password[=:]\\s*['\"](.*?)['\"]", Pattern.CASE_INSENSITIVE);
public static class LogMasker {
private final List<Pattern> patternsToMask;
private final String maskChar;
public LogMasker(String maskChar) {
this.maskChar = maskChar;
this.patternsToMask = new ArrayList<>();
// デフォルトの機密情報パターンを追加
patternsToMask.add(CREDIT_CARD_PATTERN);
patternsToMask.add(EMAIL_PATTERN);
patternsToMask.add(PASSWORD_PATTERN);
}
public String maskSensitiveInfo(String logMessage) {
if (logMessage == null || logMessage.isEmpty()) {
return logMessage;
}
String masked = logMessage;
for (Pattern pattern : patternsToMask) {
Matcher matcher = pattern.matcher(masked);
masked = matcher.replaceAll(mr -> {
String match = mr.group();
// クレジットカード番号の場合、最後の4桁は表示
if (pattern.equals(CREDIT_CARD_PATTERN)) {
return match.substring(match.length() - 4)
.padStart(match.length(), '*');
}
// メールアドレスの場合、@の前後で分割して一部をマスク
if (pattern.equals(EMAIL_PATTERN)) {
String[] parts = match.split("@");
return parts[0].substring(0, 2) + "***@" + parts[1];
}
// その他の機密情報は完全にマスク
return maskChar.repeat(match.length());
});
}
return masked;
}
}
public static void main(String[] args) {
LogMasker masker = new LogMasker("*");
String log = "User email: john.doe@example.com, CC: 4111-1111-1111-1111";
System.out.println(masker.maskSensitiveInfo(log));
// 出力: "User email: jo***@example.com, CC: ************1111"
}
}
5.3 Web APIレスポンスの文字列加工実装例
RESTful APIのレスポンス処理における文字列加工の実装例です。
public class ApiResponseProcessor {
private static final ObjectMapper mapper = new ObjectMapper();
public static class ApiResponse {
private String status;
private Object data;
private String message;
// getters, setters
}
public static class ResponseSanitizer {
private final Set<String> fieldsToSanitize;
private final Pattern htmlPattern =
Pattern.compile("<[^>]*>");
private final Pattern scriptPattern =
Pattern.compile("<script[^>]*>.*?</script>",
Pattern.DOTALL | Pattern.CASE_INSENSITIVE);
public ResponseSanitizer(Set<String> fieldsToSanitize) {
this.fieldsToSanitize = fieldsToSanitize;
}
public String sanitizeResponse(String jsonResponse) {
try {
JsonNode root = mapper.readTree(jsonResponse);
sanitizeNode(root);
return mapper.writeValueAsString(root);
} catch (JsonProcessingException e) {
logger.error("JSONの処理に失敗しました", e);
return jsonResponse;
}
}
private void sanitizeNode(JsonNode node) {
if (node.isObject()) {
((ObjectNode) node).fields().forEachRemaining(entry -> {
if (fieldsToSanitize.contains(entry.getKey())) {
String sanitized = sanitizeContent(
entry.getValue().asText());
((ObjectNode) node).put(entry.getKey(), sanitized);
} else if (entry.getValue().isContainerNode()) {
sanitizeNode(entry.getValue());
}
});
} else if (node.isArray()) {
node.forEach(this::sanitizeNode);
}
}
private String sanitizeContent(String content) {
String sanitized = content;
// HTMLタグの削除
sanitized = htmlPattern.matcher(sanitized).replaceAll("");
// スクリプトの削除
sanitized = scriptPattern.matcher(sanitized).replaceAll("");
// 特殊文字のエスケープ
sanitized = StringEscapeUtils.escapeHtml4(sanitized);
return sanitized.trim();
}
}
public static void main(String[] args) {
Set<String> fieldsToSanitize = new HashSet<>(
Arrays.asList("description", "comment", "userInput"));
ResponseSanitizer sanitizer = new ResponseSanitizer(fieldsToSanitize);
String response = "{\"status\":\"success\"," +
"\"data\":{\"description\":\"<script>alert('xss');</script>\"}}";
String sanitized = sanitizer.sanitizeResponse(response);
System.out.println(sanitized);
// 出力: {"status":"success","data":{"description":"alert('xss');"}}
}
}
これらのユースケースは、実際の開発現場でよく遭遇する状況に基づいています。
1. CSVファイル処理
● 文字エンコーディングの適切な処理
● クォーテーションのエスケープ
● 大規模ファイルの効率的な処理
2. ログ処理
● 機密情報の確実な保護
● パフォーマンスへの配慮
● 適切なマスキングルールの設定
3. API処理
● セキュリティ対策(XSS対策等)
● レスポンスの整形と検証
● エラーハンドリング
これらのユースケースを参考に、実際の開発では状況に応じて適切なアプローチを選択することが重要です。
まとめ:効果的な文字列置換の実装に向けて
本記事で解説した内容を実践することで、以下のような効果が期待できます。
1. 基本的な実装スキル
● replace()とreplaceAll()の適切な使い分け
● 正規表現を活用した効率的な置換処理
● 文字エンコーディングを考慮した安全な実装
2. パフォーマンス最適化
● StringBuilderを使用した効率的な処理
● 大規模データ処理時の最適化テクニック
● メモリ使用量の削減方法
3. エラー対策とセキュリティ
● NullPointerExceptionの防止
● 正規表現のエラー処理
● 機密情報の適切な処理
4. 実践的な活用方法
● CSVファイル処理での活用
● ログ処理での機密情報の保護
● Web APIレスポンスの加工
次のステップ:さらなる学習に向けて
文字列処理の基礎を理解したら、次のようなテーマに取り組むことをお勧めします。
1. 正規表現のより深い理解
2. パフォーマンスチューニングの実践
3. セキュリティ対策の強化
4. ユニットテストの充実
本記事で解説した内容は、実務での基礎となる重要な知識です。これらを適切に活用し、より堅牢なアプリケーション開発に役立ててください。

