Java Top

I just announced the new Spring Boot 2 material, coming in REST With Spring:

>> CHECK OUT THE COURSE

1. Introduction

In this short tutorial, we’ll see several ways to remove leading and trailing characters from a String. For the sake of simplicity, we’ll remove zeroes in the examples.

With each implementation, we’ll create two methods: one for leading, and one for trailing zeroes.

This problem has an edge case: what do we want to do, when the input contains zeroes only? Return an empty String, or a String containing a single zero? We’ll see implementations for both use cases in each of the solutions.

We have unit tests for each implementation, which you can find on GitHub.

2. Using StringBuilder

In our first solution, we’ll create a StringBuilder with the original String, and we’ll delete the unnecessary characters from the beginning or the end:

String removeLeadingZeroes(String s) {
    StringBuilder sb = new StringBuilder(s);
    while (sb.length() > 0 && sb.charAt(0) == '0') {
        sb.deleteCharAt(0);
    }
    return sb.toString();
}

String removeTrailingZeroes(String s) {
    StringBuilder sb = new StringBuilder(s);
    while (sb.length() > 0 && sb.charAt(sb.length() - 1) == '0') {
        sb.setLength(sb.length() - 1);
    }
    return sb.toString();
}

Note, that we use StringBuilder.setLength() instead of StringBuilder.deleteCharAt() when we remove trailing zeroes because it also deletes the last few characters and it’s more performant.

If we don’t want to return an empty String when the input contains only zeroes, the only thing we need to do is to stop the loop if there’s only a single character left.

Therefore, we change the loop condition:

String removeLeadingZeroes(String s) {
    StringBuilder sb = new StringBuilder(s);
    while (sb.length() > 1 && sb.charAt(0) == '0') {
        sb.deleteCharAt(0);
    }
    return sb.toString();
}

String removeTrailingZeroes(String s) {
    StringBuilder sb = new StringBuilder(s);
    while (sb.length() > 1 && sb.charAt(sb.length() - 1) == '0') {
        sb.setLength(sb.length() - 1);
    }
    return sb.toString();
}

3. Using String.subString()

In this solution when we remove leading or trailing zeroes, we find the position of the first or last non-zero character.

After that, we only have to call substring(), to return the remaining parts:

String removeLeadingZeroes(String s) {
    int index;
    for (index = 0; index < s.length(); index++) {
        if (s.charAt(index) != '0') {
            break;
        }
    }
    return s.substring(index);
}

String removeTrailingZeroes(String s) {
    int index;
    for (index = s.length() - 1; index >= 0; index--) {
        if (s.charAt(index) != '0') {
            break;
        }
    }
    return s.substring(0, index + 1);
}

Note, that we have to declare the variable index before the for loop because we want to use the variable outside the loop’s scope.

Also note, that we have to look for non-zero characters manually, since String.indexOf() and String.lastIndexOf() work only for exact matching.

If we don’t want to return an empty String, we have to do the same thing like before: change the loop condition:

String removeLeadingZeroes(String s) {
    int index;
    for (index = 0; index < s.length() - 1; index++) {
        if (s.charAt(index) != '0') {
            break;
        }
    }
    return s.substring(index);
}

String removeTrailingZeroes(String s) {
    int index;
    for (index = s.length() - 1; index > 0; index--) {
        if (s.charAt(index) != '0') {
            break;
        }
    }
    return s.substring(0, index + 1);
}

4. Using Apache Commons

Apache Commons has many useful classes, including org.apache.commons.lang.StringUtils. To be more precise, this class is in Apache Commons Lang3.

4.1. Dependencies

We can use Apache Commons Lang3 by inserting this dependency into our pom.xml file:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.8.1</version>
</dependency>

4.2. Implementation

In the StringUtils class, we have the methods stripStart() and stripEnd(). They remove leading and trailing characters respectively.

Since it’s exactly what we need, our solution is pretty straightforward:

String removeLeadingZeroes(String s) {
    return StringUtils.stripStart(s, "0");
}

String removeTrailingZeroes(String s) {
    return StringUtils.stripEnd(s, "0");
}

Unfortunately, we can’t configure, if we want to remove all occurrences or not. Therefore, we need to control it manually.

If the input wasn’t empty, but the stripped String is empty, then we have to return exactly one zero:

String removeLeadingZeroes(String s) {
    String stripped = StringUtils.stripStart(s, "0");
    if (stripped.isEmpty() && !s.isEmpty()) {
        return "0";
    }
    return stripped;
}

String removeTrailingZeroes(String s) {
    String stripped = StringUtils.stripEnd(s, "0");
    if (stripped.isEmpty() && !s.isEmpty()) {
        return "0";
    }
    return stripped;
}

Note, that these methods accept a String as their second parameter. This String represents a set of characters, not a sequence we want to remove.

For example, if we pass “01”, they’ll remove any leading or trailing characters, that are either ‘0’ or ‘1’.

5. Using Guava

Guava also provides many utility classes. For this problem, we can use com.google.common.base.CharMatcher, which provides utility methods to interact with matching characters.

5.1. Dependencies

To use Guava, we should add the following dependencies to our pom.xml file:

<dependency>
    <groupId>com.google.guava</groupId>
    <artifactId>guava</artifactId>
    <version>27.0.1-jre</version>
</dependency>

Note, that if we want to use Guava in an Android application, we should use version 27.0-android instead.

5.2. Implementation

In our case, we’re interested in trimLeadingFrom() and trimTrailingFrom().

As their name suggests, they remove any leading or trailing character respectively from a String, which matches the CharMatcher:

String removeLeadingZeroes(String s) {
    return CharMatcher.is('0').trimLeadingFrom(s);
}

String removeTrailingZeroes(String s) {
    return CharMatcher.is('0').trimTrailingFrom(s);
}

They have the same characteristics, as the Apache Commons methods we saw.

Therefore, if we don’t want to remove all zeroes, we can use the same trick:

String removeLeadingZeroes(String s) {
    String stripped = CharMatcher.is('0').trimLeadingFrom(s);
    if (stripped.isEmpty() && !s.isEmpty()) {
        return "0";
    }
    return stripped;
}

String removeTrailingZeroes(String s) {
    String stripped = CharMatcher.is('0').trimTrailingFrom(s);
    if (stripped.isEmpty() && !s.isEmpty()) {
        return "0";
    }
    return stripped;
}

Note, that with CharMatcher we can create more complex matching rules.

6. Using Regular Expressions

Since our problem is a pattern matching problem, we can use regular expressions: we want to match all zeroes at the beginning or the end of a String.

On top of that, we want to remove those matching zeroes. In other words, we want to replace them with nothing, or in other words, an empty String.

We can do exactly that, with the String.replaceAll() method:

String removeLeadingZeroes(String s) {
    return s.replaceAll("^0+", "");
}

String removeTrailingZeroes(String s) {
    return s.replaceAll("0+$", "");
}

If we don’t want to remove all zeroes, we could use the same solution we used with Apache Commons and Guava. However, there’s a pure regular expression way to do this: we have to provide a pattern, which doesn’t match the whole String.

That way, if the input contains only zeroes, the regexp engine will keep exactly one out from the matching. We can do this with the following patterns:

String removeLeadingZeroes(String s) {
    return s.replaceAll("^0+(?!$)", "");
}

String removeTrailingZeroes(String s) {
    return s.replaceAll("(?!^)0+$", "");
}

Note, that “(?!^)” and “(?!$)” means that it’s not the beginning or the end of the String respectively.

7. Conclusion

In this tutorial, we saw several ways to remove leading and trailing characters from a String. The choice between these implementations is often simply personal preference.

As usual, the examples are available over on GitHub.

Java bottom

I just announced the new Spring Boot 2 material, coming in REST With Spring:

>> CHECK OUT THE LESSONS

Leave a Reply

avatar
  Subscribe  
Notify of