Java method to reverse upper-lower case doesn't reverse all letters

Viewed 1352

I wanted to create a method that reverse upper-lower case letters in a word. The problem I'm having is that the method doesn't reverse all the letters. For example when I type "nIceToMeEtyoU" it prints "NiCETomEETYou". It didn't work for the "o", the second "e" and "t". I just couldn't figure out what's wrong with the code.

public static String reverseCase(String str) {
    char changed;
    String a = str;
    for (int i = 0; i < a.length(); i++) {
        char d = a.charAt(i);
        boolean letter = Character.isUpperCase(d);
        if (letter == true) {
            changed = Character.toLowerCase(d);
        } else {
            changed = Character.toUpperCase(d);
        }
        a = a.replace(d, changed);
    }
    return a;
}
10 Answers

String::replace returns a new String with all occurrences of the character you wanted replaced changed.

Also, Strings in Java are immutable, meaning you cannot replace a character in a string while keeping the same string. In order to replace the character at a specific index, see this post

It didn't work for the "o","second e" and "t".

The replace() method replaces all occurrances of the character in the string.

Instead, use a StringBuffer to append each character as you iterate through the string.

Then when the loop is finished you recreate the String using the toString() method of the StringBuffer.

Instead of using String#replace which replaces all instances of the match with the given replacement, I suggest you build the string using a StringBuilder as shown below:

public class Main {
    public static void main(String[] args) {
        // Tests
        System.out.println(reverseCase("nIceToMeEtyoU"));
        System.out.println(reverseCase("NiCETomEETYou"));
    }

    public static String reverseCase(String str) {
        StringBuilder sb = new StringBuilder();

        // Use the enhanced for loop
        for (char ch : str.toCharArray()) {
            if (Character.isUpperCase(ch)) {
                ch = Character.toLowerCase(ch);
            } else {
                ch = Character.toUpperCase(ch);
            }
            sb.append(ch);
        }
        return sb.toString();
    }
}

Output:

NiCEtOmEeTYOu
nIcetOMeetyOU

With your approach, whichever characters are being changed in one pass, will get reverted back in the next passes if they are found again in those passes.

replace replaces all occurrences of a character in a string. So, e.g., the first time you encounter e you'll replace all the es with Es. Then, the when next "original" e (which is now an E) is encountered, all Es will be turned back to es and so on.

Instead of using replace, you could accumulate the characters you encounter in a new object. While you could use a String and add to it with the += operator, using a StringBuilder should have better performance:

public static String reverseCase(String str) {
    StringBuilder sb = new StringBuilder(str.length());
    for (int i = 0; i < str.length(); i++) {
        char c = a.charAt(i);
        id (Character.isUpperCase(c) {
            c = Character.toLowerCase(c);
        } else i (Character.isLowerCase(c)) {
            c = Character.toLowerCase(c);
        }
        sb.append(c);
    }
    return st.toString();
}

Instead of using replace (which replaces all the occurrences in the string) you can concat each character to create a new string.

public static String reverseCase(String input) {
    StringBuilder a = new StringBuilder();
    for (char c : input.toCharArray()) {
      a.append(Character.isUpperCase(c) ? Character.toLowerCase(c) : Character.toUpperCase(c));
    }
    return a.toString();
}

You could StringBuilder for String operations which is not accessed by other threads. The main issue you are facing here is because of the replace() method as mentioned by others. The replace() method replaces all occurrances of the character in the string. Instead of trying to replace, just build new string and return. Another way you could do it is to create an array of characters and replace the character (with the variable : 'changed') at the current position inside the loop.

public static String reverseCase(String str) {
        char changed;
        StringBuilder reversedStringBuilder=new StringBuilder();
        for (int i = 0; i < str.length(); i++) {
            char d = str.charAt(i);
            boolean letter = Character.isUpperCase(d);
            if (letter) {
                changed = Character.toLowerCase(d);
            } else {
                changed = Character.toUpperCase(d);
            }
            reversedStringBuilder.append(changed);
        }
        return reversedStringBuilder.toString();
    }
    
    public static void main(String[] args) {
        System.out.println(reverseCase("nIceToMeEtyoU"));
    }

a.replace(d, changed) replaces every occurrence of d in the string, not just the one at the index you are looking at.

  • So for every letter that occurs an even number of times in the string, the replace of that letter is performed an even number of times, leaving all occurrences at the same case that the first occurrence had from the outset. This was what happened to o, for example. When you met the first o, both occurrences were replaced to upper case O. When you met the next O, it was already upper case, so both occurrences were changed to lower case again.
  • Conversely if a letter occurs an odd number of times, it will through the same procedure be left at the opposite case of what the first occurrence had from the outset. From the outset you had e twice and E once. First the two lower case e were changed to upper case. Second alll three were changed to lower case. Third, all to upper case.

Instead do not perform the replacements directly in the string. Use either a StringBuffer or StringBuilder or a char array. In each of these you can perform the replacement by index so that you are only affecting the occurrence of the letter that you intend to affect.

Bonus tip: if (letter == true) is considered mediocre style. Prefer just if (letter).

Bonus information: Letters exist that become more than one letter when switching case. If I enter the German word Füße (feet), ß only exists in lower case, it becomes SS in upper case. So the correct reverse of the word would be fÜSSE. However, Character.toUpperCase() cannot return two chars, so just leaves it at ß.

Using Streams

import java.util.stream.Collectors;

public class FlipCase {

    static char flipCase(char ch) {
        if (Character.isUpperCase(ch)) return Character.toLowerCase(ch);
        else return Character.toUpperCase(ch);
    }

    public static String reverseCase(String str) {
        return str
                .chars()
                .mapToObj(i -> (char) i)
                .map(ch -> String.valueOf(FlipCase.flipCase(ch)))
                .collect(Collectors.joining());
    }

    public static void main(String[] args) {
        System.out.println(reverseCase("nIceToMeEtyoU")); // NiCEtOmEeTYOu
    }
}

As has been stated, the issue is the replace. Best to use a StringBuilder and create a new String.

I also offer an alternative that you may find interesting.

You can do it by flipping the bit from upper case to lower case and visa versa.

The key is knowing that the sixth bit from the right is the bit between cases for Strings (as in your example). That would be 2 to the 5th or 32. The Exclusive OR operator (^) can be used to flip the bit.

String s = "nIceToMeEtyoU";
System.out.println(reverseCase(s));
    
public static String reverseCase(String s) {
   StringBuilder sb = new StringBuilder(s.length());
   for(char c : s.toCharArray()) {
       // change the case if letter
       sb.append(Character.isLetter(c) ? (char) (c ^ 32) : c);
   }
   return sb.toString();
}

Prints

NiCEtOmEeTYOu

Did you try Apache Commons before? I'm sure that it is the most simple way to handle this case:

package com.awm4n.swapcaseexample;
import org.apache.commons.lang3.StringUtils;
public class SwapCaseExample {
    public static void main(String[] args) {
        String str = "TheMOStsimPLEWAytoswaPCASE";            
        System.out.println( StringUtils.swapCase(str) );
    }
}

And the output will be:

tHEmosTSIMplewaYTOSWApcase
Related