Notice
Recent Posts
Recent Comments
Link
«   2025/07   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

horizonyeong

String 클래스 본문

김영한님 강의 정리/실전 자바 - 중급 1편

String 클래스

horizonyeong 2025. 5. 22. 01:07

자바에서 문자를 다루는 대표적인 타입은 char, String 2가지가 있다.

	char[] charArr = new char[]{'h', 'e', 'l', 'l', 'o'};
        System.out.println(charArr);

        String str = "hello";
        System.out.println(str);

 

기본형인 char는 문자 하나를 다룰 때 사용한다. 여러문자를 나열하려면 char[ ] 을 사용해야 한다.

불편한 방법이기에 자바는 문자열을 편리하게 다루는 String 클래스를 제공한다.

 

String 클래스로 문자열을 생성하는 방법은 2가지가 있다

String str1 = "hello"
String str2 = new String("hello");

String은 클래스다.str1 변수에는 String 인스턴스의 참조값만 들어갈 수 있다. 어색한 느낌이다.

문자열은 매우 자주 사용된다. 그래서 편의상 쌍따옴표로 문자열을 감싸면 자바 언어에서 new String("hello") 로 변경해준다.

 

String 클래스 구조

public final class String {

    //문자열 보관
    private final char[] value; //자바 9 이전
    private final byte[] value; //자바 9 이후
    
    //여러 메서드
    public String concat(String str) {...}
    public int length() {...}
    ...

클래스이므로 속성과 기능을 가진다.

String의 실제 문자 데이터 자체는 char[ ] 에 보관된다.

String 클래스는 불편한 char[ ]을 내부에 감추고 문자열을 다룰 수 있도록 다양한 기능을 제공한다.

 

기능(메서드)

  • length() : 문자열의 길이 반환
  • charAt (int index) : 특정 인덱스의 문자 반환
  • substring (int beginIndex, int endIndex) : 문자열의 부분 문자열 반환
  • indexOf (String str) : 특정 문자열이 시작되는 인덱스 반환
  • toLowerCase(), toUpperCase()
  • trim(): 문자열 양 끝의 공백을 제거
  • concat (String str) : 문자열을 더한다.
  • 외 다양한 기능 제공

String 클래스와 참조형

String은 기본형이 아니라 참조형이다. 참조형 변수에는 계산할 수 없는 참조값이 들어있다

따라서 + 같은 연산자를 사용할 수 없다.

 

String a = "hello";
String b = " java";

String result1 = a.concat(b);
String result2 = a + b;
  • 문자열을 더할때는 String이 제공하는 concat( ) 과 같은 메서드를 사용해야 한다.
  • 하지만 문자열은 자주 다루어지기 때문에 + 연산을 제공한다.

String 클래스 - 비교

String 클래스를 비교할 때는 항상 == 비교가 아니라 .equals()비교를 해야한다.

  • 동일성(Identity): == 연산자 사용, 두 객체의 참조가 동일한 객체를 가르키고 있는지 확인
  • 동등성(Equality): equals( ) 메서드를 사용하여 두 객체가 논리적으로 같은지 확인
public class StringEqualsMain1 {

    public static void main(String[] args) {
        String str1 = new String("hello");
        String str2 = new String("hello");

        System.out.println("new String() == 비교: " + (str1 == str2));
        System.out.println("new String() equals 비교:" +(str1.equals(str2)));

        String str3 = "hello";
        String str4 = "hello";
        System.out.println("리터럴 == 비교: " +(str3 == str4));
        System.out.println("리터럴 equals 비교: " +(str3.equals(str4)));
    }
}
실행결과
new String() == 비교: false
new String() equals 비교:true
리터럴 == 비교: true
리터럴 equals 비교: true
  • str1 과 str2는 new String( )을 사용해서 각각 인스턴스를 생성. 서로 다른 인스턴스이므로 동일성(==)비교에 실패
  • 둘은 내부에 같은 "hello" 값을 가지고 있기 때문에 논리적으로 같다. 동등성(equals()) 비교에 성공
    • String 클래스는 내부 문자열 값을 비교하도록 equals( ) 메서드를 재정의 해두었다.

  • String str3 = "hello" 와 같이 문자열 리터럴을 사용하는 경우 자바는 메모리 효율성과 성능 최적하를 위해 문자열 풀을 사용한다.
  • 자바가 실행되는 시점에 문자열 리터럴이 있으면 문자열 풀에 String 인스턴스를 미리 만들어 둔다. 이때 같은 문자열이 있으면 다시 만들지 않는다.
  • String str3 = "hello"와 같이 문자열 리터럴을 사용하면 문자열 풀에서 "hello" 라는 문자를 가진 String 인스턴스를 찾는다. 그리고 찾은 인스턴스의 참조(x003)을 반환한다.
  • String str4 = "hello" 도 마찬가지.
  • 문자열 리터럴을 사용하는 경우 같은 참조값을 가지므로 == 비교에 성공

 

  • 풀(Pool)은 공용 자원을 모아둔 곳을 뜻한다. 문자열 풀에 필요한 String 인스턴스를 미리 만들어 두고 여러곳에서 재사용.
  • 성능과 메모리 최적화
  • 문자열 풀은 힙 영역 사용
  • 문자열 풀에서 문자를 찾을 때는 해시 알고리즘 사용

String 클래스 - 불변 객체

String은 불변 객체이다. 따라서 생성 이후에 절대로 내부의 문자열 값을 변경할 수 없다.

변경이 필요한 경우 기존 값을 변경하지 않고 새로운 결과를 만들어서 반환한다.

 

String이 불변으로 설계된 이유

문자열 풀에 있는 String 인스턴스의 값이 중간에 변경되면 같은 문자열을 참고하는 다른 변수의 값도 함께 변경된다.

str3이 참조하는 문자를 변경하면 str4의 문자도 함께 변경되는 사이드 이펙트 문제가 발생한다.

 

String 클래스 - 주요 메서드1

 

StringBuilder - 가변 String

Stirng str = "A" + "B" + "C" + "D";
Stirng str = String("A") + String("B") + String("C") + String("D");
Stirng str = new String("AB") + String("C") + String("D");
Stirng str = new String("ABC") + String("D");
Stirng str = new String("ABCD")
  • 이 경우 3개의 String 클래스가 추가로 생성된다.
  • 문제는 중간에 만들어진 new String("AB"), new String("ABC") 는 사용되지 않는다. 
  • 최종적으로 만들어진 new String("ABCD") 만 사용된다.
  • 중간에 만들어진 new String("AB"), new String("ABC") 사용되지도 않고, GC의 대상이 된다.

불변인 String의 단점

문자를 더하거나 변경할 때 마다 계속해서 새로운 객체를 생성해야 된다는 점.

문자를 자주 변경해야 하는 상황이라면 많은 String 객체를 만들고 GC 해야 한다. 결과적으로 컴퓨터의 CPU, 메모리, 자원을 더 사용하게 된다.

 

참고 : 실제로는 문자열을 다룰 때 자바가 내부에서 최적화를 적용한다.

 

StirngBuilder

String의 단점을 해결하기 위해서는 가변 String이 존재하면 된다. 가변은 내부의 값을 바로 변경하면 되기 때문에 새로운 객체 생성이 필요가 없다. 자바는 가변 String인 StringBuilder를 제공한다. 가변의 경우 사이드 이펙트에 주의해서 사용해야 한다.

 

StirngBuilder 사용 예

public static void main(String[] args) {

        StringBuilder sb = new StringBuilder();
        sb.append("A");
        sb.append("B");
        sb.append("C");
        sb.append("D");
        System.out.println("sb = " + sb);
        
        //StringBuilder -> String
        String string = sb.toString();
        System.out.println("string = " + string);
    }
  • 마지막으로 toString()메소드를 사용해 StringBuilder의 결과를 기반으로 String을 생성해서 반환한다
  • StringBuilder는 보통 문자열을 변경하는 동안만 사용하다가 문자열 변경이 끝나면 안전한 불변 String으로 변환 하는것이 좋다.

(StringBuilder 내부의 문자들을 하나의 String 객체로 바꿔서 반환 하도록 오버라이딩?)

 

가변 vs 불변

 

String 최적화

자바 컴파일러는 다음과 같이 문자열 리터럴을 더하는 부분을 자동으로 합쳐준다.

 

컴파일 전

String helloWorld = "Hello, " + "World!";

 

컴파일 후

String helloWorld = "Hello, World!";

따라서 런타임에 별도의 문자열 결합 연산을 수행하지 않기 때문에 성능이 향상 된다.

 

String 변수 최적화

문자열 변수의 경우 그 안에 어떤 값이 들어있는지 컴파일 시점에는 알 수 없기 때문에 단순하게 합칠 수 없다.

String result = str1 + str2;

 

이런 경우 예를 들면 다음과 같이 최적화를 수행한다 ( 자바 버전마다 최적화 방식이 다름 )

자바 9 부터는 StringConcatFactory를 사용하여 최적화를 수행

String result = new StringBuilder().append(str1).append(str2).toString();

 

String 최적화가 어려운 경우

다음과 같이 문자열을 루프안에서 문자열을 더하는 경우에는 최적화가 이루어지지 않는다.

String result = "";
for (int i = 0; i < 100000; i++) {
	result += "Hello Java ";
    
//다음과 같이 최적화 (버전 마다 다르다)
for (int i = 0; i < 100000; i++) {
	result = StringBuilder().append(result).append("Hello Java ").toSting();
}

반복문의 루프 내부에서는 최적화가 되는것 처럼 보이지만, 반복 횟수만큼 객체를 생성해야 한다.

(변수 result는 String 타입이므로 toString() 메서드로 String 타입으로 반환 해야 된다! 

-> 루프 반복 마다 StringBuilder는 물론이고 toString() 으로 String 객체 생성)

반복문 내에서 문자열 연결은, 런타임에 연결할 문자열의 개수와 내용이 결정된다. 이런 경우, 컴파일러는 얼마나 많은 반복이 일어날지, 각 반복에서 문자열이 어떻게 변할지 예측할 수 없다.

 

이럴 때는 직접 StringBuilder를 사용

StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100000; i++) {
	sb.append ";
}
String result = sb.toString();

 

정리

문자열을 합칠 때 대부분의 경우 최적화가 되므로 + 연산을 사용하며 된다.

 

StringBuilder를 직접 사용하는 것이 더 좋은 경우

  • 반복문에서 반복해서 문자를 연결할 때
  • 조건문을 통해 동적으로 문자열을 조합할 때
  • 복잡한 문자열의 특정 부분을 변경해야 할 때
  • 매우 긴 대용량 문자열을 다룰 때

참고: StringBuilder vs String Buffer

 

메서드 체이닝 - Method Chaining

public class ValueAdder {

    private int value;

    public ValueAdder add(int addValue) {
        value += addValue;
        return this;
    }

    public int getValue() {
        return value;
    }
}
  • 단순히 값을 누적해서 더하는 기능을 제공하는 클래스다.
  • add() 메서드를 호출할 때마다 내부의 value에 값을 누적한다.
  • add() 메서드는 자기 자신(this)의 참조값을 반환한다.
public class MethodChainingMain3 {

    public static void main(String[] args) {
        ValueAdder adder = new ValueAdder();

        int result = adder.add(1).add(2).add(3).getValue();
        System.out.println("result = " + result);
    }
}

실행순서

add() 메서드를 호출하면 ValueAdder 인스턴스 자신의 참조값이 반환된다. 이 반환된 참조값을 변수에 담지 않아도 된다.

대신에 반환된 참조값을 즉시 사용해서 바로 메서드를 호출 할 수 있다.

 

메서드 호출의 결과로 자기 자신의 참조값을 반환하면, 반환된 참조값을 사용해서 메서드 호출을 이어나갈 수 있다.

.을 찍고 메서드를 계속 연결해서 사용한다. 마치 메서드가 체인으로 연결된 것 처럼 보인다.

이러한 기법을 메서드 체이닝이라 한다.

 

메서드 체이닝 기법은 코드를 간결하고 읽기 쉽게 만들어 준다.

 

StringBuilder와 메서드 체인(Chain)

StringBuilder는 메서드 체이닝 기법을 제공한다.

StringBuilder의 append() 메서드를 보면 자기 자신의 참조값을 반환한다.

StringBuilder 에서 문자열을 변경하는 대부분의 메서드도 메서드 체이닝 기법을 제공하기 위해 자기 자신을 반환한다.

예) insert(), delete(), reverse()