본문 바로가기

Java

String Class

1. A Brief Summary of the String Class

자바의 String은 변경할 수 없는(immutable) 유니코드 문자의 시퀀스를 포함합니다. 이는 C/C++와 달리 자바의 String은 단순한 char 배열이 아닌 java.lang.String 클래스의 객체입니다.

그러나 자바의 String은 특별합니다. 일반적인 클래스와는 다른 점이 있습니다:

  • String은 "hello, world"와 같은 이중 따옴표로 둘러싸인 문자열 리터럴과 관련이 있습니다. String 변수에 문자열 리터럴을 직접 할당할 수 있으며, String 인스턴스를 생성하기 위해 생성자를 호출할 필요가 없습니다.
  • '+' 연산자는 두 개의 String 피연산자를 연결하는 데 사용됩니다. '+' 연산자는 Point나 Circle과 같은 다른 객체에는 사용할 수 없습니다.
  •  String은 변경할 수 없습니다. 즉, 한 번 생성되면 내용을 수정할 수 없습니다. 예를 들어, toUpperCase() 메서드는 기존 내용을 수정하는 대신 새로운 String을 생성하여 반환합니다.

즉, 자바의 String은 특별한 속성을 가지고 있으며, 이는 문자열을 처리하는 방식과 기능을 효율적으로 지원합니다.

1.1 Method Summary

String 클래스에서 자주 사용되는 메서드를 아래에 요약하였습니다. 자세한 목록은 java.lang.String의 JDK API를 참조하시기 바랍니다.

  • length(): 문자열의 길이를 반환합니다.
  • isEmpty(): 문자열이 비어 있는지 여부를 확인합니다. (str.length() == 0 과 동일)
  • isBlank(): 문자열이 공백 문자(Unicode 공백)로만 이루어져 있는지 확인합니다. (JDK 11)
  • equals(String another): 두 문자열을 비교합니다. '=='나 '!='를 사용할 수 없습니다.
  • equalsIgnoreCase(String another): 대소문자를 무시하고 두 문자열을 비교합니다.
  • compareTo(String another): 두 문자열을 비교하여 동일하면 0을 반환하고, 사전적 순서에 따라 -1 또는 1을 반환합니다.
  • startsWith(String another): 문자열이 주어진 문자열로 시작하는지 여부를 확인합니다.
  • endsWith(String another): 문자열이 주어진 문자열로 끝나는지 여부를 확인합니다.
  • indexOf(String key): 주어진 문자열이 처음으로 나타나는 인덱스를 반환합니다.
  • charAt(int idx): 주어진 인덱스에 해당하는 문자를 반환합니다.
  • substring(int fromIdx): 주어진 인덱스부터 문자열의 끝까지의 부분 문자열을 반환합니다.
  • toLowerCase(): 문자열을 소문자로 변환하여 새로운 문자열을 반환합니다.
  • toUpperCase(): 문자열을 대문자로 변환하여 새로운 문자열을 반환합니다.
  • concat(String another): 두 문자열을 연결하여 새로운 문자열을 반환합니다.
  • trim(): 문자열의 앞뒤 공백을 제거한 새로운 문자열을 반환합니다.
// Length
int length()       // returns the length of the String
boolean isEmpty()  // same as str.length() == 0
boolean isBlank()  // contains only white spaces (Unicode aware) (JDK 11)

// Comparison
boolean equals(String another) // CANNOT use '==' or '!=' to compare two Strings in Java
boolean equalsIgnoreCase(String another)
int compareTo(String another)  // return 0 if this string is the same as another;
                               // <0 if lexicographically less than another; or >0
int compareToIgnoreCase(String another)
boolean startsWith(String another)
boolean startsWith(String another, int fromIdx)  // search begins at fromIdx
boolean endsWith(String another)

// Searching: index from 0 to str.length()-1
int indexOf(String key)
int indexOf(String key, int fromIdx)
int indexOf(int char)
int indexOf(int char, int fromIdx)      // search forward starting at fromIdx
int lastIndexOf(String key)
int lastIndexOf(String key, int fromIdx)  // search backward starting at fromIdx
int lastIndexOf(int char)
int lastIndexOf(int char, int fromIdx)

// Extracting a char or substring, include fromIdx but exclude toIdx
char charAt(int idx)
String substring(int fromIdx)
String substring(int fromIdx, int toIdx)

// Creating a new String or char[] from the original - Strings are immutable
String toLowerCase()
String toUpperCase()
String concat(String another)  // same as str+another
String trim()          // creates a new String removing white spaces from front and back
String strip()         // strips the leading and trailing white spaces (Unicode aware) (JDK 11)
String stripLeading()     // (JDK 11)
String stripTrailing()    // (JDK 11)
String repeat(int count)  // (JDK 11)
String indent(int n)  // adjusts the indentation by n (JDK 12)
char[] toCharArray()                        // create a char[] from this string
void getChars(int srcBegin, int srcEnd, char[] dst, int dstBegin)  // copy into dst char[]

// Working with CharSequence (super-interface of String, StringBuffer, StringBuilder)
boolean contains(CharSequence cs)       // (JDK 5)
boolean contentEquals(CharSequence cs)  // (JDK 5)
boolean contentEquals(StringBuffer sb)  // (JDK 4)
static String join(CharSequence delimiter, CharSequence... elements)  // (JDK 8)
static String join(CharSequence delimiter, Iterable<CharSequence> elements)  // (JDK 8)

// Text Processing and Regular Expression (JDK 4)
boolean matches(String regex)
String replace(char old, char new)
String replace(CharSequence target, CharSequence replacement)  // (JDK 4)
String replaceAll(String regex, String replacement)
String replaceFirst(String regex, String replacement)
String[] split(String regex)  // Split the String using regex as delimiter, return a String array
String[] split(String regex, int count)  // for count times only

/*** static methods ***/
// Converting primitives to String
static String valueOf(type arg)  // type can be primitives or char[]
// Formatting using format specifiers
static String format(String formattingString, Object... args)  // same as printf()

/*** Stream and Functional Programming ***/
Stream<String> lines()  // returns a stream of lines (JDK 11)
IntStream chars()       // returns a IntStream of characters (JDK 9)
IntStream codePoints()
R transform(Function<String, R> f)  // transforms from String to type R (JDK 12)

그 외에도 문자열을 다루는 다양한 메서드가 있으며, 자세한 내용은 JDK API 문서를 참조하시기 바랍니다. 이러한 String 클래스의 메서드들은 문자열을 다루고 조작하는 데 유용하게 사용될 수 있습니다.

2. String is Really Special!

문자열은 프로그램에서 빈번하게 사용되기 때문에 자바에서는 특별한 대우를 받습니다. 따라서 효율성(연산 및 저장 측면에서)이 중요합니다.

자바의 설계자들은 객체 지향 언어에서 모든 것을 객체로 만드는 대신 primitive data 타입을 유지하여 언어의 성능을 개선하기로 결정했습니다. primitive data 타입은 메서드 스택에 저장되며, 더 적은 저장 공간이 필요하고 조작이 더 저렴합니다. 반면 객체는 프로세스 힙에 저장되며 복잡한 메모리 관리와 더 많은 저장 공간을 필요로 합니다.

성능상의 이유로 자바의 String은 primitive data 타입과 객체 사이에 위치하도록 설계되었습니다. String의 특별한 기능에는 다음이 포함됩니다:

  • '+' 연산자는 primitive data 타입(예: int와 double)에 대해 덧셈을 수행하는 데 사용되지만, String 객체에 대해서는 연결(concatenation)을 수행합니다. '+' 연산자는 두 개의 String 피연산자에 대해 연결(concatenation)을 수행합니다. 자바는 소프트웨어 공학적인 이유로 연산자 오버로딩을 지원하지 않습니다. C++과 같이 연산자 오버로딩을 지원하는 언어에서는 '+' 연산자를 뺄셈에 사용할 수 있지만, 이는 코드의 가독성을 떨어뜨리는 결과를 가져옵니다. 자바에서는 문자열 연결을 지원하기 위해 내부적으로 '+' 연산자를 오버로딩한 유일한 연산자입니다. '+' 연산자는 Point나 Circle과 같은 임의의 두 클래스 객체에 대해서는 작동하지 않음에 유의해야 합니다.
  • String은 다음 두 가지 방법으로 생성할 수 있습니다:
    1) 문자열 리터럴을 String 참조에 직접 할당하는 방법 - 원시 타입과 마찬가지로 가능합니다.
    2) "new" 연산자와 생성자를 사용하여 생성하는 방법 - 다른 클래스와 유사합니다. 그러나 이 방법은 흔히 사용되지 않으며 권장되지 않습니다. 예를 들어 첫 번째 문장에서, str1은 String 참조로 선언되고 문자열 리터럴 "Java is Hot"으로 초기화됩니다. 두 번째 문장에서는 str2가 String 참조로 선언되고 "I'm cool"을 포함하도록 new 연산자와 생성자를 통해 초기화됩니다.
String str1 = "Java is Hot"; // 문자열 리터럴을 통한 암시적 생성 
String str2 = new String("I'm cool"); // new 연산자와 생성자를 통한 명시적 생성 문자열 리터럴은 공통 풀(common pool)에 저장됩니다.
  •  String literal은 common pool에 저장됩니다. 이를 통해 동일한 내용을 가진 문자열의 저장 공간을 공유하여 저장 공간을 절약할 수 있습니다. new 연산자를 통해 할당된 String 객체는 힙에 저장되며, 동일한 내용에 대해 저장 공간을 공유하지 않습니다.

2.1  String Literal vs. String Object

언급한대로, 문자열을 구성하는 두 가지 방법이 있습니다: 문자열 리터럴을 할당함으로써 암시적으로 생성하거나 new 연산자와 생성자를 통해 명시적으로 String 객체를 생성하는 것입니다. 예를 들어,

String s1 = "Hello";              // String literal
String s2 = "Hello";              // String literal
String s3 = s1;                   // same reference
String s4 = new String("Hello");  // String object
String s5 = new String("Hello");  // String object

 

Java는 문자열 리터럴을 "string common pool"이라고 불리는 특별한 메커니즘을 통해 유지합니다. 만약 두 개의 문자열 리터럴이 동일한 내용을 가지고 있다면, 그들은 common pool의 동일한 string literal을 공유합니다. 이 방식은 자주 사용되는 문자열의 저장 공간을 절약하기 위해 채택되었습니다. 반면에, new 연산자와 생성자를 통해 생성된 String 객체들은 힙(heap)에 유지됩니다. 힙 내의 각 String 객체는 다른 객체와 마찬가지로 자체 저장 공간을 갖습니다. 힙에서는 두 개의 String 객체가 동일한 내용을 가지더라도 저장 공간을 공유하지 않습니다.

두 개의 String을 내용을 비교하기 위해 String 클래스의 equals() 메서드를 사용할 수 있습니다. 객체의 참조(또는 포인터)를 비교하기 위해 관계 연산자 '=='를 사용할 수도 있습니다. 다음 코드를 살펴보세요.

s1 == s1;        // true, same pointer
s1 == s2;        // true, s1 and s1 share storage in common pool
s1 == s3;        // true, s3 is assigned same pointer as s1
s1.equals(s3);   // true, same contents
s1 == s4;        // false, different pointers
s1.equals(s4);   // true, same contents
s4 == s5;        // false, different pointers in heap
s4.equals(s5);   // true, same contents

 

중요 사항:

  • 위의 예제에서는 관계 연산자 '=='를 사용하여 두 개의 String 객체의 참조를 비교했습니다. 이는 문자열 리터럴이 공통 풀에서 저장 공간을 공유하는 것과 힙에 생성된 String 객체와의 차이를 보여주기 위해 수행되었습니다. 하지만 (s1 == s2)와 같은 방식으로 두 개의 String의 내용을 비교하는 것은 논리적인 오류입니다.
  • String은 공통 풀에서 저장 공간을 공유하는 문자열 리터럴을 직접 할당하여 생성할 수 있습니다. 힙에 String 객체를 생성하는 데 new 연산자를 사용하는 것은 일반적이지 않으며 권장되지 않습니다.

2.2  String is Immutable

동일한 내용을 가진 문자열 리터럴은 공통 풀에서 저장 공간을 공유하기 때문에, Java의 String은 변경할 수 없도록 설계되었습니다. 즉, 한 번 String이 생성되면 그 내용을 수정할 수 없습니다. 그렇지 않으면 동일한 저장 위치를 공유하는 다른 String 참조도 변경의 영향을 받게 되는데, 이는 예측할 수 없고 원하지 않는 결과를 초래할 수 있습니다. toUpperCase()와 같은 메서드는 문자열 객체의 내용을 수정하는 것처럼 보일 수 있습니다. 하지만 실제로는 새로운 완전히 다른 String 객체가 생성되고 호출자에게 반환됩니다. 원래의 String 객체는 더 이상 참조가 없을 때 해제되고, 이후 가비지 컬렉션에 의해 정리됩니다.

String이 변경할 필요가 있는 경우 (새로운 저장 공간을 차지하는 많은 새로운 String이 생성됨), String을 사용하는 것은 효율적이지 않습니다. 예를 들어,

// inefficient codes
String str = "Hello";
for (int i = 1; i < 1000; ++i) {
   str = str + i;
}

만약 String의 내용을 자주 수정해야 한다면, String 대신 StringBuffer 또는 StringBuilder 클래스를 사용하세요.

 

3.  StringBuffer & StringBuilder

이전에 설명한대로, 동일한 내용을 가진 문자열 리터럴은 문자열 공통 풀에서 동일한 저장 공간을 공유하기 때문에 String은 변경할 수 없습니다. 한 String의 내용을 직접 수정하는 것은 같은 저장 공간을 공유하는 다른 String에 부작용을 일으킬 수 있습니다.

JDK는 가변적인(mutable) 문자열을 지원하기 위해 두 개의 클래스를 제공합니다: StringBuffer와 StringBuilder(core 패키지인 java.lang에 속함). StringBuffer 또는 StringBuilder 객체는 일반적인 객체와 마찬가지로 힙(heap)에 저장되며 공유되지 않으므로 다른 객체에 부작용을 일으키지 않고 수정할 수 있습니다.

StringBuilder 클래스는 JDK 5에서 소개되었습니다. StringBuffer 클래스와 동일하지만, StringBuilder는 다중 스레드 작업에 대해 동기화되지 않습니다. 그러나 단일 스레드 프로그램에서는 동기화 오버헤드가 없는 StringBuilder가 더 효율적입니다.

 

3.1  java.lang.StringBuffer

Read the JDK API specification for java.lang.StringBuffer.

// Constructors
StringBuffer()             // an initially-empty StringBuffer
StringBuffer(int size)     // with the specified initial size
StringBuffer(String s)     // with the specified initial content
 
// Length
int length()
 
// Methods for building up the content
StringBuffer append(type arg)  // type could be primitives, char[], String, StringBuffer, etc
StringBuffer insert(int offset, arg)
 
// Methods for manipulating the content
StringBuffer delete(int fromIdx, int toIdx)
StringBuffer deleteCharAt(int idx)
void setLength(int newSize)
void setCharAt(int idx, char newChar)
StringBuffer replace(int fromIdx, int toIdx, String s)
StringBuffer reverse()
 
// Methods for extracting whole/part of the content
char charAt(int idx)
String substring(int fromIdx)
String substring(int fromIdx, int toIdx)
String toString()
 
// Indexing
int indexOf(String key)
int indexOf(String key, int fromIdx)
int lastIndexOf(String key)
int lastIndexOf(String key, int fromIdx)

 

기억해야 할 점은 StringBuffer가 일반적인 객체라는 것입니다. StringBuffer를 생성하기 위해서는 생성자를 사용해야 합니다(문자열 리터럴에 할당하는 대신). 또한, '+' 연산자는 StringBuffer를 포함한 객체에 적용되지 않습니다. StringBuffer를 조작하기 위해서는 append()나 insert()와 같은 적절한 메서드를 사용해야 합니다.

문자열을 붙이는 방법(String의 '+' 연산자와 같은 방법)으로 문자열을 생성하기 위해서는 String 연결(concatenation) 대신 StringBuffer(다중 스레드) 또는 StringBuilder(단일 스레드)를 사용하는 것이 더 효율적입니다. 예를 들어,

// Create a string of YYYY-MM-DD HH:MM:SS
int year = 2010, month = 10, day = 10;
int hour = 10, minute = 10, second = 10;
String dateStr = new StringBuilder()
      .append(year).append("-").append(month).append("-").append(day).append(" ")
      .append(hour).append(":").append(minute).append(":").append(second).toString();
System.out.println(dateStr);
   
// StringBuilder is more efficient than String concatenation
String anotherDataStr = year + "-" + month + "-" + day + " " + hour + ":" + minute + ":" + second;
System.out.println(anotherDataStr);

사실, JDK 컴파일러는 '+' 연산자를 통한 문자열 연결을 처리하기 위해 String과 StringBuffer를 모두 사용합니다. 예를 들어,

String msg = "a" + "b" + "c";

는 더 효율적인 실행을 위해 다음과 같은 코드로 컴파일됩니다:

String msg = new StringBuffer().append("a").append("b").append("c").toString();

이 과정에서 두 개의 객체가 생성됩니다. 중간의 StringBuffer 객체와 반환된 String 객체입니다.

 

3.2  java.lang.StringBuilder (JDK 5)

JDK 5에서는 StringBuffer 클래스와 거의 동일한 StringBuilder 클래스를 소개했습니다(패키지는 java.lang입니다). 다만, StringBuilder는 동기화(스레드)되지 않습니다. 즉, 여러 개의 스레드가 동시에 StringBuilder 인스턴스에 접근하는 경우, 일관성을 보장할 수 없습니다. 그러나 대부분의 경우 단일 스레드 프로그램에서는 동기화 오버헤드를 제거하여 StringBuilder를 더 빠르게 만들 수 있습니다.

StringBuilder는 StringBuffer 클래스와 API 호환성을 가지며, 동일한 생성자와 메서드 세트를 갖추고 있지만 동기화 보장이 없습니다. 따라서 단일 스레드 환경에서는 StringBuffer에 대한 대체 수단으로 사용할 수 있습니다.

 

3.3  Benchmarking String/StringBuffer/StringBuilder

다음 프로그램은 String 객체와 StringBuffer를 사용하여 긴 문자열을 뒤집는 데 걸리는 시간을 비교합니다.

// Reversing a long String via a String vs. a StringBuffer
public class StringsBenchMark {
   public static void main(String[] args) {
      long beginTime, elapsedTime;
 
      // Build a long string
      String str = "";
      int size = 16536;
      char ch = 'a';
      beginTime = System.nanoTime();   // Reference time in nanoseconds
      for (int count = 0; count < size; ++count) {
         str += ch;
         ++ch;
         if (ch > 'z') {
            ch = 'a';
         }
      }
      elapsedTime = System.nanoTime() - beginTime;
      System.out.println("Elapsed Time is " + elapsedTime/1000 + " usec (Build String)");
 
      // Reverse a String by building another String character-by-character in the reverse order
      String strReverse = "";
      beginTime = System.nanoTime();
      for (int pos = str.length() - 1; pos >= 0 ; pos--) {
         strReverse += str.charAt(pos);   // Concatenate
      }
      elapsedTime = System.nanoTime() - beginTime;
      System.out.println("Elapsed Time is " + elapsedTime/1000 + " usec (Using String to reverse)");
 
      // Reverse a String via an empty StringBuffer by appending characters in the reverse order
      beginTime = System.nanoTime();
      StringBuffer sBufferReverse = new StringBuffer(size);
      for (int pos = str.length() - 1; pos >= 0 ; pos--) {
         sBufferReverse.append(str.charAt(pos));      // append
      }
      elapsedTime = System.nanoTime() - beginTime;
      System.out.println("Elapsed Time is " + elapsedTime/1000 + " usec (Using StringBuffer to reverse)");
 
      // Reverse a String by creating a StringBuffer with the given String and invoke its reverse()
      beginTime = System.nanoTime();
      StringBuffer sBufferReverseMethod = new StringBuffer(str);
      sBufferReverseMethod.reverse();     // use reverse() method
      elapsedTime = System.nanoTime() - beginTime;
      System.out.println("Elapsed Time is " + elapsedTime/1000 + " usec (Using StringBuffer's reverse() method)");
 
      // Reverse a String via an empty StringBuilder by appending characters in the reverse order
      beginTime = System.nanoTime();
      StringBuilder sBuilderReverse = new StringBuilder(size);
      for (int pos = str.length() - 1; pos >= 0 ; pos--) {
         sBuilderReverse.append(str.charAt(pos));
      }
      elapsedTime = System.nanoTime() - beginTime;
      System.out.println("Elapsed Time is " + elapsedTime/1000 + " usec (Using StringBuilder to reverse)");
 
      // Reverse a String by creating a StringBuilder with the given String and invoke its reverse()
      beginTime = System.nanoTime();
      StringBuffer sBuilderReverseMethod = new StringBuffer(str);
      sBuilderReverseMethod.reverse();
      elapsedTime = System.nanoTime() - beginTime;
      System.out.println("Elapsed Time is " + elapsedTime/1000 + " usec (Using StringBuidler's reverse() method)");
   }
}

이 코드는 주어진 긴 문자열을 String 객체와 StringBuffer를 사용하여 리버스하는 시간을 측정합니다. String을 사용한 경우와 StringBuffer를 사용한 경우의 실행 시간을 비교하여 출력합니다.

Elapsed Time is 332100 usec (Build String)
Elapsed Time is 346639 usec (Using String to reverse)
Elapsed Time is 2028 usec   (Using StringBuffer to reverse)
Elapsed Time is 847 usec    (Using StringBuffer's reverse() method)
Elapsed Time is 1092 usec   (Using StringBuilder to reverse)
Elapsed Time is 836 usec    (Using StringBuidler's reverse() method)

StringBuilder가 StringBuffer보다 2배 빠르며, String보다 300배 빠름을 알 수 있습니다. reverse() 메서드는 가장 빠른데, StringBuilder와 StringBuffer에서 거의 동일한 시간이 소요됩니다.

 

4. java.util.regex

정규식을 활용하여 문자열 처리를 위해 java.util.regex 패키지에는 Pattern과 Matcher 클래스가 제공됩니다. 아래는 간단한 예제 코드와 함께 이들 클래스에 대한 자세한 설명입니다.

import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class RegexExample {
    public static void main(String[] args) {
        String input = "Hello, Java! This is a sample string.";
        String regex = "\\b\\w+\\b"; // 정규식: 단어 경계로 구분된 단어
        
        // Pattern 객체 생성
        Pattern pattern = Pattern.compile(regex);
        
        // Matcher 객체 생성
        Matcher matcher = pattern.matcher(input);
        
        // 매칭된 결과 반복 처리
        while (matcher.find()) {
            String matched = matcher.group();
            System.out.println("Matched: " + matched);
        }
    }
}

위의 예제는 주어진 문자열에서 정규식 패턴에 맞는 단어를 찾아 출력하는 간단한 프로그램입니다. 코드를 살펴보면 다음과 같은 과정을 거칩니다:

1. 문자열과 정규식 패턴을 정의합니다.
2. Pattern 클래스의 `compile()` 메서드를 사용하여 정규식 패턴을 컴파일하여 Pattern 객체를 생성합니다.
3. Matcher 클래스의 `matcher()` 메서드를 사용하여 입력 문자열과 Pattern 객체를 연결하여 Matcher 객체를 생성합니다.
4. Matcher 객체의 `find()` 메서드를 사용하여 입력 문자열에서 패턴에 맞는 부분을 찾습니다. 반복적으로 호출하면 다음 매칭 결과를 찾을 수 있습니다.
5. `group()` 메서드를 사용하여 매칭된 부분을 추출합니다.
6. 매칭된 결과를 원하는 방식으로 처리합니다. 위의 예제에서는 단순히 매칭된 단어를 출력하고 있습니다.

 

※정규식 "\\b\\w+\\b"는 다음과 같은 의미를 가지고 있습니다:
- "\\b": 단어 경계를 나타내는 메타문자입니다. 이것은 단어의 시작 또는 끝을 나타냅니다. 예를 들어, "Hello"에서 "H"와 "o" 사이에는 단어 경계가 있습니다.
- "\\w+": 하나 이상의 단어 문자를 나타내는 패턴입니다. "\w"는 알파벳 대소문자, 숫자, 밑줄(_)을 포함하는 모든 단어 문자를 의미합니다. "+"는 이전 패턴이 하나 이상의 문자와 일치해야 함을 나타냅니다. 따라서 "\\w+"는 하나 이상의 단어 문자를 나타냅니다.
- "\\b": 다시 단어 경계를 나타내는 메타문자입니다.
따라서 "\\b\\w+\\b" 정규식은 단어 경계로 둘러싸인 하나 이상의 단어 문자를 찾습니다. 예를 들어, "Hello, Java!"에서 "Hello"와 "Java"는 "\\b\\w+\\b" 패턴에 매칭됩니다.


이와 같은 방식으로 Pattern과 Matcher 클래스를 활용하여 다양한 정규식 기반의 문자열 처리를 수행할 수 있습니다. 정규식은 문자열 검색, 분리, 대체, 패턴 매칭 등 다양한 작업에 유용하게 사용됩니다.

 

5. Super-Interface CharSequence for String, StringBuffer and StringBuilder (since JDK 4)

java.lang.CharSequence는 문자열의 읽기 전용 시퀀스를 나타내는 인터페이스입니다. CharSequence는 문자열에 대한 일반화된 접근 방법을 제공하여 문자열을 다루는 다양한 클래스들이 공통적으로 사용할 수 있도록 합니다. 

CharSequence를 사용하는 이유는 다음과 같습니다:

  1. 일반화된 문자열 접근: CharSequence는 문자열을 다루는 다양한 클래스들이 공통적으로 사용할 수 있는 일반화된 접근 방법을 제공합니다. 이는 코드의 유연성과 재사용성을 높일 수 있습니다.
  2. 읽기 전용 문자열: CharSequence는 읽기 전용 시퀀스를 나타내는 인터페이스입니다. 따라서 CharSequence를 구현한 클래스들은 문자열의 내용을 수정할 수 없습니다. 이는 문자열의 안정성과 불변성을 보장하고, 멀티스레드 환경에서 안전하게 사용할 수 있습니다.
  3. 메모리 효율성: CharSequence는 문자열에 대한 일반화된 접근 방법을 제공하면서도 추가적인 메모리를 사용하지 않습니다. 따라서 CharSequence를 사용하는 것은 메모리 효율적인 방법입니다.
  4. API 호환성: CharSequence는 많은 Java API에서 사용되고 있습니다. 예를 들어, StringBuilder, StringBuffer, String 등이 CharSequence를 구현하고 있습니다. 따라서 CharSequence를 활용하면 다른 API와의 상호 운용성을 갖출 수 있습니다.

또한 CharSequence는 특정 인터페이스나 클래스에서 기대하는 문자열의 읽기 전용 시퀀스를 전달하는 데 사용될 수 있습니다. 예를 들어, 메서드 매개변수로 CharSequence를 사용하면 해당 메서드 내에서 문자열을 읽기만 할 수 있도록 제한할 수 있습니다.

종합적으로, CharSequence는 문자열을 다루는 다양한 클래스들 간의 일반화된 접근 방법을 제공하고, 안전성과 효율성을 갖춘 문자열 처리를 위한 인터페이스입니다.

CharSequence 인터페이스는 다음과 같은 메서드들을 정의합니다:

  • char charAt(int index): 주어진 인덱스에 해당하는 문자를 반환합니다.
  •  int length(): 문자열의 길이를 반환합니다.
  • CharSequence subSequence(int start, int end): 주어진 시작 인덱스와 끝 인덱스 범위의 부분 문자열을 반환합니다.
  • String toString(): CharSequence 객체를 문자열로 변환하여 반환합니다.
// java.lang.CharSequence
abstract charAt(int index) -> char
abstract length() -> int
abstract subSequence(int fromIdx, int toIdx) -> CharSequence
abstract toString() -> String

JDK 8 added two default methods into the interface:

// java.lang.CharSequence (JDK 8)
default chars() -> IntStream
default codePoints() -> IntStream

JDK 11 added one static methods into the interface:

// java.lang.CharSequence (JDK 11)
static compare(CharSequence cs1, CharSequence cs2) -> int

 

아래는 CharSequence를 구현하는 간단한 예제 코드입니다:

public class MyString implements CharSequence {
    private String str;

    public MyString(String str) {
        this.str = str;
    }

    @Override
    public int length() {
        return str.length();
    }

    @Override
    public char charAt(int index) {
        return str.charAt(index);
    }

    @Override
    public CharSequence subSequence(int start, int end) {
        return str.subSequence(start, end);
    }

    @Override
    public String toString() {
        return str;
    }

    public static void main(String[] args) {
        MyString myString = new MyString("Hello, World!");

        // 사용 예시
        System.out.println("Length: " + myString.length());
        System.out.println("Character at index 4: " + myString.charAt(4));
        System.out.println("Substring from index 7 to 12: " + myString.subSequence(7, 12));
        System.out.println("Original string: " + myString);
    }
}


위의 예제에서 MyString 클래스는 CharSequence를 구현하여 문자열을 나타냅니다. MyString 클래스의 객체를 생성하고 문자열의 길이, 특정 인덱스의 문자, 부분 문자열, 원래의 문자열 등을 출력하는 예시를 보여줍니다. 이를 통 CharSequence 인터페이스의 메서드들을 사용하여 문자열을 다룰 수 있음을 알 수 있습니다.

 

메서드에서 CharSequence 매개변수 사용하기:

public void processString(CharSequence sequence) {
    int length = sequence.length();
    for (int i = 0; i < length; i++) {
        char ch = sequence.charAt(i);
        // 문자 처리 로직 추가
    }
}

위의 예제에서는 `processString` 메서드가 CharSequence를 매개변수로 받아들이고, 문자열을 처리하는 로직을 수행합니다. CharSequence 인터페이스를 사용하면 해당 메서드에서는 문자열을 읽기만 할 수 있으며, 문자열에 대한 일반화된 접근 방법을 제공합니다.

다른 클래스에서 CharSequence 반환하기:

public class MyStringList {
    private List<String> stringList;

    public MyStringList(List<String> stringList) {
        this.stringList = stringList;
    }

    public CharSequence getCombinedString() {
        StringBuilder builder = new StringBuilder();
        for (String str : stringList) {
            builder.append(str);
        }
        return builder.toString();
    }
}

위의 예제에서는 MyStringList 클래스가 List<String>을 포함하는 클래스입니다. `getCombinedString` 메서드는 stringList의 문자열들을 결합한 결과를 CharSequence로 반환합니다. 이렇게 함으로써 외부에서는 해당 문자열을 읽기만 할 수 있으며, 필요한 경우에만 문자열로 변환할 수 있습니다.

CharSequence를 활용한 문자열 비교:

public class StringComparator {
    public static boolean compareStrings(CharSequence str1, CharSequence str2) {
        return str1.toString().equals(str2.toString());
    }
}

위의 예제에서는 StringComparator 클래스가 두 개의 CharSequence를 비교하는 정적 메서드를 제공합니다. CharSequence 인터페이스를 사용하여 문자열을 비교할 수 있으며, toString() 메서드를 호출하여 비교를 수행합니다.

이러한 예제들은 CharSequence를 사용하여 문자열을 처리하는 방법을 보여주는 일부 일반적인 상황을 보여줍니다. CharSequence를 구현하고 활용함으로써 코드의 유연성과 재사용성을 높일 수 있습니다.

'Java' 카테고리의 다른 글

Class 클래스와 Constructor 클래스  (0) 2024.04.08
Abstract  (0) 2024.04.08
Call by Value / Call by Reference  (0) 2024.04.08
try-catch-finally  (0) 2024.04.08
Anonymous Class  (0) 2024.04.08