본문 바로가기

자바 기초

자바 String 클래스, StringBuffer, StringBuilder- Do it ! 자바프로그래밍기초

자바는 문자열을 사용할 수 있도록 String 클래스를 제공한다.

 

String을 사용하는 방법은 두 가지가 있는데,

String str1 = new String("abc"); 처럼 생성자의 매개변수로 문자열을 생성하는 방식과

String str2 = "test"; 처럼 문자열 상수를 가리키는 방식이 있다.

 

두 가지는 큰 차이가 있다.

new 예약어를 사용하여 객체를 생성하는 경우는 "abc" 문자열을 위한 메모리가 할당되고 새로운 객체가 생성된다.

str2 = "test"와 같이 문자열 상수를 가리키는 경우에는 "test"라는 문자열 상수의 메모리 주소를 가리키게 된다.

따라서 String str3 = "test" 코드를 작성하면 str2와 str3는 주소 값이 같게 된다.

 

그림으로 나타내면 다음과 같고, test나 10, 20 등과 같이 프로그램에서 사용되는 상수 값을 저장하는 공간을

'상수 풀'이라고 한다.

 

 

public class StringTest {
	String str1 = new String("abc");
    String str2 = new String("abc");
    
    System.out.println(str1 == str2); // 인스턴스가 매번 새로 생성되므로 주소 값이 서로 다르므로 false
    System.out.println(str1.equals(str2)); // 문자열 값은 같으므로 true
    
    String str3 = "abc";
    String str4 = "abc";
    
    System.out.println(str3 == str4); // 문자열abc는 상수풀에 저장되어 있으므로 str3,str4가 가리키는 주소 같음
    System.out.println(str3.equals(str4)); // 문자열 값도 같으므로 true 반환
}

위 코드를 보면 str1 == str2 는 인스턴스가 각각 생성되어서 주소 값이 서로 다르므로 false가 출력되고,

str1.equals(str2) 는 문자열 값을 비교해서 같으므로 true가 출력,

str3 == str 4 는 문자열 "abc"가 상수 풀에 저장되고, "abc"를 str3, str4가 같이 가리키므로 주소는 같아서 true 출력,

str3.equals(str4) 는 문자열 값 비교해서 같으므로 true가 출력된다.

 

 

이렇게 생성된 문자열들은 변경되지 않고 이것을 '문자열은 불변한다'라고 한다.

그렇다면 프로그램에서 두 개의 문자열을 연결하면 어떻게 될까???

이 경우에는 하나의 문자열이 변경되는 것이 아니라 두 문자열이 연결된 새로운 문자열이 생성된다.

 

public class StringTest {
	public static void main(String[] args) {
    	String a = new String("abc");
        String b = new String("def");
        System.out.println(a);
        System.out.println("처음 문자열 주소 값 : " + System.identityHashCode(a));
        
        a = a.concat(b);
        
        System.out.println(a);
        System.out.println("연결된 문자열 주소 값 : " + System.identityHashCode(b));
    }
}

문자열은 불변하므로 a 변수값 자체가 변하는게 아니라 새로운 문자열이 생성된 것이다.

abcdef 문자열이 새로 생성되고 a는 그 문자열을 가리키게 된 것이고, 처음 해시코드값과 문자열을 연결한 후 해시코드값을 비교해보면 주소 값이 달라진 것을 알 수 있다.

 

 

 

그렇다면 문자열은 어떻게 변경해야하나 ???

String 클래스는 한번 생성되면 그 내부의 문자열이 변경되지 않기 때문에 String 클래스를 사용하여 문자열을 계속 연결하거나 변경하는 프로그램을 작성하면 메모리가 많이 낭비된다.

이 문제를 해결하는 것이 바로 StringBufferStringBuilder 클래스가 있다.

이 두 클래스를 사용하여 문자열을 연결하면 기존에 사용하던 char[] 배열이 확장되므로 추가 메모리를 사용하지 않는다.

따라서 문자열을 연결하거나 변경할 경우 두 클래스 중 하나를 사용하면 된다.

 

두 클래스의 차이는 여러 작업(스레드)이 동시에 문자열을 변경하려 할 때 문자열이 안전하게 변경되도록 보장해 주는가 그렇지 않은가의 차이다.

 

StringBuffer 클래스는 문자열이 안전하게 변경되도록 보장하지만, StringBuilder 클래스는 보장되지 않는다.

따로 스레드를 생성하는 멀티스레드 프로그램이 아니라면 StringBuilder가 실행속도가 더 빠르다.

 

 

package object;

public class StringBuilderTest {

	public static void main(String[] args) {
		String javaStr = new String("Java");
		System.out.println("javaStr 문자열 주소 : " + System.identityHashCode(javaStr));
		
		StringBuilder buffer = new StringBuilder(javaStr);
		System.out.println("연산 전 buffer 메모리 주소 : " + System.identityHashCode(buffer));
		
		buffer.append(" and");
		buffer.append(" android");
		buffer.append(" programming is fun!!");
		System.out.println("연산 후 buffer 메모리 주소 : " + System.identityHashCode(buffer));
		
		javaStr = buffer.toString();
		System.out.println(javaStr);
		System.out.println("연결된 javaStr 문자열 주소 : " + System.identityHashCode(buffer));
	}

}

위 코드에서 StringBuilder 클래스를 생성하고 여기에 문자열을 추가했다. 그러면 append()가 실행될 때마다 메모리가 새로 생성되는 것이 아니고, 하나의 메모리에 계속 연결되는 것을 해시 코드 값을 통해 알 수 있다. 연산 전 메모리 주소와 연산 후 메모리 주소가 같기 때문에. 그리고 문자열을 변경한 후 buffer에 toStrint()을 호출하면 다시 문자열로 반환할 수 있다.