☕️ java

🔗 Java 문자열와 구분자

beomsic 2023. 10. 30. 15:56

 

 

우아한테크코스의 프리코스 자동차 경주 게임 미션에 아래와 같은 요구사항이 있었다.

  • 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한 명 이상일 수 있다.
  • 우승자가 여러 명일 경우 쉼표(,)를 이용하여 구분한다.

 

이렇게 문자열을 연결해야하는 요구사항에 대해 해결하고자 여러 방법을 찾아보았다.

 

🧩 StringBuilder


⚠️ String + 연산의 문제

자바에서 String은 불변이라 문자열에 대해 수정을 할 수 없다.

String str1 = "abc";
String str2 = "def";
String resulta = str1 + str2; // abcdef

위와 같은 코드가 있을 때, 자바에서는 str1의 길이 + str2의 길이를 가지는 새로운 String을 만들어 낸다.

따라서, 여러 String을 연결하는데 String의 + 연산을 사용하게 되면 새로운 String을 계속해서 생성해내고 이에 걸리는 시간도 매우 오래 걸린다.

 

이런 문제를 해결하고자 StringBuilder를 사용해보았다.

 

💡 StringBuilder

자바에서는 StringBuilder 클래스를 제공해 String + 연산에 대해 빠르고 효율적으로 처리할 수 있도록 한다.

 

- StringBuilder는 불변이 아니고 미리 일정한 크기의 배열을 잡아 거기에 String 값을 붙여나가는 방식이다.

 

🆚 비교

JMH를 이용해 아래 코드에 대한 성능을 비교해보았다.

@State(Scope.Benchmark)
public class Sample {

    private static final int N = 1000;
    @Benchmark
    public void testStringBuilder(Blackhole blackhole) {
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = 0; i < N; i++) {
            stringBuilder.append(String.valueOf(i));
        }
    }

    @Benchmark
    public void testString(Blackhole blackhole) {
        String str = "";
        for (int i = 0; i < N; i++) {
            str += String.valueOf(i);
        }
    }
}

      - StringBuilder를 사용한 경우 더 좋은 성능을 보여준다.

🧑🏻‍💻 실제 코드 작성

StringBuilder를 통해 요구사항을 처리한 코드

StringBuilder stringBuilder = new StringBuilder();
for(String winner : winnerList) {
    stringBuilder.append(winner);
    stringBuilder.append(", ");
}
stringBuilder.delete(stringBuilder.length() - 2, stringBuilder.length());

 

🤩 StringJoiner


StringJoiner는 Java 8에서 java.util.package 에 추가된 새로운 기능이다.

  • 구분 기호, 접두사, 접미사를 사용해 문자열을 연결하는데 사용할 수 있다.

 

🧪 test

@Test
public void whenAddingListElements_thenJoinedListElements() {
    List<String> fruitList = new ArrayList<>();
    fruitList.add("Apple");
    fruitList.add("Banana");
    fruitList.add("Peach");

    StringJoiner fruitJoiner = new StringJoiner(",");

    for (String fruit : fruitList) {
	    fruitJoiner.add(fruit);
    }

    assertEquals(fruitJoiner.toString(), "Apple,Banana,Peach");
}

 

🧑🏻‍💻 요구사항에 대해 StringJoiner를 적용한 코드

List<String> winnerList = new ArrayList<>();

StringJoiner joiner = new StringJoiner(", ");
winnerList.forEach(joiner::add);

🔥 구분자를 사이사이에 붙여주어 코드가 훨씬 줄어들 수 있었다.

  • StringBuilder를 사용했을 때 보다 더 깔끔하게 코드를 작성할 수 있게 되었다.

 

 

📍 StringJoiner는 Stream API를 이용해서 더 간결하게 작성할 수 있다.

// Collectors.joining() 사용
List<String> winnerList = new ArrayList<>();
String winnerResult = winnerList.stream().collect(Collectors.joining(", "));
  • Collectors.joining()는 내부적으로 StringJoiner를 사용해 조인 작업을 수행한다.

 

 

📍 Collectors

// Collectors 
public final class Collectors {
    ...

    public static Collector<CharSequence, ?, String> joining(CharSequence delimiter) {
        return joining(delimiter, "", "");
    }

    ...

    public static Collector<CharSequence, ?, String> joining(CharSequence delimiter,
                                                             CharSequence prefix,
                                                             CharSequence suffix) {
        return new CollectorImpl<>(
                () -> new StringJoiner(delimiter, prefix, suffix),
                StringJoiner::add, StringJoiner::merge,
                StringJoiner::toString, CH_NOID);
    }
    ...
}
  • joining 함수를 보면 StringJoiner를 사용하고 있다.

 

📌 String.join()

String.join()StringJoiner와 비슷하게 문자열 사이에 구분자를 넣어준다.

StringJoiner와 다른점은 접두사와 접미사를 넣어주는 작업은 하지 않는다.

 

 

String.join()을 사용한 경우

String winnerResult = String.join(", ", winnerList);

이번 미션에서는 간단하게 콤마(,)를 사이에 넣어 문자열을 연결하는 간단한 요구사항이므로 String.join()을 사용했다.

 

📖 참고자료

Java 8 StringJoiner | Baeldung

Concatenating Strings in Java | Baeldung

☕ 자바 String / StringBuffer / StringBuilder 차이점 & 성능 비교

Performance Comparison Between Different Java String Concatenation Methods | Baeldung