Post

Java 문법 (16) - String

String 클래스

자바의 기본형 중에 문자를 다루는 타입은 char이다. char를 사용해서 문자를 여러개 나열하려면 char[]를 사용해야하는데, 이는 매우 불편하므로 자바는 문자열을 편하게 다룰 수 있는 String 클래스를 제공한다.

String은 int, char와 다르게 대문자로 시작하는 것을 보아, 기본형이 아니라 참조형인 것을 알 수 있다. 즉, 스택 영역이 아닌 힙 영역에서 문자열 데이터가 관리된다는 것이다.




String은 불변 객체

자바에서 String은 불변객체이다. 즉, 생성 이후 객체의 값을 변경할 수 없다는 의미이다. 아래 예제코드를 보면, 변수 str이 참조하는 객체를 “Hello”라고 생성하고, 이후에 “ Java”라는 문자열을 더해서 객체 값을 변경시킨 것 처럼 보인다. 하지만 실제로 메모리 주소를 출력해보면 메모리에 “Hello Java”를 따로 만들고 그 참조값을 str변수에 대입해주는 방식으로 동작한다.

1
2
3
4
String str = "Hello";
System.out.println("str 주소: " + str.hashCode()); //str 주소: 69609650
str = str + " Java";
System.out.println("str 주소: " + str.hashCode()); //str 주소: 387417328


String 클래스를 들어가보면, 아래와 같이 생겼다. char[] 배열이 final로 선언되어 있는 것을 확인할 수 있고, 이는 불변 객체라는 의미이다.

1
2
3
4
5
6
public final class String{
	
	private final char[] value; // 자바 9이전, 자바 9이후는 byte 배열
	// 여러 메서드 
	...
}

참고: 자바 9버전 이후부터는 char 배열이 아니라 byte 배열로 바뀌었다. 메모리를 더 효율적으로 사용할 수 있게 변경된 것인데, 자바에서 문자 하나를 표현할 때 char는 2byte를 차지한다. 그런데 단순 영어, 숫자는 1byte로 표현이 가능하다. 따라서 영어, 숫자는 1byte를 사용하고 그렇지 않은 경우 2byte (UTF-16 인코딩)를 사용한다.

불변 객체




불변으로 설계된 이유

  • 사이드 이펙트 문제: 문자열 리터럴을 변수에 저장하는 경우 자바 내부에서 문자열 풀을 통해 최적화한다. 만약 String 내부의 값을 변경할 수 있다면, 기존에 문자열 풀에서 같은 문자를 참조하는 변수의 모든 문자가 같이 변경되는 사이드 이펙트 문제가 발생할 수 있다. 또한 멀티쓰레드 환경에서 동기화 문제가 발생을 막는 장점도 있다.
  • 최적화: 사이드 이펙트가 없기 때문에 문자열 풀에서 객체를 공유하고 이 과정에서 데이터 캐싱이 일어나 성능적인 이득이 있다.




String 문자열 생성 방법

String 클래스를 통해 문자열을 생성하는 방법은 2가지가 있다. 메모리 내부적으로 보면 이 둘은 큰 차이가 존재한다.

  1. 객체 생성: String str2 = new String("hello");
  2. ”” 사용: String str1 = "hello";

1번의 경우 일반적인 객체가 생성되는 것처럼 힙 영역에 따로 생성된다. str1과 str2는 서로 다른 객체를 참조하므로, == 비교는 false가 되고, equals는 true가 된다.

하지만 2번의 경우 자바에서 문자열 풀을 통해 자동으로 최적화 된다. “hello”라는 같은 문자열을 갖는 객체를 str3, str4가 참조하게 되어, == 과 , equals 모두 true가 출력된다.

1
2
3
4
5
6
7
8
9
10
11
12
String str1 = new String("hello");  
String str2 = new String("hello");  
// new로 객체를 생성한 경우에 다른 객체이기 때문에 == 연산은 false, 
// 논리적으로는 같은 값이기 때문에 equals는 true
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)));




스트링 주요 메서드

문자열 정보 조회

  • length() : 문자열의 길이를 반환
  • isEmpty() : 문자열이 비어 있는지 확인 (길이가 0)
  • isBlank() : 문자열이 비어 있는지 확인 (길이가 0이거나 공백(Whitespace)만 있는 경우)
  • charAt(int index) : 지정된 인덱스에 있는 문자를 반환

문자열 비교

  • equals(Object anObject) : 두 문자열이 동일한지 비교
  • equalsIgnoreCase(String anotherString) : 두 문자열을 대소문자 구분 없이 비교
  • compareTo(String anotherString) : 두 문자열을 사전 순으로 비교
  • compareToIgnoreCase(String str) : 두 문자열을 대소문자 구분 없이 사전적으로 비교
  • startsWith(String prefix) : 문자열이 특정 접두사로 시작하는지 확인
  • endsWith(String suffix) : 문자열이 특정 접미사로 끝나는지 확인

문자열 검색

  • contains(CharSequence s) : 문자열이 특정 문자열을 포함하고 있는지 확인
  • indexOf(String ch) / indexOf(String ch, int fromIndex) : 문자열이 처음 등장하는 위치를 반환
  • lastIndexOf(String ch) : 문자열이 마지막으로 등장하는 위치를 반환

문자열 조작 및 변환

  • substring(int beginIndex) / substring(int beginIndex, int endIndex) : 문자열의 부분문자열을 반환
  • concat(String str) : 문자열의 끝에 다른 문자열을 붙인다.
  • replace(CharSequence target, CharSequence replacement) : 특정 문자열을 새 문자열로 대체
  • replaceAll(String regex, String replacement) : 문자열에서 정규 표현식과 일치하는 부분을 새 문자열로 대체
  • replaceFirst(String regex, String replacement) : 문자열에서 정규 표현식과 일치하는 첫 번째 부분을 새 문자열로 대체
  • toLowerCase() / toUpperCase() : 문자열을 소문자나 대문자로 변환한다.
  • trim() : 문자열 양쪽 끝의 공백을 제거한다. 단순 Whitespace 만 제거
  • strip() : Whitespace 와 유니코드 공백을 포함해서 제거

문자열 분할 및 조합

  • split(String regex) : 문자열을 정규 표현식을 기준으로 분할
  • join(CharSequence delimiter, CharSequence... elements) : 주어진 구분자로 여러 문자열을결합

기타

  • valueOf(Object obj) : 다양한 타입을 문자열로 변환
  • toCharArray(): 문자열을 문자 배열로 변환
  • format(String format, Object... args) : 형식 문자열과 인자를 사용하여 새로운 문자열을 생성
  • matches(String regex) : 문자열이 주어진 정규 표현식과 일치하는지 확인




String 단점

String을 사용하면 자바가 내부적으로 컴파일 할 때 최적화를 하지만, 그렇지 못하는 경우도 있다. 예를들어 반복문 내부에서 문자열을 계속 변경하는 경우가 있다면, 객체를 계속 생성하고, 변경사항을 적용하고 또 추가하고를 반복해서 오히려 cpu와 메모리를 많이 써서 비효율 적으로 동작한다. 이런 경우에는 StringBuilder를 사용해 해결할 수 있다. StringBuilder