본문 바로가기

Java/Chapter 11. 메소드 오버로딩과 String 클래스

[Java] 11.03 - String 클래스의 메소드(2)

concat 메소드는 이어서 호출이 가능하다.

 concat 메소드는 다음의
형태로 호출이 가능하다.

 그리고 이 문장을 통해서
참조변수 str은

 3개의 문자열을 연결해서
만든 "ABCDEFG"를 참조하게
된다.

String str = "AB".concat("CD").concat("EF");
String str = "ABCDEF";
// 두 문장은 동일한 결과를 출력한다.

 위의 문장에서 concat 메소드의
호출이 이어져 있다는 점이
특이하다.

 이러한 형태로 concat 메소드의
호출이 가능한 이유는 무엇인가?

 이에 대한 이해를 돕기 위해
위 문장에 소괄호를 추가하면
다음과 같다.

String str = ("AB".concat("CD")).concat("EF");

 즉 위의 문장에서 왼편에 위치한
concat 메소드가 먼저 호출되고,

 그 결과로 문자열 "ABCD"가
만들어지면서 다음의 상태가
된다.

String str = "ABCD".concat("EF");

 그리고 한번 더 concat 메소드가
호출이 되어 다음의 상태가 된다.

String str = "ABCDEF";

 그런데 세 개의 문자열을
연결하는 과정에서,

 중간에 문자열 "ABCD"를 담은
String 인스턴스가 만들어진 점에
대해서는 생각해 볼 필요가 있다.

 인스턴스의 빈번한 생성은
자바의 성능에 좋지 않은
영향을 주기 때문이다.

 

문자열 결합의 최적화 : Optimization of String Concatenation

 다음 문장이 자바 컴파일러에
의해서 어떻게 처리될지
생각해보자.

 어떠한 과정을 거쳐서 하나의
문자 열로 구성이 될지 생각해보자.

String sword = "공격력: " + 14 + '.' + 42;
String sword = "공격력: 14.42";
// 두 문장은 동일한 결과를 출력한다.

 먼저 다음과 같은 형태의
변환을 예상해 볼 수 있다.

 이 문장은 복잡해 보이지만
앞서 설명한 내용을 바탕으로
충분히 이해할 수 있는 문장이다.

String sword = "공격력: ".concat(valueOf(14)).concat(valueOf('.')).concat(valueOf(42));

 위의 문장을 실행해도,

 연결된 하나의 문자열을
얻을 수 있다.

 그러나 이 문장에는 다음과
같은 문제점이 존재한다.

기본 자료형의 값을 문자열로 변환하는 과정을 여러 번 거쳐야 한다.

 valueOf 메소드의 호출을 통해
기본 자료형의 값을

 문자열로 변환하는 일은
'String 인스턴스의 생성'을
의미하고,

이는 곧 성능에 영향을 미친다.

 따라서 이러한 문제점의
해결을 위해

 StringBuilder라는 클래스가
제공된다.

 그리고 앞서 보인 문장도
컴파일러에 의해 다음과
같이 처리가 된다.

이 문장을 이해하기 위해서는 이어서 설명하는 내용을 알아야 한다.
String sword = "공격력: " + 14 + '.' + 42;
String sword = "공격력: ".concat(valueOf(14)).concat(valueOf('.')).concat(valueOf(42));
// 두 문장은 동일한 결과를 출력한다.

 위의 방법으로 문자열을
구성하면

 기본 자료형의 값을 문자
열로 변환할 팔요가 없다.

 그리고 concat 메소드는
호출될 때마다 새로운 String
인스턴스를 생성하는데,

 위의 경우에는 그러한
인스턴스의 생성도
일어나지 않는다.

 그럼 위 문장의 이해를 위해
StringBuilder 클래스를 살펴보자.

 

StringBuilder 클래스

 StringBuilder 클래스는 내부적으로
문자열을 저장하기 위한 메모리
공간을 지닌다.

 그리고 이 메모리 공간은
String 클래스의 메모리
공간과 달리

 문자를 추가하거나 삭제하는
것이 가능하다.

 따라서 수정하면서 유지해야
할 문자열이 있다면

 이 클래스에 그 내용을 담아서
관리하는 것이 효율적이다.

 StringBuilder의 대표적인
메소드는 다음과 같다.

 문자열에 내용을 더하는
메소드 뿐만 아니라

 내용의 일부를 수정 및
삭제하는 메소드도 존재한다.

public StringBuilder append(int i)
// 기본 자료형 데이터를 문자열 내용에 추가
public StringBuilder delete(int start, int end)
// 인덱스 start에서부터 end 이전까지의 내용을 삭제
public StringBuilder insert(int offset, String str)
// 인덱스 offset의 위치에 str에 전달된 문자열 추가
public StringBuilder replace(int start, int end, String str)
// 인덱스 start에서부터 end 이전까지의 내용을 str의 문자열로 대체
public StringBuilder reverse()
// 저장된 문자열의 내용을 뒤집는다.
public StringBuilder substring(int start, int end)
// 인덱스 start에서부터 end 이전까지의 내용만 담은 String 인스턴스의 생성 및 반환
public StringBuilder toString()
// 저장된 문자열의 내용을 담은 String 인스턴스의 생성 및 반환

 이 중에서 몇몇 메소드는
다양한 인자를 전달받도록
오버로딩 되어 있다.

 특히 append 메소드는 다음과
같이 매우 다양하게 오버로딩
되어 있다.

실제로는 보다 다양하게 오버로딩 되어 있다.
StringBuilder append(boolean b)
StringBuilder append(char c)
StringBuilder append(double d)
StringBuilder append(float f)
StringBuilder append(int i)
StringBuilder append(long lng)
StringBuilder append(Object obj)
StringBuilder append(String str)

 그럼 다음 예제를 통해서
앞서 소개한 메소드의 사용
방법을 보이겠다.

 코드와 실행 결과만 확인해도
메소드의 기능을 이해할 수
있도록 예제를 작성하였다.

class BuildString{
    public static void main(String[] args){
        // 문자열 "123"이 저장된 인스턴스의 생성
        StringBuilder stbuf = new StringBuilder("123");

        stbuf.append(45678);    // 문자열 덧붙이기
        System.out.println(stbuf.toString());

        stbuf.delete(0,2);      // 문자열 일부 삭제
        System.out.println(stbuf.toString());

        stbuf.replace(0, 3, "AB");  // 문자열 일부 교체
        System.out.println(stbuf.toString());

        stbuf.reverse();        // 문자열 내용 반전
        System.out.println(stbuf.toString());

        String sub = stbuf.substring(2, 4);       // 일부만 문자열로 반환
        System.out.println(sub);
    }
}

/*
실행 결과
12345678
345678
AB678
876BA
6B
*/

 그리고 StringBuilder 인스턴스
내부에는

 문자열 관리를 위한 메모리
공간이 존재하는데,

 이 공간의 크기를 인스턴스
생성 과정에서 다음과 같이
지정해 줄 수 있다.

StringBuilder stbuf = new StringBuilder(64);
// 생성자의 인자로 전달된 숫자의 크기 만큼 문자를 저장할 공간 마련

 물론 StringBuilder 인스턴스는
메모리 공간을 스스로 관리한다.

 즉 부족하면 그 공간을 늘린다.

 그러나 이는 소모가 많은 작업이다.

 따라서 사용 계획에 따라
적절한 크기를 초기에 만들면

 그만큼의 성능 향상을 기대할
수 있다.

 참고로 StringBuilder의 생성자는
다음과 같이 정의되어 있다.

public StringBuilder()
// 16개의 문자를 저장할 수 있는 메모리 공간 확보
public StringBuilder(int capacity)
// capacity개의 문자를 저장할 수 있는 메모리 공간 확보
public StringBuilder(String str)
// 전달되는 문자열과 16개의 문자를 저장할 수 있는 메모리 공간 확보

 지금까지 설명한 정도의
내용이면

 StringBuilder 클래스에 대한
이해는 충분하다.

 그러나 이 클래스에 정의된
대다수 메소드의 반환형이

 다음과 같이 StringBuilder임에
대해서는 생각해 볼 필요가 있다.

public StringBuilder append(int i)
public StringBuilder delete(int start, int end)
public StringBuilder insert(int offset, String str)

 위의 메소드들은 과욘 무엇을
반환하는 것일까?

 다음 예제를 통해서 반환의
대상이 무엇인지 확인해보자.

class ReturnStringBuilder{
    public static void main(String[] args){
        StringBuilder stb1 = new StringBuilder("123");
        StringBuilder stb2 = stb1.append(45678);

        System.out.println(stb1.toString());
        System.out.println(stb2.toString());

        // 인덱스 0~4까지의 문자 삭제
        stb2.delete(0, 4);

        System.out.println(stb1.toString());
        System.out.println(stb2.toString());

        // 참조 값의 비교
        if(stb1 == stb2)
            System.out.println("같은 인스턴스 참조");
        else
            System.out.println("다른 인스턴스 참조");
    }
}

/*
실행 결과
12345678
12345678
5678
5678
같은 인스턴스 참조
*/

 실행 결과는 다음 문장에서
append 메소드가 반환하는
것은

 append 메소드가 호출된
인스턴스의 참조 값임을
알려준다.

StringBuilder stb2 = stb1.append(45678);    // 이 경우 stb1의 참조 값이 반환된다.

 즉 위의 문장이 실행된 이후에는
stb1과 stb2는 같은 인스턴스를
참조하게 된다.

 그리고 이러한 특성은 다음과
같이 메소드를 이어서 호출을
했을 때,

 새로운 인스턴스를 생성하는
것이 아니라

 한 인스턴스의 메소드를 이어서
호출하는 결과를 갖게 한다.

stb1.append(45).append(67).append(89);

 위의 문장은 다음과 같은
순서로 호출이 이뤄져

 stb1이 참조하는 인스턴스의
append 메소드가 세 번 호출된다.

호출 순서를 기준으로 소괄호를 묶었다.
((stb1.append(45)).append(67)).append(89);

 이 내용까지 이해하였으면
자바 컴파일러가 변환해 놓은

 다음 문장이 어떻게 처리되는
지 이해할 수 있을 것이다.

String sword = "공격력: " + 14 + '.' + 42;
String sword = (new StringBuilder("공격력: ").append(14).append('.').append(42)).toString();
// 두 문장은 같은 결과를 출력한다.

 

StringBuilder 클래스 이전에 사용이 되던 StringBuffer 클래스

 StringBuilder는 자바 5에서
등장한 클래스이다.

 따라서 그 이전에는 StringBuffer라는
클래스가 사용이 되었다.

 그렇다면 이 둘의 차이점은
무엇일까?

 생성자를 포함하여 메소드의
수와 그 기능들만 놓고 보면

 전혀 차이를 보이지 않는다.

 즉 이 두 클래스는 다음 세
가지가 일치한다.

  • 생성자를 포함한 메소드의 수
  • 메소드의 기능
  • 메소드의 이름과 매개변수 선언

 그리고 이 세가지가 일치한다는
것은 사실상 같은 클래스임을
의미한다.

 그러나 차이점이 하나 있는데,
이는 다음과 같다.

StringBuffer는 쓰레드에 안전하지만,
StringBuilder는 쓰레드에 안전하지 않다.

 이 문장을 정확히 이해하려면
쓰레드를 알아야 한다.

 그러나 지금의 궁금증을 해소할
목적으로 간단히 설명하자면
이러하다.

 StringBuffer 클래스라는 것은
멀티 쓰레드 환경에서 안전하게
동작하도록 만들어졌다.

 그런데 이렇게 만들어지면
속도가 느려지는 단점을 갖는다.

멀티 쓰레드에 안전하게 설계된
StringBuffer 클래스는 속도가 느리다.

 즉 멀티 쓰레드와 상관없는
상황에서의 StringBuffer 클래스의
사용은 아쉬움을 남긴다.

 그래서 멀티 쓰레드에 상관 없는
상황에서 사용할 목적으로

 StringBuilder 클래스를 정의하기에
이른다.

 물론 이 클래스는 멀티 쓰레드에
안전하지 않다.

 대신에 속도가 빠르다는 장점이
있다.


참고 및 출처

윤성우의 열혈 Java 프로그래밍
국내도서
저자 : 윤성우
출판 : 오렌지미디어 2017.07.05
상세보기