# Table of Contents

# Java 문자열 다루기

Java에서 문자열을 다루는 방법에 대해 정리한다.

# String Constant Pool

Java에서는 보통 다음과 같이 문자열 리터럴(String Literal)로 문자열 타입의 변수를 초기화한다.

String str = "Hello";

문자열 리터럴은 힙 영역의 String Constant Pool 영역에 생성된다. 그리고 같은 문자열 리터럴은 한번만 생성된 후 같은 문자열을 공유한다.

String str1 = "Hello";
String str2 = "Hello";
String str3 = str1;

System.out.println(str1 == str2);  // true
System.out.println(str1 == str3);  // true

따라서 다음 세 변수 str1, str2, str3는 같은 문자열 리터럴을 가리키게 된다.

# new String()

문자열은 String클래스의 생성자로 초기화할 수도 있다.

String str = new String("Hello");

String클래스의 생성자로 생성한 문자열은 일반 객체처럼 힙 영역에 저장된다.

생성자로 전달한 문자열은 value이라는 char타입 배열의 변수에 문자열을 저장한다. 이는 String 클래스의 정의에서 확인할 수 있다.

public final class String implements Serializable, Comparable<String>, CharSequence {
    private final char value[];
}

이 때문에 문자열 리터럴로 생성한 문자열 변수와 String클래스의 생성자로 생성한 문자열 변수는 다른 주소값을 갖는다.

String str1 = "Hello";
String str2 = "Hello";
String str5 = new String("Hello");

System.out.println(str1 == str5);   // false
System.out.println(str2 == str5);   // false

String클래스의 생성자를 사용하면 마치 일반 객체처럼 힙 영역에 매번 객체가 새롭게 생성된다.

String str5 = new String("Hello");
String str6 = new String("Hello");

System.out.println(str5 == str6);   // false

# String.intern()

String.intern()String Constant Pool에서 리터럴 문자열이 이미 존재하는지 체크한 후 존재하면 해당 문자열을 반환하고 아니면 리터럴 문자열을 String Constant Pool에 추가한다.

String str1 = "Hello";
String str2 = new String("Hello");
String str3 = str2.intern();

System.out.println(str1 == str2);   // false
System.out.println(str1 == str3);   // true

# 불변 객체

String Constant Pool에 저장되는 문자열 리터럴은 불변객체다. 따라서 + 연산자 또는 String.concat()로 문자열을 붙이는 작업을 수행하면 기존 인스턴스가 변경되는 것이 아니라 매번 새로운 인스턴스가 생성된다.

String str1 = "Hello";
String str2 = "World";
String str3 = "HelloWorld";
String str4 = str1 + str2;
String str5 = str1.concat(str2);

System.out.println(str3 == str4);   // false
System.out.println(str3 == str5);   // false
System.out.println(str4 == str5);   // false
String str1 = new String("Hello");
String str2 = new String("World");
String str3 = new String("HelloWorld");
String str4 = str1 + str2;
String str5 = str1.concat(str2);

System.out.println(str3 == str4);   // false
System.out.println(str3 == str5);   // false
System.out.println(str4 == str5);   // false

이처럼 문자열은 불변 객체이기 때문에 문자열 추가, 수정, 삭제 등의 연산이 빈번하면 힙 영역에 객체가 매번 새롭게 생성되어 성능에 영향을 주게 된다. 이러한 문제를 해결하기 위해 Java는 가변 객체인 StringBufferStringBuilder를 도입했다.

# StringBuffer 클래스

StringBuffer가 제공하는 append(), insert(), delete(), replace() 등의 메소드를 제공하면 동일한 객체에서 문자열을 변경할 수 있다.

StringBuffer 예제를 살펴보자. StringBuffer는 다음과 같이 생성한다.

StringBuffer buffer = new StringBuffer("Hello");

이제 뒤에 문자열을 붙여보자.

buffer.append(" world!");

위 코드는 아래처럼 객체를 새롭게 생성하는게 아니라

아래처럼 기존 객체의 값을 변경한다.

StringBuffer클래스는 다양한 메소드를 지원한다.

# append()

끝에 문자열을 추가한다.

StringBuilder builder = new StringBuilder("abcd");
builder.append("efg");

System.out.println(builder);    // abcdefg

# insert()

특정 위치에 문자열을 추가한다.

StringBuilder builder = new StringBuilder("1111");
builder.insert(2, 3333);

System.out.println(builder);    // 11333311

# delete()

특정 범위의 문자열을 삭제한다.

StringBuilder builder = new StringBuilder("1122211");
builder.delete(2, 5);

System.out.println(builder);    // 1111

# replace()

특정 범위의 문자열을 다른 문자열로 대치한다.

StringBuilder builder = new StringBuilder("1122211");
builder.replace(2, 5, "aaaa");

System.out.println(builder);    // 11aaaa11

# reverse()

문자열을 뒤짚는다.

StringBuilder builder = new StringBuilder("123456");
builder.reverse();

System.out.println(builder);    // 654321

# substring()

문자열의 일부분을 추출한다.

StringBuilder builder = new StringBuilder("123456");
String substring = builder.substring(0, 3);

System.out.println(substring);    // 123

# toString()

StringBuilder객체를 String객체로 변환한다.

StringBuilder builder = new StringBuilder("123456");
String str = builder.toString();

System.out.println(str);    // 123456

# StringBuilder 클래스

StringBuilderStringBuffer와 사용법이 거의 유사하다. 다만 다음과 같은 차이점이 있다.

StringBuffer StringBuilder
동기화를 지원한다 동기화를 지원하지 않는다
멀티 스레드 환경에서 안전하다 멀티 스레드 환경에서 안전하지 않다.
동기화 때문에 성능이 상대적으로 낮다. 성능이 상대적으로 높다.

따라서 단일 스레드 환경에서는 StringBuilder를 사용하고 멀티 스레드 환경에서는 StringBuffer를 사용한다. 요약하자면 문자열의 추가, 수정, 삭제가 빈번한 경우 String보다 StringBuffer 또는 StringBuilder클래스를 사용한다.

# 이스케이프 문자

Java에서 사용하는 이스케이프 문자(Escape Sequence)는 다음과 같다.

문자 의미
\' 작은 따옴표
\" 큰 따옴표
\n New Line
\t Tab
\\ 역슬래시
\b Backspace
\f Form Feed
\r Carriage Reture

공백은 이스케이프 문자가 아니며 을 사용하면 된다.

# StringTokenizer

StringTokenizer를 사용하면 문자열을 쉽게 자를 수 있다. 기본 구분자(delimiter)\t\n\r\f이다.

String str = "I am a programmer.\nhello world!";
StringTokenizer tokenizer = new StringTokenizer(str);

System.out.println(tokenizer.countTokens());    // 6

while (tokenizer.hasMoreTokens()) {
    String token = tokenizer.nextToken();
    System.out.println(token);
}
I
am
a
programmer.
hello
world!

StringTokenizer 생성자의 두 번째 인자로 구분자를 직접 지정해줄 수 있다.

String str = "I am a programmer.hello world!";
StringTokenizer tokenizer = new StringTokenizer(str, ".");
System.out.println(tokenizer.countTokens());    // 6
while (tokenizer.hasMoreTokens()) {
    String token = tokenizer.nextToken();
    System.out.println(token);
}
I am a programmer
hello world!

# String 클래스

String클래스에는 문자열 조작을 위한 다양한 메소드가 존재한다.

# toUpperCase()

문자열을 대문자로 변환한다.

String name = "Leonel Messi";
System.out.println(name.toUpperCase());     // LEONEL MESSI

# toLowerCase()

문자열을 소문자로 변환한다.

String name = "Leonel Messi";
System.out.println(name.toLowerCase());     // leonel messi

# substring()

문자열의 일부분을 반환한다.

String name = "Leonel Messi";
System.out.println(name.substring(0, 6));     // Leonel

# trim()

문자열 앞과 뒤의 공백을 제거한다.

String name = "      Hello  World    ";
System.out.println(name.trim());     // Hello  World

# spilt()

인자로 전달받은 문자 또는 정규표현식을 기준으로 문자열을 나누어 배열에 저장하여 반환한다.

String str = "aa_bb_cc_dd_ee";
String[] spilt = str.split("_");

for (int i=0; i<spilt.length; i++) {
    System.out.println(spilt[i]);
}
aa
bb
cc
dd
ee

다음과 같이 공백(Space)을 기준으로 문자열을 자를 수도 있다.

String str = "aa bb cc dd ee";
String[] spilt = str.split(" ");

for (int i=0; i<spilt.length; i++) {
    System.out.println(spilt[i]);
}
aa
bb
cc
dd
ee

이스케이프 문자를 기준으로 문자열을 자를 수도 있다.

String str = "Hello World!\nNice to meet you"; // Hello World!	See you again

String[] spilt = str.split("\n");

for (int i=0; i<spilt.length; i++) {
    System.out.println(spilt[i]);
}
Hello World!
See you again

OR(|) 연산자로 구분자 여러 개를 지정할 수도 있다.

String str = "a_b-c_d%e@f";
System.out.println(str);

String[] spilt = str.split("_|-|%|@");

for (int i=0; i<spilt.length; i++) {
    System.out.println(spilt[i]);
}
a
b
c
d
e
f

# replace()

특정 문자를 다른 문자로 대치한다.

String str = "afafaf";
System.out.println(str.replace("a", "c"));      // cfcfcf

# replaceAll()

특정 문자열을 다른 문자열로 대치한다.

String str = "afafaf";
System.out.println(str.replaceAll("af", "19"));     // 191919

# startsWith()

문자열이 특정 문자열로 시작하는지 확인한다.

String str = "abcdef";
System.out.println(str.startsWith("ab"));  // true
System.out.println(str.startsWith("aa"));  // false

# endsWith()

문자열이 특정 문자열로 끝나는지 확인한다.

String str = "abcdef";
System.out.println(str.endsWith("def")); // true
System.out.println(str.endsWith("fff")); // false

# length()

문자열의 길이를 반환한다.

String str = "abcdef";
System.out.println(str.length());   // 6

# indexOf()

문자열에서 특정 문자의 인덱스를 반환한다. 특정 문자가 없으면 음수값을 반환한다.

String str = "abcde";
System.out.println(str.indexOf('b'));   // 1
System.out.println(str.indexOf('z'));   // -1

# charAt()

문자열에서 특정 인덱스에 위치하는 문자를 반환한다.

String str = "abcde";
System.out.println(str.charAt(0));   // a
System.out.println(str.charAt(1));   // b
System.out.println(str.charAt(2));   // c

# concat()

문자열 뒤에 추가한다. +연산자와 동일하다.

String str = "aaa";
System.out.println(str.concat("ccc"));  // aaaccc
System.out.println(str + "eee");        // aaaeee

# equals()

두 문자열이 동일한지 비교한다.

String str = "aaa";
System.out.println(str.equals("aaa"));      // true
System.out.println(str.equals("ccc"));      // false

# contains()

특정 문자나 문자열이 포함되는지 확인한다.

String str = "abcde";
System.out.println(str.contains("a"));      // true
System.out.println(str.contains("bcd"));    // true
System.out.println(str.contains("z"));      // false

# compareTo()

문자열을 비교하는데 사용한다.

System.out.println("aaaa".compareTo("bbbb"));   // -1
System.out.println("aaaa".compareTo("aaaa"));   // 0
System.out.println("bbbb".compareTo("aaaa"));   // 1

# matches()

문자열이 특정 패턴의 문자열을 포함하는지 확인한다. 특정 패턴은 정규 표현식을 의미한다.

String str = "aaacccaaa";
System.out.println(str.matches("(.*)ccc(.*)"));  // true