JavaRush /Java Blog /Random EN /Breaking down the StringUtils class

Breaking down the StringUtils class

Published in the Random EN group
Hello everyone, my dear readers. I try to write about what really interests me and what worries me at the moment. Therefore, today there will be light reading, which will be useful to you as a reference in the future: let's talk about StringUtils . Parsing the StringUtils class - 1It just so happened that at one time I ignored the Apache Commons Lang 3 library . This is a library with auxiliary classes for working with different objects. Such a collection of useful methods for working with strings, collections, and so on. On the current project, where I had to work in more detail with strings in a 25-year-old translation of business logic (from COBOL to Java), it turned out that I did not have enough deep knowledge about the StringUtils class. So I had to create everything myself. What I mean? The fact that you can not write certain tasks with string manipulation yourself, but use a ready-made solution. What's wrong with writing yourself? At least in that it is more code that has been written for a long time. No less acute is the question of testing the code that is written additionally. When we use a library that has a proven track record of being good, we expect it to be already tested and not have to write multiple test cases to test it. It just so happened that the set of methods for working with a string in Java is not so big. Really missing many methods that would be useful for work. This class was also created to provide checks for NullPointerException. The plan of our article will be as follows:
  1. How to connect?
  2. Examples from my work: how, without knowing about such a useful class, I created my own bicycle crutch.
  3. We analyze other methods that seemed interesting to me.
  4. Let's summarize.
All cases will be added to a separate repository in the Javarush Community organization on GitHub. Examples and tests for them will be separately recorded there.

0. How to connect

Those who go toe to toe with me are already more or less familiar with both git and maven, so further I will rely on this knowledge and not repeat myself. For those who missed my previous articles or just joined the reading, here are the materials about maven and gita . Of course, without a build system (maven, gredl) you can also connect everything manually, but this is wild in our time and you definitely don’t need to do this: it’s better to learn how to do everything right right away. Therefore, to work with maven, first add the appropriate dependency:
<dependency>
   <groupId>org.apache.commons</groupId>
   <artifactId>commons-lang3</artifactId>
   <version>${apache.common.version}</version>
</dependency>
Where ${apache.common.version} is the version of this library. Next, to import in some class, add an import:
import org.apache.commons.lang3.StringUtils;
And that's it, it's in the hat))

1. Examples from a real project

  • leftPad method

The first example generally seems so stupid now that it's good that my colleagues knew about StringUtils.leftPad and prompted me. What was the task: the code was built in such a way that it was necessary to transform the data if they did not come quite correctly. It was expected that the string field should consist only of numbers, i.e. if its length is 3 and its value is 1, then the entry should be “001”. That is, first you need to remove all spaces, and then pave it with zeros. More examples to make the essence of the problem clear: from “12” -> “012” from “1” -> “001” And so on. What did I do? Described this in the LeftPadExample class . I wrote a method that does it all:
public static String ownLeftPad(String value) {
   String trimmedValue = value.trim();

   if(trimmedValue.length() == value.length()) {
       return value;
   }

   StringBuilder newValue = new StringBuilder(trimmedValue);

   IntStream.rangeClosed(1, value.length() - trimmedValue.length())
           .forEach(it -> newValue.insert(0, "0"));
   return newValue.toString();
}
I took as a basis the idea that we can simply get the difference between the original and the trimmed value and fill in front with zeros. For this, I used an IntStream to do the same operation n times. And it definitely needs to be tested. And here is what could have been done if I knew in advance about the StringUtils.leftPad method :
public static String apacheCommonLeftPad(String value) {
   return StringUtils.leftPad(value.trim(), value.length(), "0");
}
As you can see, there is much less code, while still using a well-established library. For this case, I created two tests in the LeftPadExampleTest class (usually when they plan to test a class, they create a class with the same name+Test in the same package, only in src/test/java). These tests check first one method to correctly transform the value, then another. Of course, much more tests would need to be written, but the topic of testing in our case is not the main one:
package com.github.codegymcommunity.stringutilsdemo;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

@DisplayName("Unit-level testing for LeftPadExample")
class LeftPadExampleTest {

   @DisplayName("Should transform by using ownLeftPad method as expected")
   @Test
   public void shouldTransformOwnLeftPadAsExpected() {
       //given
       String value = "1   ";
       String expectedTransformedValue = "0001";

       //when
       String transformedValue = LeftPadExample.ownLeftPad(value);

       //then
       Assertions.assertEquals(expectedTransformedValue, transformedValue);
   }

   @DisplayName("Should transform by using StringUtils method as expected")
   @Test
   public void shouldTransformStringUtilsLeftPadAsExpected() {
       //given
       String value = "1   ";
       String expectedTransformedValue = "0001";

       //when
       String transformedValue = LeftPadExample.apacheCommonLeftPad(value);

       //then
       Assertions.assertEquals(expectedTransformedValue, transformedValue);
   }

}
I can make a few comments about the tests for now. They are written according to JUnit 5:
  1. A test will be treated as a test if it has the corresponding @Test annotation.
  2. If it is difficult to describe the work of the test in the name, or the description is long and inconvenient to read, you can add the @DisplayName annotation and make it a normal description that will be visible when the tests are run.
  3. When writing tests, I use the BDD approach, in which I divide tests into logical parts:
    1. //given - data setup block before the test;
    2. //when - the block where the part of the code that we are testing is launched;
    3. //then is a block in which the results of the when block work are checked.
If run, they will confirm that everything is working as expected.

  • stripStart method

Here I needed to solve the issue with a string that could have spaces and commas at the beginning. After the transformation, they should not have been in the new meaning. The problem statement is as clear as ever. A few examples will solidify our understanding: “, , books” -> “books” “,,,books” -> “books” b , books” -> “b , books ” which has two methods. One - with its own solution:
public static String ownStripStart(String value) {
   int index = 0;
   List commaSpace = asList(" ", ",");
   for (int i = 0; i < value.length(); i++) {
       if (commaSpace.contains(String.valueOf(value.charAt(i)))) {
           index++;
       } else {
           break;
       }
   }
   return value.substring(index);
}
Here the idea was to find the index starting from which there are no more spaces and commas. If they were not at all at the beginning, then the index will be zero. And the second - with a solution through StringUtils :
public static String apacheCommonLeftPad(String value) {
   return StringUtils.stripStart(value, StringUtils.SPACE + COMMA);
}
Here we pass in the first argument information about which string we are working with, and in the second we pass a string consisting of characters to be skipped. In the same way, we create the StripStartExampleTest class:
package com.github.codegymcommunity.stringutilsdemo;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

@DisplayName("Unit-level testing for StripStartExample")
class StripStartExampleTest {

   @DisplayName("Should transform by using stripStart method as expected")
   @Test
   public void shouldTransformOwnStripStartAsExpected() {
       //given
       String value = ", , books";
       String expectedTransformedValue = "books";

       //when
       String transformedValue = StripStartExample.ownStripStart(value);

       //then
       Assertions.assertEquals(expectedTransformedValue, transformedValue);
   }

   @DisplayName("Should transform by using StringUtils method as expected")
   @Test
   public void shouldTransformStringUtilsStripStartAsExpected() {
       //given
       String value = ", , books";
       String expectedTransformedValue = "books";

       //when
       String transformedValue = StripStartExample.apacheCommonLeftPad(value);

       //then
       Assertions.assertEquals(expectedTransformedValue, transformedValue);
   }
}

  • isEmpty method

This method, of course, is much simpler, but it is no less useful for that. It extends the String.isEmpty() method by adding a null check. For what? To avoid NullPointerException, that is, to avoid calling methods on a variable that is null . So instead of writing:
if(value != null && value.isEmpty()) {
   //doing something
}
You can simply do this:
if(StringUtils.isEmpty(value)) {
   //doing something
}
The advantage of this method is that you can immediately see where which method is used.

2. Parsing other methods of the StringUtils class

Now let's talk about those methods that, in my opinion, also deserve attention. Speaking generally about StringUtils , it's worth saying that it provides null-safe methods similar to those found in the String class (as is the case with the isEmpty method ). Let's go through them:

  • compare method

There is such a method in String and there will be a NullPointerException if one of them is null in comparing two strings. To avoid ugly checks in our code, we can use the StringUtils.compare(String str1, String str2) method : it returns an int as the result of the comparison. What do these values ​​mean? int = 0 if they are the same (or both are null). int < 0 if str1 is less than str2. int > 0 if str1 is greater than str2. Also, if you look at their documentation, then the following scenarios are presented in the Javadoc of this method:
StringUtils.compare(null, null)   = 0
StringUtils.compare(null , "a")   < 0
StringUtils.compare("a", null)    > 0
StringUtils.compare("abc", "abc") = 0
StringUtils.compare("a", "b")     < 0
StringUtils.compare("b", "a")     > 0
StringUtils.compare("a", "B")     > 0
StringUtils.compare("ab", "abc")  < 0

  • contains... methods

Here, the developers of the utility went wild. Whatever method you want. I decided to put them together:
  1. contains is a method that checks if the intended string is inside another string. Why is it useful? You can use this method if you need to make sure that there is a certain word in the text.

    Examples:

    StringUtils.contains(null, *)     = false
    StringUtils.contains(*, null)     = false
    StringUtils.contains("", "")      = true
    StringUtils.contains("abc", "")   = true
    StringUtils.contains("abc", "a")  = true
    StringUtils.contains("abc", "z")  = false

    Again, NPE (Null Pointer Exception) security is present.

  2. containsAny - a method that checks if there is at least some character from those presented in the string. Also a useful thing: often you have to do this.

    Examples from documentation:

    StringUtils.containsAny(null, *)                  = false
    StringUtils.containsAny("", *)                    = false
    StringUtils.containsAny(*, null)                  = false
    StringUtils.containsAny(*, [])                    = false
    StringUtils.containsAny("zzabyycdxx", ['z', 'a']) = true
    StringUtils.containsAny("zzabyycdxx", ['b', 'y']) = true
    StringUtils.containsAny("zzabyycdxx", ['z', 'y']) = true
    StringUtils.containsAny("aba", ['z'])             = false

  3. containsIgnoreCase is a useful extension to the contains method . Indeed, to check such a case without this method, you will have to go through several options. And so only one method will be harmoniously used.

  4. Some examples from the docs:

    StringUtils.containsIgnoreCase(null, *) = false
    StringUtils.containsIgnoreCase(*, null) = false
    StringUtils.containsIgnoreCase("", "") = true
    StringUtils.containsIgnoreCase("abc", "") = true
    StringUtils.containsIgnoreCase("abc", "a") = true
    StringUtils.containsIgnoreCase("abc", "z") = false
    StringUtils.containsIgnoreCase("abc", "A") = true
    StringUtils.containsIgnoreCase("abc", "Z") = false

  5. containsNone - judging by the name, you can already understand what is being checked. There should be no lines inside. Useful stuff, definitely. Quick search for some objectionable characters ;). In our telegram bot we will filter mats, we will not pass by these funny methods.

    And examples, where without them:

    StringUtils.containsNone(null, *)       = true
    StringUtils.containsNone(*, null)       = true
    StringUtils.containsNone("", *)         = true
    StringUtils.containsNone("ab", '')      = true
    StringUtils.containsNone("abab", 'xyz') = true
    StringUtils.containsNone("ab1", 'xyz')  = true
    StringUtils.containsNone("abz", 'xyz')  = false

  • defaultString method

A series of methods that help avoid adding an extra if if the string is null and some default value needs to be set. There are many options for every taste. Chief among them is StringUtils.defaultString(final String str, final String defaultStr) - in case str is null, we'll just pass the defaultStr value. Examples from documentation:
StringUtils.defaultString(null, "NULL")  = "NULL"
StringUtils.defaultString("", "NULL")    = ""
StringUtils.defaultString("bat", "NULL") = "bat"
It is very convenient to use it when you create a POJO class with data.

  • deleteWhitespace method

This is an interesting method, although there are not so many options for its application. However, if such an opportunity presents itself, the method will definitely be very useful. It removes all spaces from a string. Wherever this gap is, there will be no trace of it))) Examples from the docks:
StringUtils.deleteWhitespace(null)         = null
StringUtils.deleteWhitespace("")           = ""
StringUtils.deleteWhitespace("abc")        = "abc"
StringUtils.deleteWhitespace("   ab  c  ") = "abc"

  • endsWith method

Speaks for himself. This is a very useful method: it checks if a string ends with the suggested string or not. Often this is needed. Of course, you can write a check yourself, but using a ready-made method is clearly more convenient and better. Examples:
StringUtils.endsWith(null, null)      = true
StringUtils.endsWith(null, "def")     = false
StringUtils.endsWith("abcdef", null)  = false
StringUtils.endsWith("abcdef", "def") = true
StringUtils.endsWith("ABCDEF", "def") = false
StringUtils.endsWith("ABCDEF", "cde") = false
StringUtils.endsWith("ABCDEF", "")    = true
As you can see, everything ends with an empty string))) I think that this example (StringUtils.endsWith("ABCDEF", "") = true) just comes as a bonus, because this is absurd) There is also a method that ignores case .

  • equals method

A great example of a null safe method that compares two strings. Whatever we put there, the answer will be, and will be without errors. Examples:
StringUtils.equals(null, null)   = true
StringUtils.equals(null, "abc")  = false
StringUtils.equals("abc", null)  = false
StringUtils.equals("abc", "abc") = true
StringUtils.equals("abc", "ABC") = false
Of course, there is also equalsIgnoreCase - everything is done exactly the same, only we ignore case. Let's see?
StringUtils.equalsIgnoreCase(null, null)   = true
StringUtils.equalsIgnoreCase(null, "abc")  = false
StringUtils.equalsIgnoreCase("abc", null)  = false
StringUtils.equalsIgnoreCase("abc", "abc") = true
StringUtils.equalsIgnoreCase("abc", "ABC") = true

  • equalsAny method

Go ahead and extend the equals method . Let's say instead of multiple equality tests, we want to perform one. For this, we can pass a string with which they will also compare a set of strings, if any of them is equal to the proposed one, then it will be TRUE. We pass a string and a collection of strings to compare them with each other (the first string with the strings from the collection). Difficult? Here are examples from the docs to help you understand what I mean:
StringUtils.equalsAny(null, (CharSequence[]) null) = false
StringUtils.equalsAny(null, null, null)    = true
StringUtils.equalsAny(null, "abc", "def")  = false
StringUtils.equalsAny("abc", null, "def")  = false
StringUtils.equalsAny("abc", "abc", "def") = true
StringUtils.equalsAny("abc", "ABC", "DEF") = false
There is also equalsAnyIgnoreCase . And examples for it:
StringUtils.equalsAnyIgnoreCase(null, (CharSequence[]) null) = false
StringUtils.equalsAnyIgnoreCase(null, null, null)    = true
StringUtils.equalsAnyIgnoreCase(null, "abc", "def")  = false
StringUtils.equalsAnyIgnoreCase("abc", null, "def")  = false
StringUtils.equalsAnyIgnoreCase("abc", "abc", "def") = true
StringUtils.equalsAnyIgnoreCase("abc", "ABC", "DEF") = true

Outcome

As a result, we leave with the knowledge of what StringUtils iswhat useful methods it has. Well, with the realization that there are such useful things and you don’t need to fence crutches every time in places where you could close the issue with a ready-made solution. In general, we have analyzed only a part of the methods. If there is a desire, I can continue: there are many more of them, and they really deserve attention. If you have any ideas how else it can be submitted, please write - I'm always open to new ideas. The documentation for the methods is written very high quality, test cases with results have been added, which helps to better understand the operation of the method. Therefore, we do not shy away from reading the documentation: it will dispel your doubts about the functionality of the utility. To get a new coding experience, I advise you to look at how utility classes are made and written. This will be useful in the future, since usually each project has its own utility classes, and writing experience would be helpful. Traditionally, I propose to subscribe to the github onmy account ) For those who don't know about my telegram bot project, here is a link to the first article . Thank you all for reading. Added some useful links below. Breaking down the StringUtils class - 2
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION