Comment remplacer des sous-chaînes littérales insensibles à la casse en Java


130

À l'aide de la méthode replace(CharSequence target, CharSequence replacement)de String, comment puis-je rendre la cible insensible à la casse?

Par exemple, la façon dont cela fonctionne actuellement:

String target = "FooBar";
target.replace("Foo", "") // would return "Bar"

String target = "fooBar";
target.replace("Foo", "") // would return "fooBar"

Comment puis-je faire en sorte que le remplacement (ou s'il existe une méthode plus appropriée) ne soit pas sensible à la casse pour que les deux exemples renvoient "Bar"?

Réponses:


284
String target = "FOOBar";
target = target.replaceAll("(?i)foo", "");
System.out.println(target);

Production:

Bar

Il convient de mentionner que replaceAllle premier argument est traité comme un modèle d'expression régulière, ce qui peut entraîner des résultats inattendus. Pour résoudre ce problème, utilisez également Pattern.quotecomme suggéré dans les commentaires.


1
Et si la cible contient $ ou des caractères diacritiques comme á?
stracktracer

3
Je veux dire deux choses: 1. "blÁÜ123" .replaceAll ("(? I) bláü") ne remplace rien. 2. "Sentence! End" .replaceAll ("(? I) Sentence.") Remplace peut-être plus que prévu.
stracktracer

1
Vous ne pouvez pas transformer une chaîne en regex si simple. Ce n'est généralement pas correct, cela ne fonctionnera que pour des cas spécifiques.
Danubian Sailor

19
Utilisez Pattern.quote () pour empêcher que la chaîne de recherche ne soit interprétée comme une expression régulière. Cela résout les bizarreries Unicode énumérées ci-dessus, mais devrait convenir aux jeux de caractères de base. eg target.replaceAll("(?i)"+Pattern.quote("foo"), "");
Jeff Adamson

1
Juste pour être sûr. Pattern.quote ("foo") n'est pas nécessaire si la chaîne est "foo", non? Seulement si c'est quelque chose de plus chic, non?
ed22

10

Si vous ne vous souciez pas du cas, alors peut-être que cela n'a pas d'importance s'il renvoie tout le cas:

target.toUpperCase().replace("FOO", "");

Vous pouvez également passer le Locale dans toUpperCase (locale) si vous traitez avec des caractères comme á.
rob

10

Pas aussi élégant peut-être que d'autres approches, mais c'est assez solide et facile à suivre, en particulier. pour les nouveaux utilisateurs de Java. Une chose qui m'intéresse à propos de la classe String est la suivante: elle existe depuis très longtemps et bien qu'elle prenne en charge un remplacement global par une expression régulière et un remplacement global par des chaînes (via CharSequences), ce dernier n'a pas de paramètre booléen simple : 'isCaseInsensitive'. Vraiment, vous auriez pensé qu'en ajoutant simplement ce petit interrupteur, tous les problèmes que son absence cause aux débutants en particulier auraient pu être évités. Maintenant sur JDK 7, String ne prend toujours pas en charge ce petit ajout!

Enfin bref, je vais arrêter de me plaindre. Pour tout le monde, en particulier les nouveaux utilisateurs de Java, voici votre deus ex machina à copier-coller . Comme je l'ai dit, pas aussi élégant et ne vous rapportera pas de prix de codage, mais cela fonctionne et est fiable. Tous les commentaires, n'hésitez pas à contribuer. (Oui, je sais, StringBuffer est probablement un meilleur choix pour gérer les deux lignes de mutation de chaîne de caractères, mais il est assez facile d'échanger les techniques.)

public String replaceAll(String findtxt, String replacetxt, String str, 
        boolean isCaseInsensitive) {
    if (str == null) {
        return null;
    }
    if (findtxt == null || findtxt.length() == 0) {
        return str;
    }
    if (findtxt.length() > str.length()) {
        return str;
    }
    int counter = 0;
    String thesubstr = "";
    while ((counter < str.length()) 
            && (str.substring(counter).length() >= findtxt.length())) {
        thesubstr = str.substring(counter, counter + findtxt.length());
        if (isCaseInsensitive) {
            if (thesubstr.equalsIgnoreCase(findtxt)) {
                str = str.substring(0, counter) + replacetxt 
                    + str.substring(counter + findtxt.length());
                // Failing to increment counter by replacetxt.length() leaves you open
                // to an infinite-replacement loop scenario: Go to replace "a" with "aa" but
                // increment counter by only 1 and you'll be replacing 'a's forever.
                counter += replacetxt.length();
            } else {
                counter++; // No match so move on to the next character from
                           // which to check for a findtxt string match.
            }
        } else {
            if (thesubstr.equals(findtxt)) {
                str = str.substring(0, counter) + replacetxt 
                    + str.substring(counter + findtxt.length());
                counter += replacetxt.length();
            } else {
                counter++;
            }
        }
    }
    return str;
}

cette méthode est extrêmement lente car sa complexité est O (size_str * size_findtext)
Mladen Adamovic

9

Les expressions régulières sont assez complexes à gérer en raison du fait que certains caractères sont réservés: par exemple, "foo.bar".replaceAll(".")produit une chaîne vide, car le point signifie "n'importe quoi". Si vous souhaitez remplacer, seul le point doit être indiqué en paramètre "\\.".

Une solution plus simple consiste à utiliser des objets StringBuilder pour rechercher et remplacer du texte. Il en faut deux: un qui contient le texte en version minuscule tandis que le second contient la version originale. La recherche est effectuée sur le contenu en minuscules et l'index détecté remplacera également le texte d'origine.

public class LowerCaseReplace 
{
    public static String replace(String source, String target, String replacement)
    {
        StringBuilder sbSource = new StringBuilder(source);
        StringBuilder sbSourceLower = new StringBuilder(source.toLowerCase());
        String searchString = target.toLowerCase();

        int idx = 0;
        while((idx = sbSourceLower.indexOf(searchString, idx)) != -1) {
            sbSource.replace(idx, idx + searchString.length(), replacement);
            sbSourceLower.replace(idx, idx + searchString.length(), replacement);
            idx+= replacement.length();
        }
        sbSourceLower.setLength(0);
        sbSourceLower.trimToSize();
        sbSourceLower = null;

        return sbSource.toString();
    }


    public static void main(String[] args)
    {
        System.out.println(replace("xXXxyyyXxxuuuuoooo", "xx", "**"));
        System.out.println(replace("FOoBaR", "bar", "*"));
    }
}

1
Fonctionne très bien! Notez que "target" ne doit pas être nul. La suppression de sbSourceLower ne devrait plus être nécessaire.
msteiger

Merci pour la solution concise et merci à @msteiger pour la correction. Je me demande pourquoi personne n'a ajouté une solution similaire à une bibliothèque célèbre comme Guava, Apache Commons, etc.?
yetanothercoder

4

Pour les caractères non Unicode:

String result = Pattern.compile("(?i)препарат", 
Pattern.UNICODE_CASE).matcher(source).replaceAll("БАД");

4

org.apache.commons.lang3.StringUtils:

public static String replaceIgnoreCase (String text, String searchString, String replacement)

La casse remplace de manière insensible toutes les occurrences d'une chaîne dans une autre chaîne.


3

J'aime la réponse de smas qui utilise une expression régulière. Si vous allez faire le même remplacement plusieurs fois, il est logique de précompiler l'expression régulière une fois:replaceAll

import java.util.regex.Pattern;

public class Test { 

    private static final Pattern fooPattern = Pattern.compile("(?i)foo");

    private static removeFoo(s){
        if (s != null) s = fooPattern.matcher(s).replaceAll("");
        return s;
    }

    public static void main(String[] args) {
        System.out.println(removeFoo("FOOBar"));
    }
}

3

Simplifiez-vous la vie sans bibliothèques tierces:

    final String source = "FooBar";
    final String target = "Foo";
    final String replacement = "";
    final String result = Pattern.compile(target, Pattern.LITERAL | Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE).matcher(source)
.replaceAll(Matcher.quoteReplacement(replacement));
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.