JavaRush /Java Blog /Random-KO /StringUtils 클래스를 분석해 보겠습니다.
Roman Beekeeper
레벨 35

StringUtils 클래스를 분석해 보겠습니다.

Random-KO 그룹에 게시되었습니다
사랑하는 독자 여러분, 안녕하세요. 나는 지금 내가 정말로 관심을 갖고 있는 것과 내가 걱정하는 것에 대해 글을 쓰려고 노력합니다. 따라서 오늘은 나중에 참고할 때 유용할 가벼운 내용을 읽어보겠습니다. StringUtils 에 대해 이야기해 보겠습니다 . 한때 나는 Apache Commons Lang 3StringUtils 클래스를 분석해 보겠습니다. - 1 라이브러리를 우회했습니다 . 이것은 다양한 객체로 작업하기 위한 보조 클래스가 있는 라이브러리입니다. 이는 문자열, 컬렉션 등을 작업하는 데 유용한 메서드 모음입니다. 25년 된 비즈니스 로직(COBOL에서 Java로)을 번역하기 위해 문자열을 사용하여 더 자세히 작업해야 했던 현재 프로젝트에서 StringUtils 클래스 에 대한 깊은 지식이 없다는 것이 밝혀졌습니다 . 그래서 모든 것을 직접 만들어야 했어요. 내 말은? 문자열 조작과 관련된 특정 작업을 직접 작성할 필요가 없고 기성 솔루션을 사용한다는 사실입니다. 직접 쓰는게 뭐가 문제인가요? 적어도 이것은 오래 전에 이미 작성된 코드에 가깝습니다. 추가로 작성된 코드를 테스트하는 문제도 그에 못지않게 시급합니다. 우리가 훌륭하다고 입증된 라이브러리를 사용할 때, 우리는 그것이 이미 테스트를 거쳤으며 이를 테스트하기 위해 많은 테스트 사례를 작성할 필요가 없다고 기대합니다. Java에서 문자열을 작업하기 위한 메소드 세트가 그다지 크지 않은 경우도 있습니다. 실제로 업무에 유용한 방법은 많지 않습니다. 이 클래스는 NullPointerException에 대한 검사를 제공하기 위해 생성됩니다. 우리 기사의 개요는 다음과 같습니다.
  1. 연결하는 방법?
  2. 내 작업의 예: 이렇게 유용한 수업을 모르고 자전거 목발을 만든 방법.
  3. 제가 흥미로웠던 다른 방법을 살펴보겠습니다.
  4. 요약해보자.
모든 사례는 GitHub의 Javarush 커뮤니티 조직에 있는 별도의 저장소 에 추가됩니다 . 이에 대한 별도의 예제와 테스트가 있을 것입니다.

0. 연결방법

나와 함께 걷는 사람들은 이미 Git과 Maven에 어느 정도 익숙하므로 앞으로는 이 지식에 의존하고 반복하지 않겠습니다. 이전 기사를 놓치셨거나 이제 막 읽기 시작한 분들을 위해 MavenGit 에 대한 자료를 제공합니다 . 물론 빌드 시스템(Maven, Gredl) 없이 모든 것을 수동으로 연결할 수도 있지만 요즘은 미친 짓이고 꼭 그렇게 할 필요는 없습니다. 모든 것을 올바르게 수행하는 방법을 즉시 배우는 것이 좋습니다. 따라서 Maven을 사용하려면 먼저 적절한 종속성을 추가해야 합니다.
<dependency>
   <groupId>org.apache.commons</groupId>
   <artifactId>commons-lang3</artifactId>
   <version>${apache.common.version}</version>
</dependency>
${apache.common.version} 은 이 라이브러리의 버전입니다. 다음으로, 일부 클래스를 가져오려면 import를 추가하세요.
import org.apache.commons.lang3.StringUtils;
그게 전부입니다. 가방에 모두 들어 있습니다.))

1. 실제 프로젝트의 예

  • leftPad 방법

첫 번째 예는 일반적으로 너무 어리석은 것 같아서 동료들이 StringUtils.leftPad 에 대해 알고 나에게 말해 준 것이 매우 좋습니다 . 작업은 무엇이었습니까? 코드는 데이터가 올바르게 도착하지 않은 경우 데이터를 변환해야 하는 방식으로 작성되었습니다. 문자열 필드는 숫자로만 구성되어야 합니다. 즉, 길이가 3이고 값이 1이면 항목은 "001"이어야 합니다. 즉, 먼저 모든 공백을 제거한 다음 0으로 덮어야 합니다. 작업의 본질을 명확하게 하기 위한 추가 예: "12" -> "012"부터 "1" -> "001" 등. 내가 뭘 한거지? 이는 LeftPadExample 클래스에 설명되어 있습니다 . 나는 이 모든 것을 수행하는 방법을 작성했습니다.
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();
}
기본적으로 원본 값과 잘린 값의 차이를 간단히 구해서 앞에 0을 채울 수 있다는 생각을 했습니다. 이를 위해 IntStream을 사용하여 동일한 작업을 n번 수행했습니다. 그리고 이것은 반드시 테스트되어야 합니다. StringUtils.leftPad 메서드 에 대해 미리 알았더라면 수행할 수 있었던 작업은 다음과 같습니다 .
public static String apacheCommonLeftPad(String value) {
   return StringUtils.leftPad(value.trim(), value.length(), "0");
}
보시다시피 코드도 훨씬 적고, 모두가 인증한 라이브러리도 사용하고 있습니다. 이를 위해 LeftPadExampleTest 클래스에 두 개의 테스트를 만들었습니다 . (보통 클래스를 테스트할 때 동일한 패키지에 동일한 이름 + Test를 가진 클래스를 생성하고 src/test/java에서만 생성합니다.) 이러한 테스트에서는 한 가지 방법을 확인하여 값이 올바르게 변환되는지 확인한 다음 다른 방법을 확인합니다. 물론 훨씬 더 많은 테스트를 작성해야 하지만 테스트는 우리의 경우 주요 주제가 아닙니다.
package com.github.javarushcommunity.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);
   }

}
지금은 테스트에 대해 몇 가지 의견을 말씀드리겠습니다. JUnit 5를 사용하여 작성되었습니다.
  1. 테스트에 적절한 주석(@Test)이 있으면 테스트로 처리됩니다.
  2. 테스트의 동작을 이름으로 설명하기 어렵거나 설명이 길어 읽기 불편한 경우에는 @DisplayName 주석을 추가하여 테스트 실행 시 표시되는 일반적인 설명으로 만들 수 있습니다.
  3. 테스트를 작성할 때 테스트를 논리적 부분으로 나누는 BDD 접근 방식을 사용합니다.
    1. //주어진 - 테스트 전 데이터 설정 블록;
    2. //테스트 중인 코드 부분이 실행되는 블록은 언제입니까?
    3. //then은 when 블록의 결과를 확인하는 블록입니다.
실행하면 모든 것이 예상대로 작동하는지 확인할 수 있습니다.

  • 스트립스타트 메소드

여기서 나는 처음에 공백과 쉼표가 있을 수 있는 줄의 문제를 해결해야 했습니다. 변형 후에는 새로운 의미를 가지지 말았어야 했습니다. 문제 설명은 그 어느 때보다 명확합니다. 몇 가지 예를 통해 우리의 이해가 더욱 강화될 것입니다. “, , books” -> “books” “,,, books” -> “books” b , books” -> “b , books” leftPad의 경우와 마찬가지로 StrimStartExample 클래스 에는 두 가지 메서드가 있습니다. 하나 - 자체 솔루션 포함:
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);
}
여기서 아이디어는 더 이상 공백이나 쉼표가 없는 인덱스를 찾는 것이었습니다. 처음에 전혀 없었다면 인덱스는 0이 됩니다. 두 번째는 StringUtils를 통한 솔루션입니다 .
public static String apacheCommonLeftPad(String value) {
   return StringUtils.stripStart(value, StringUtils.SPACE + COMMA);
}
여기에서는 작업 중인 문자열에 대한 첫 번째 인수 정보를 전달하고, 두 번째 인수에서는 건너뛰어야 하는 문자로 구성된 문자열을 전달합니다. 같은 방식으로 StripStartExampleTest 클래스를 생성합니다 .
package com.github.javarushcommunity.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 메소드

물론 이 방법은 훨씬 간단하지만 유용성이 떨어지지는 않습니다. 이는 null 검사도 추가하는 String.isEmpty() 메서드 의 기능을 확장합니다 . 무엇을 위해? NullPointerException을 방지하려면, 즉 null 변수에 대한 메서드 호출을 방지해야 합니다 . 따라서 쓰지 않으려면 다음을 수행하십시오.
if(value != null && value.isEmpty()) {
   //doing something
}
간단히 이렇게 할 수 있습니다:
if(StringUtils.isEmpty(value)) {
   //doing something
}
이 방법의 장점은 어떤 방법이 어디에 사용되는지 즉시 알 수 있다는 것입니다.

2. StringUtils 클래스의 다른 메소드 분석

이제 제 생각에는 주목할 가치가 있는 방법에 대해 이야기해 보겠습니다. StringUtils 에 대해 일반적으로 말하면 String 클래스 에 있는 것과 유사한 null 안전 메서드를 제공한다는 점은 말할 가치가 있습니다 ( isEmpty 메서드 의 경우와 마찬가지로 ). 그것들을 살펴보겠습니다:

  • 비교 방법

이러한 메서드는 String 에 존재하며 두 문자열을 비교할 때 그 중 하나가 null인 경우 NullPointerException을 발생시킵니다. 코드에서 보기 흉한 검사를 피하기 위해 StringUtils.compare(String str1, String str2) 메서드를 사용할 수 있습니다 . 이 메서드는 비교 결과로 int를 반환합니다. 이 값은 무엇을 의미합니까? int = 0인 경우(또는 둘 다 null인 경우) int < 0, str1이 str2보다 작은 경우. int > 0(str1이 str2보다 큰 경우) 또한 해당 문서를 보면 이 메소드의 Javadoc은 다음 시나리오를 제시합니다.
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

  • 다음을 포함합니다... 메소드

여기서 유틸리티 개발자들은 폭발적인 반응을 보였습니다. 원하는 방법이 무엇이든 있습니다. 나는 그것들을 하나로 묶기로 결정했습니다.
  1. 포함 은 예상 문자열이 다른 문자열 안에 있는지 확인하는 메서드입니다. 이것이 어떻게 유용합니까? 텍스트에 특정 단어가 있는지 확인해야 하는 경우 이 방법을 사용할 수 있습니다.

    예:

    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

    이번에도 NPE(Null Pointer Exception) 보안이 존재합니다.

  2. ContainsAny 는 문자열에 있는 문자 중 하나라도 존재하는지 확인하는 메서드입니다. 또한 유용한 점은 자주 이 작업을 수행해야 한다는 것입니다.

    문서의 예:

    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. containIgnoreCase는 Contains 메소드 에 대한 유용한 확장입니다 . 실제로 이 방법 없이 이러한 사례를 확인하려면 몇 가지 옵션을 거쳐야 합니다. 따라서 단 하나의 방법만이 조화롭게 사용될 것입니다.

  4. 문서의 몇 가지 예:

    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. containNone - 이름으로 판단하면 검사 대상이 무엇인지 이미 이해할 수 있습니다. 내부에 선이 없어야합니다. 확실히 유용한 것입니다. 원하지 않는 문자를 빠르게 검색하세요. ;) 텔레그램 봇 에서는 음란물을 필터링하고 이러한 재미있는 방법을 무시하지 않을 것입니다.

    그리고 예를 들어, 그것들이 없었다면 우리는 어디에 있었을까요?

    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 메소드

문자열이 null이고 일부 기본값을 설정해야 하는 경우 추가 정보 추가를 방지하는 데 도움이 되는 일련의 방법입니다. 모든 취향에 맞는 다양한 옵션이 있습니다. 그 중 가장 중요한 것은 StringUtils.defaultString(final String str, final String defaultStr) 입니다. str이 null인 경우 단순히 defaultStr 값을 전달합니다 . 문서의 예:
StringUtils.defaultString(null, "NULL")  = "NULL"
StringUtils.defaultString("", "NULL")    = ""
StringUtils.defaultString("bat", "NULL") = "bat"
데이터를 가지고 POJO 클래스를 생성할 때 사용하면 매우 편리합니다.

  • deleteWhitespace 메소드

적용 옵션이 많지는 않지만 이것은 흥미로운 방법입니다. 동시에 그러한 경우가 발생하면 이 방법은 확실히 매우 유용할 것입니다. 문자열에서 모든 공백을 제거합니다. 이 간격이 있는 곳에는 흔적이 없습니다.))) 문서의 예:
StringUtils.deleteWhitespace(null)         = null
StringUtils.deleteWhitespace("")           = ""
StringUtils.deleteWhitespace("abc")        = "abc"
StringUtils.deleteWhitespace("   ab  c  ") = "abc"

  • endWith 메소드

그 자체로 말합니다. 이는 매우 유용한 방법입니다. 문자열이 제안된 문자열로 끝나는지 여부를 확인합니다. 이것은 종종 필요합니다. 물론 수표를 직접 작성할 수도 있지만 이미 만들어진 방법을 사용하는 것이 확실히 더 편리하고 좋습니다. 예:
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
보시다시피 모든 것이 빈 줄로 끝납니다.))) 이 예제 (StringUtils.endsWith("ABCDEF", "") = true)는 터무니없기 때문에 단지 보너스일 뿐이라고 생각합니다.) 대소문자를 무시합니다.

  • 같음 메서드

두 문자열을 비교하는 null 안전 메서드의 좋은 예입니다. 우리가 거기에 무엇을 넣으면, 답은 거기에 있을 것이고, 오류가 없을 것입니다. 예:
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
물론, equalsIgnoreCase 도 있습니다 . 모든 것이 정확히 동일한 방식으로 수행되며 대소문자만 무시합니다. 어디 보자?
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

  • 같음모든 메소드

계속해서 equals 메소드를 확장해 보겠습니다 . 여러 개의 동등성 검사 대신에 한 가지를 수행하고 싶다고 가정해 보겠습니다. 이를 위해 문자열 집합을 비교할 문자열을 전달할 수 있으며, 그 중 하나라도 제안된 문자열과 같으면 TRUE가 됩니다. 문자열과 문자열 컬렉션을 전달하여 서로 비교합니다(컬렉션의 문자열이 있는 첫 번째 문자열). 어려운? 다음은 의미를 이해하는 데 도움이 되는 문서의 예입니다.
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
equalsAnyIgnoreCase 도 있습니다 . 이에 대한 예는 다음과 같습니다.
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

결론

결과적으로 우리는 StringUtils가 무엇인지 , 그리고 어떤 유용한 방법이 있는지에 대한 지식을 남깁니다. 글쎄, 그러한 유용한 것들이 있고 기성 솔루션의 도움으로 문제를 종결할 수 있는 장소에서 매번 목발로 울타리를 칠 필요가 없다는 것을 깨닫고. 일반적으로 우리는 방법의 일부만 분석했습니다. 원한다면 계속할 수 있습니다. 더 많은 것들이 있으며 정말 주목할 가치가 있습니다. 이것이 어떻게 다른 방식으로 제시될 수 있는지에 대한 아이디어가 있으면 적어 주십시오. 저는 항상 새로운 아이디어에 열려 있습니다. 메소드에 대한 문서는 매우 잘 작성되었으며, 결과가 포함된 테스트 예제가 추가되어 메소드의 작동을 더 잘 이해하는 데 도움이 됩니다. 따라서 우리는 문서를 읽는 것을 주저하지 않습니다. 이 문서를 읽으면 유틸리티의 기능에 대한 의심이 사라질 것입니다. 새로운 코딩 경험을 얻으려면 유틸리티 클래스가 어떻게 만들어지고 작성되는지 살펴보는 것이 좋습니다. 일반적으로 각 프로젝트에는 자체 스크랩 클래스가 있고 이를 작성하는 경험이 도움이 되기 때문에 이는 향후에 유용할 것입니다. 전통적으로 Github에서 내 계정을 구독하는 것이 좋습니다. ) 텔레그램 봇을 사용한 내 프로젝트에 대해 모르시는 분들을 위해 첫 번째 기사 에 대한 링크가 있습니다 . 읽어주신 모든 분들께 감사드립니다. 아래에 몇 가지 유용한 링크를 추가했습니다.
코멘트
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION