[그린컴퓨터] Server/JAVA(자바 JDK)

String 클래스 { String 선언 2가지 방법, String 클래스의 final char[ ] 변수, StringBuffer 와 StringBuilder 클래스 활용하기, String 클래스의 다양한 메서드 }

Ben의 프로그램 2023. 6. 6. 20:48
728x90

String을 선언하는 두 가지 방법

  • 자바는 문자열을 사용할 수 있도록 String 클래스를 제공합니다. 
  • 문자열은 프로그램을 구현할 때 많이 활용합니다. 
  • String 을 선언하는 두 가지 방법이 있습니다.
  • 1. 생성자의 매개변수로 문자열 생성하기
  • 2. 문자열 상수를 가리키기 
String str1 = new String("abc"); // 생성자의 매개변수로 문자열 생성
String str2 = "test"; // 문자열 상수를 가리키는 방식
  • 언뜻 보기에는 비슷해 보이지만, 내부적으로는 아주 큰 차이가 있습니다. 

  • 1. new 예약어를 사용하여 객체를 생서아는 경우 "abc" 문자열을 위한 메모리가 할당되고 새로운 객체가 생성됩니다.
  • 2. str2 = "test" 와 같이 생성자를 이용하지 않고 바로 문자열 상수를 가리키는 경우에는 str2 가 기존에 만들어져 있던 "test" 라는 문자열 상수의 메모리 주소를 가리키게 됩니다.
  • 3. 따라서 str3 = "test" 라는 코드를 작성하면 str2 와 str3 의 주소 값은 같습니다. 
  • 예제를 통해서 다시한번 살펴보겠습니다. 
public class StringTest1 {

	public static void main(String[] args) {
		String str1 = new String("abc");
		String str2 = new String("abc");
		
		System.out.println(str1 == str2); // false, 메모리 주소가 다름
		System.out.println(str1.equals(str2)); // true, String 의 equals
		
		String str3 = "abc";
		String str4 = "abc";
		
		System.out.println(str3 == str4); // true, 같은 상수 풀에서 가져옴
		System.out.println(str3.equals(str4)); // true, String 의 equals
	}

}
  • String 을 생성자로 생성했을 때와 문자열 상수를 대입했을 때의 차이가 분명하게 드러나는 것을 볼 수 있습니다. 

상수풀 constant pool ?

  • test 나 10, 20 등과 같이 프로그램에서 사용되는 상수 값을 저장하는 공간을 '상수 풀 constant pool' 이라고 합니다.

되돌아보는 literal 리터럴

  • 리터럴(literal) 이란 프로그램에서 사용하는 모든 숫자, 문자, 논리값을 일컫는 말입니다. 
char ch = 'A';
int num = 10;
final double PI = 3.14;
  • 위에서 사용한 'A', 19, 3.14 와 같은 문자와 숫자를 '리터럴' 혹은 '리터럴 상수'라고 합니다. 
  • 리터럴은 변수나 상수 값으로 대입할 수 있습니다. 
  • 리터럴은 프로그램이 시작할 때 시스템에 같이 로딩되어 특정 메모리 공간인 상수 풀(constant pool)에 놓입니다. 
  • 예를 들어, int num = 3 문장에서 값 3(리터럴)이 메모리 공간 어딘가에 존재해야 num 변수에 그 값을 복사할 수 있습니다. 
  • 즉 숫자(리터럴)가 변수에 대입되는 과정은 일단 숫자(리터럴) 값이 어딘가 메모리에 쓰여 있고, 이 값이 다시 변수 메모리에 복사되는 것입니다. 
  • 또 한가지 리터럴 풀과 알아야 하는 점은 데이터 형태에 따른 메모리의 기본 크기의 영향 입니다. 
  • 자바에서 정수를 표현하는 메모리의 기본 크기는 4바이트입니다. 
  • 이는 상수 풀에서도 마찬가지입니다. 
  • 그렇기 때문에 4바이트 크기에 들어갈 수 없는 크기의 정수는 long 형 8바이트로 처리하라고 컴파일러에게 알려주어야 상수 풀에서 공간을 마련한 다음 상수 풀에서 복사하여 변수에 대입할 수 있습니다. 
  • 실수도 마찬가지입니다. 
  • 모든 실수 리터럴은 double 형 8바이트로 처리됩니다. 
  • float pi = 3.14 같은 경우에 4바이트로 실수를 처리해야 하기 때문에 컴파일러에게 식별자 f 를 붙여서 3.14f 와 같은 방법으로 컴파일러에게 알려주어야 합니다. 

String 클래스의 final char[ ] 변수

  • 다른 프로그래밍 언어는 문자열을 구현할 때 일반적으로 char[ ] 배열을 사용합니다.
  • 자바는 String 클래스를 제공해 char[ ] 배열을 직접 구현하지 않고도 편리하게 문자열을 사용할 수 있습니다. 
  • String.java 파일을 보면 다음과 같이 선언되어 있습니다. 

  • String 클래스의 구현 내용을 보면 private final char value[ ] 라고 선언된 char 형 배열이 있습니다. 
  • 프로그램에서 String s = new String("abc") 라고 쓰면 abc 는 String 클래스의 value 변수에 저장됩니다. 
  • 그런데 이 변수는 final 로 선언되어 있습니다. 
  • final 은 해당 변수가 변경할 수 없다는 것을 의미합니다. 
  • 따라서 한 번 생성된 문자열은 변경되지 않습니다. 
  • 이런 문자열의 특징을 '문자열은 불변(immutable)한다'라고 합니다. 
  • 그러면 프로그램에서 두 개의 문자열을 연결하면 어떻게 될까요? 불변의 문자열을 붙였으니까 오류가 날까요?
  • 이 경우 둘 중 하나의 문자열이 변경되는 것이 아니라 아예 새로운 문자열이 생성되어 데이터를 담습니다. 
  • 다음 예제는 String 으로 두 개의 문자열 ("java", "android") 를 생성하고 concat( ) 메서드로 두 문자열을 연결합니다. 
public class StringTest2 {

	public static void main(String[] args) {
		String javaStr = new String("java");
		String androidStr = new String("android");
		
		System.out.println(javaStr);
		System.out.println("처음 문자열 주소 값 : " + System.identityHashCode(javaStr));
		
		javaStr = javaStr.concat(androidStr);
		
		System.out.println(javaStr);
		System.out.println("연결된 문자열 주소 값 : " + System.identityHashCode(javaStr));
	}

}

  • 위 코드의 출력 결과를 보면 '문자열은 불면 immutable 하므로 javaStr 변수 값 자체가 변하는 것이 아니라 새로운 문자열이 생긴 것이다' 라는 것을 한번에 확인할 수 있습니다. 

StringBuffer 와 StringBuilder 클래스 활용하기 

  • 프로그램을 만들다 보면 문자열을 변경하거나 연결해야 할 때가 많습니다. 
  • 그런데 String 클래스는 한번 생성되면 그 내부의 문자열이 변경되지 않기 때문에 String 클래스를 사용하여 문자열을 계속 연결하거나 변경하는 프로그램을 작성하면 메모리가 많이 낭비됩니다. 
  • 이 문제를 해결하는 것이 바로 StringBuffer 와 StringBuilder 클래스입니다. 
  • StringBuffer 와 StringBuilder 는 내부에 변경 가능한(final이 아닌) char[ ] 변수를 가지고 있습니다. 
  • 이 두 클래스를 사용하여 문자열을 연결하면 기존에 사용하던 char[ ] 배열이 확장되므로 추가 메모리를 사용하지 않습니다. 
  • 따라서 문자열을 연결하거나 변경할 경우 두 클래스 중 하나를 사용하면 됩니다. 
  • 두 클래스의 차이는 여러 작업(스레드)이 동시에 문자열을 변경하려 할 때 문자열이 안전하게 변경되도록 보장해 주는가 그렇지 않은가의 차이입니다. 
  • StringBuffer 클래스는 문자열이 안전하게 변경되도록 보장하지만, StringBuilder 클래스는 보장되지 않습니다. 
  • 프로그램에서 따로 스레드를 생성하는 멀티스레드 프로그램이 아니라면 StringBuilder 를 상요하는 것이 실행 속도가 좀 더 빠른 차이가 있습니다. 
  • 다음 예제로 StringBuilder 사용 방법을 알아보겠습니다. 

StringBuilder 사용 예제

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); // String 으로부터 StringBuilder 생성
		System.out.println("연산 전 buffer 메모리 주소 : " + System.identityHashCode(buffer));

		buffer.append(" and");
		buffer.append(" android");
		buffer.append(" programming is fun!!!"); // buffer StringBuilder 에게 문자열 추가
		System.out.println("연산 후 buffer 메모리 주소 : " + System.identityHashCode(buffer));

		javaStr = buffer.toString(); // buffer 를 다시 String 클래스로 반환
		System.out.println(javaStr);
		System.out.println("새로 만들어진 javaStr 문자열 주소 : " + System.identityHashCode(javaStr));
	}
}
  • "Java" 라는 문자열에 여러 문자열을 추가해야 하는 경우에는 StringBuilder 클래스를 생성하고 여기에 문자열을 추가(append) 해주면 됩니다. 
  • 그러면 append 메서드가 실행될 때마다 추가로 메모리를 사용하는 것이 아닌 기존의 메모리에 계속해서 문자열을 추가하게 됩니다. 
  • 그런 다음 다시 String 클래스형이 필요하면 다시 처음 만들었던 변수에 다시 담아주면 메모리 낭비를 최소화할 수 있습니다. 

String 클래스의 다양한 메서드

  • String 클래스는 문자열을 처리하기 위한 다양한 메서드를 가지고 있습니다.