ZetCode

Java Matcher.appendReplacement 方法

上次修改时间:2025 年 4 月 20 日

appendReplacement 方法是 Java 中 java.util.regex 包中 Matcher 类的一部分。 它对输入字符串执行增量替换操作,同时保留非匹配部分。

此方法通常与 appendTail 结合使用,以构建修改后的字符串,其中仅替换特定的匹配项。 通过允许对每个匹配项进行自定义处理,它提供了比简单替换方法更多的控制。

基本定义

Matcher.appendReplacement(StringBuffer sb, String replacement) 将文本附加到字符串缓冲区并执行替换。 它读取输入文本直到当前匹配项,并将其附加到缓冲区,然后附加替换字符串。

该方法需要一个 StringBuffer 来累积结果,以及一个可以引用捕获组的替换字符串。 如果没有尝试匹配或先前的匹配失败,它会抛出 IllegalStateException 异常。

基本替换示例

此示例演示了 appendReplacement 的基本用法,用于替换字符串中模式的所有出现项。 我们将用它们的文字等价物替换所有数字。

BasicReplacement.java
package com.zetcode;

import java.util.regex.*;

public class BasicReplacement {
    public static void main(String[] args) {
        String input = "I have 3 apples and 5 oranges.";
        Pattern pattern = Pattern.compile("\\d+");
        Matcher matcher = pattern.matcher(input);
        
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            String replacement = convertToWord(matcher.group());
            matcher.appendReplacement(sb, replacement);
        }
        matcher.appendTail(sb);
        
        System.out.println("Original: " + input);
        System.out.println("Modified: " + sb.toString());
    }
    
    private static String convertToWord(String number) {
        switch (number) {
            case "1": return "one";
            case "2": return "two";
            case "3": return "three";
            case "4": return "four";
            case "5": return "five";
            default: return number;
        }
    }
}

在此示例中,我们首先编译一个模式来匹配数字。 匹配器找到每个数字序列,并且对于每个匹配,我们调用 appendReplacement 并进行自定义替换。 最后,appendTail 添加最后一个匹配项之后剩余的文本。

在替换中使用组引用

此示例显示了如何在替换字符串中引用捕获组。 我们将日期格式从 “MM/DD/YYYY” 重新格式化为 “YYYY-MM-DD” 格式。

GroupReference.java
package com.zetcode;

import java.util.regex.*;

public class GroupReference {
    public static void main(String[] args) {
        String input = "Dates: 12/25/2023, 01/01/2024, 07/04/2023";
        Pattern pattern = Pattern.compile("(\\d{2})/(\\d{2})/(\\d{4})");
        Matcher matcher = pattern.matcher(input);
        
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            matcher.appendReplacement(sb, "$3-$1-$2");
        }
        matcher.appendTail(sb);
        
        System.out.println("Original: " + input);
        System.out.println("Modified: " + sb.toString());
    }
}

在这里,我们在替换字符串中使用组引用 ($1, $2, $3) 来重新排列日期组成部分。 该模式将月、日和年捕获为单独的组,然后我们在替换字符串中引用它们。

条件替换

此示例演示了基于匹配内容的条件替换。 我们将用它们的长度替换单词,但前提是它们超过 3 个字符。

ConditionalReplacement.java
package com.zetcode;

import java.util.regex.*;

public class ConditionalReplacement {
    public static void main(String[] args) {
        String input = "The quick brown fox jumps over the lazy dog";
        Pattern pattern = Pattern.compile("\\w+");
        Matcher matcher = pattern.matcher(input);
        
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            String word = matcher.group();
            String replacement = word.length() > 3 ? 
                String.valueOf(word.length()) : word;
            matcher.appendReplacement(sb, replacement);
        }
        matcher.appendTail(sb);
        
        System.out.println("Original: " + input);
        System.out.println("Modified: " + sb.toString());
    }
}

该代码检查每个匹配单词的长度,并且仅当它超过 3 个字符时才用其字符计数替换它。 这表明 appendReplacement 如何实现简单的替换方法无法处理的复杂替换逻辑。

HTML 标签转换

此示例将简单的 markdown 样式格式转换为 HTML 标签。 我们将 *bold* 文本替换为 <b>bold</b>,并将 _italic_ 替换为 <i>italic</i>。

HtmlConversion.java
package com.zetcode;

import java.util.regex.*;

public class HtmlConversion {
    public static void main(String[] args) {
        String input = "This is *bold* and this is _italic_ text.";
        Pattern boldPattern = Pattern.compile("\\*(.*?)\\*");
        Pattern italicPattern = Pattern.compile("_(.*?)_");
        
        StringBuffer sb = new StringBuffer();
        Matcher matcher = boldPattern.matcher(input);
        
        // First replace bold
        while (matcher.find()) {
            matcher.appendReplacement(sb, "<b>$1</b>");
        }
        matcher.appendTail(sb);
        
        // Then replace italic in the modified string
        String intermediate = sb.toString();
        matcher = italicPattern.matcher(intermediate);
        sb = new StringBuffer();
        while (matcher.find()) {
            matcher.appendReplacement(sb, "<i>$1</i>");
        }
        matcher.appendTail(sb);
        
        System.out.println("Original: " + input);
        System.out.println("Modified: " + sb.toString());
    }
}

此示例显示了两个替换过程 - 首先是粗体,然后是斜体格式。 我们在第二个过程中处理第一个替换的中间结果。 请注意使用非贪婪量词 (.*?) 来匹配标记之间最短的可能文本。

自定义替换逻辑

此示例演示了复杂的替换逻辑,我们在替换之前处理每个匹配项。 我们将通过用星号替换字符来混淆电子邮件地址,但第一个和最后一个字符除外。

CustomLogic.java
package com.zetcode;

import java.util.regex.*;

public class CustomLogic {
    public static void main(String[] args) {
        String input = "Contact us at support@example.com or sales@company.org";
        Pattern pattern = Pattern.compile("\\b[\\w.%-]+@[\\w.-]+\\.[a-zA-Z]{2,6}\\b");
        Matcher matcher = pattern.matcher(input);
        
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            String email = matcher.group();
            String obfuscated = obfuscateEmail(email);
            matcher.appendReplacement(sb, obfuscated);
        }
        matcher.appendTail(sb);
        
        System.out.println("Original: " + input);
        System.out.println("Modified: " + sb.toString());
    }
    
    private static String obfuscateEmail(String email) {
        String[] parts = email.split("@");
        String local = parts[0];
        String domain = parts[1];
        
        // Obfuscate local part (keep first and last character)
        if (local.length() > 2) {
            local = local.charAt(0) + "*".repeat(local.length() - 2) + 
                    local.charAt(local.length() - 1);
        }
        
        return local + "@" + domain;
    }
}

该示例显示了如何将 appendReplacement 与每个匹配项的自定义处理逻辑一起使用。 我们分割电子邮件地址,处理本地部分,然后将其重新组合以进行替换。

多行替换

此示例演示了如何使用 appendReplacement 处理多行输入。 我们将行号添加到多行字符串中的每一行。

MultilineProcessing.java
package com.zetcode;

import java.util.regex.*;

public class MultilineProcessing {
    public static void main(String[] args) {
        String input = "First line\nSecond line\nThird line\nFourth line";
        Pattern pattern = Pattern.compile("^.*$", Pattern.MULTILINE);
        Matcher matcher = pattern.matcher(input);
        
        StringBuffer sb = new StringBuffer();
        int lineNumber = 1;
        while (matcher.find()) {
            String replacement = lineNumber + ": " + matcher.group();
            matcher.appendReplacement(sb, replacement);
            lineNumber++;
        }
        matcher.appendTail(sb);
        
        System.out.println("Original:\n" + input);
        System.out.println("\nModified:\n" + sb.toString());
    }
}

这里的关键是使用 Pattern.MULTILINE 标志来使 ^ 和 $ 在每行的开头和结尾处匹配。 我们维护一个计数器,以便在使用 appendReplacement 处理每一行时对其进行编号。

带有反向引用的复杂模式

这个高级示例显示了如何在模式和替换中使用反向引用。 我们将找到重复的单词并在输出中标记它们。

DuplicateWords.java
package com.zetcode;

import java.util.regex.*;

public class DuplicateWords {
    public static void main(String[] args) {
        String input = "This this is a test test of duplicate word detection.";
        Pattern pattern = Pattern.compile("\\b(\\w+)(\\s+\\1\\b)+", Pattern.CASE_INSENSITIVE);
        Matcher matcher = pattern.matcher(input);
        
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            matcher.appendReplacement(sb, "[DUPLICATE: $1]");
        }
        matcher.appendTail(sb);
        
        System.out.println("Original: " + input);
        System.out.println("Modified: " + sb.toString());
    }
}

模式 \b(\w+)(\s+\1\b)+ 匹配重复的单词。 替换使用 $1 来引用第一个捕获组(重复的单词)。 CASE_INSENSITIVE 标志确保大小写差异不会阻止重复项的检测。

来源

Java Matcher.appendReplacement 文档

本教程涵盖了 Matcher.appendReplacement 的各种用途,从基本到高级场景。 当与正则表达式结合使用时,该方法提供了强大的字符串操作功能。

作者

我叫 Jan Bodnar,是一位敬业的程序员,在该领域拥有多年的经验。 我于 2007 年开始撰写编程文章,此后撰写了 1,400 多篇文章和八本电子书。 凭借超过八年的教学经验,我致力于分享我的知识并帮助他人掌握编程概念。

列出所有Java教程