15장. String

공부하는 감자·2023년 12월 7일
0

자바의 신 3판

목록 보기
15/30

들어가기 전

『자바의 신 3판』 을 읽고 내용 정리 및 공부한 내용을 정리한 글입니다.
서적: 자바의 신 3판 구입처

내용 정리

String의 선언문

public final class String extends Object
	implements Serializable, Comparable<String>, CharSequence
  • public final 로 선언되어 있다. 다시 말해, String은 확장(자식 클래스를 양산)할 수 없다.
  • Obejct 를 상속받았으므로 따로 확장한 클래스는 없다.
  • Serializable, Comparable<String>, CharSequence 총 3개의 인터페이스를 구현한 것을 알 수 있다.

Serializable 인터페이스

구현해야 하는 메소드가 하나도 없는 아주 특이한 인터페이스이다.

이 인터페이스를 구현한다고 선언해 놓으면, 해당 객체를 파일로 저장하거나 다른 서버에 전송 가능한 상태가 된다.

Comparable 인터페이스

compareTo() 라는 메소드 하나만 선언되어 있다.

이 메소드는 매개 변수로 넘어가는 객체와 현재 객체가 같은 지를 비교해, 순서 상으로 같으면 0, 앞에 있으면 -1, 뒤에 있으면 1을 리턴한다.

CharSequence 인터페이스

해당 클래스가 문자열을 다루기 위한 클래스라는 것을 명시적으로 나타내는 데 사용된다.

뒤에 나오는 StringBuilder와 StringBuffer 클래스도 이 인터페이스를 구현해 놓았다.

제너릭 Generic

꺽쇠(<>) 안에 String이라고 적었는데, 이는 제너릭이라는 것을 의미한다. 이는 21장에서 설명한다.

String의 생성자

책에 나온 생성자는 아주 많은데, 그 중 일부만 정리했다.

💡 디코딩
디코딩은 일반적으로 암호화되어 있거나 컴퓨터가 이해할 수 있는 값들을 알아보기 쉽게 변환하는 것을 말한다.

생성자설명
String()비어있는 String 객체를 생성한다. 그런데 이렇게 생성하는 것보다 String name = null;로 선언하는 것이 더 효율적이다.
String(byte[] bytes)현재 사용 중인 플랫폼의 캐릭터 셋을 사용하여 제공된 byte 배열을 디코딩한 String 객체를 생성한다.
String(byte[] bytes, String CharsetName)지정한 캐릭터 셋을 사용하여 제공된 byte 배열을 디코딩한 String 객체를 생성한다.
String(StringBuffer buffer)매개 변수로 넘어온 StringBuffer 클래스에 정의되어 있는 문자열의 값과 동일한 String 객체를 생성한다.
String(StringBuilder builder)매개 변수로 넘어온 StringBuilder 클래스에 정의되어 있는 문자열의 값과 동일한 String 객체를 생성한다.

이 중에서, 많이 사용하는 생성자는 다음 두 가지다.

  • String(byte[] bytes)
  • String(byte[] bytes, String CharsetName)

대부분의 언어에서는 문자열을 변환할 때 기본적으로 영어로 해석하려고 한다. 따라서 한글을 사용하는 우리나라에서는 자주 사용할 수 밖에 없다.

또, String은 대부분 따옴표로 묶어 생성하기 때문에 굳이 어려운 생성자를 사용할 필요가 없기 때문에 다른 생성자들은 잘 사용하지 않는다.

String 문자열을 byte로 변환하기

String 클래스에는 현재의 문자열 값을 byte 배열로 변환하는 getBytes() 라는 메소드들이 있다.

리턴 타입메소드 시그니처설명
byte[]getBytes()기본 캐릭터 셋의 바이트 배열을 생성한다.
byte[]getBytes(Charset charset)지정한 캐릭터 셋 객체 타입으로 바이트 배열을 생성한다.
byte[]getBytes(String charsetName)지정한 이름의 캐릭터 셋을 갖는 바이트 배열을 생성한다.

보통 캐릭터 셋을 잘 알고 있거나, 같은 프로그램 내에서 문자열을 byte 배열로 만들 때에는 맨 위의 getBytes() 를 사용하면 된다.

하지만, 다른 시스템에서 전달 받은 문자열을 byte 배열로 변환할 때에는 두번째나 세번째에 있는 메소드를 사용하는 것이 좋다. 문자열이 다른 캐릭터 셋으로 되어 있을 수도 있기 때문이다.

캐릭터 셋

문자의 집합을 의미하며, 한글이나 일본어와 같이 특정 나라의 글자를 말한다.

자바 뿐만 아니라, 어떤 프로그래밍 언어를 사용할 경우에도 특수 문자(알파벳을 제외한 나라의 문자)를 표시할 일이 생긴다.

한글도 기본적으로는 알파벳이 아니기 때문에 고유의 캐릭터 셋을 가진다.

브라우저에서 한글이 깨지는 이유는, 브라우저에서 생각하는 캐릭터 셋과 웹 페이지에 지정된 캐릭터 셋이 다르기 때문이다.

java.nio.Charset 클래스 API에는 표준 캐릭터 셋이 정해져 있다. 참고로, 밑의 표에서 UCS는 유니코드 캐릭터 셋(Unicode Character Set)의 약자다.

캐릭터 셋 이름설명
US-ASCII7비트 아스키
ISO-8859-1ISO 라틴 알파벳
UTF-88비트 UCS 변환 포맷
UTF-16BE16비트 UCS 변환 포맷. big-endian 바이트 순서를 가진다.
UTF-16LE16비트 UCS 변환 포맷. little-endian 바이트 순서를 가진다.
UTF-1616비트 UCS 변환 포맷. 바이트 순서는 byte-order mark라는 것에 의해서 정해진다.
EUC-KR8비트 문자 인코딩으로, EUC의 일종이며 대표적은 “한글 완성형” 인코딩
MS949Microsoft에서 만든 “한글 확장 완성형” 인코딩

자바에서 한글을 처리하기 위해 많이 사용하는 캐릭터 셋은 UTF-16이다. 예전에는 UTF-8이나 EUC-KR을 많이 사용했지만, 요즘은 대부분 UTF-16을 사용한다.

캐릭터 셋으로 변환

아래는 현재 플랫폼의 기본 캐릭터 셋으로 변환을 하고, 다시 현재 플랫폼의 기본 캐릭터 셋으로 변환을 하는 코드이다.

String korean = "한글";

// byte 배열로 인코딩
byte[] incode = korean.getBytes();

// String 객체로 디코딩
String decode = new String(incode);

출력 결과 디코딩했을 때 깨짐 없이 나오는 것을 볼 수 있다. 이때, 잘못된 캐릭터 셋으로 변환을 하면 우리가 알아볼 수 없는 문자로 표시된다. 이걸 “한글이 깨진다”라고 한다.

이 한글이 깨지는 현상을 방지하기 위해서는 byte 배열로 생성할 때 사용한 캐릭터 셋을 다시 문자열로 전환할 때에도 동일하게 사용해야 한다.

캐릭터 셋에 따른 인코딩 출력 결과의 차이

캐릭터 셋에 따라 인코딩 출력 결과가 달라진다. 한글을 byte 배열로 만들 때 어떤 캐릭터 셋을 쓰느냐에 따라서 배열의 크기가 다르다.

EUC-KR의 경우는 한글 두 글자를 표현하기 위해서 4바이트를 사용하지만, UTF-16은 6 바이트를 사용한다.

직접 변수 값을 변경해서 출력해보면 글자 수와 상관없이 무조건 2바이트의 차이가 발생한다는 것을 알 수 있다.

직접해 봅시다 결과

  1. “한글”을 각각 인코딩!“한글”을 각각 인코딩!!

  2. “자바의 신”을 인코딩“자바의 신”을 인코딩

  3. “자바의 신 최고 !!”를 인코딩“자바의 신 최고 !!”를 인코딩

자바에서 한글이 몇 바이트를 점유하는지 알아 두는 것은 우리 나라에서 개발하면서 매우 중요하다.

모든 생성자를 다 외우고 있을 필요는 없지만, 많이 사용되는 몇몇 생성자와 getBytes() 메소드는 꼭 기억하도록 하자.

유의할 점

아래의 생성자와 메소드는 UnsupportedEncodingException을 발생시킬 수 있다.

  • byte 배열과 String 타입의 캐릭터 셋을 받는 생성자
  • getBytes() 메소드 중에서 String 타입의 캐릭터 셋을 받는 메소드

만약 존재하지 않는 캐릭터 셋의 이름을 지정할 경우에 이 예외가 발생하므로, 반드시 try-catch로 감싸주거나 메소드 선언 시 throws 구문을 추가해 주어야만 한다.

객체의 null 체크

String뿐만이 아니라 모든 객체를 처리할 때에는 널 체크를 반드시 해야만 한다. 널은 null로 표시하며, 어떤 참조 자료형도 널이 될 수 있다.

객체가 널이라는 것은 객체가 아무런 초기화가 되어 있지 않으며, 클래스에 선언되어 있는 어떤 메소드도 사용할 수 없다는 것을 의미한다.

다시 말해서, 널 체크를 하지 않으면 객체에 사용할 수 있는 모든 메소드들은 모두 예외를 발생시킨다.

널일 때 발생하는 예외: NullPointException

객체가 널인지 아닌지 체크하는 것은 ==이나 !=연산자를 사용하여 확인한다.

String text = null;
if (text == null) System.out.print("null입니다");

널 체크를 하지 않아서 애플리케이션이 비정상으로 작동하여 장애로 이어질 수 있다. 그러므로 메소드의 매개 변수로 넘어오는 객체가 널이 될 확률이 조금이라도 있다면 반드시 한 번씩 확인하는 습관을 갖고 있어야만 한다.

String의 내용을 비교하고 검색하는 메소드

String 클래스는 문자열을 나타낸다. 따라서 문자열 내의 특정 위치를 찾거나 값을 비교하는 등의 작업은 아주 빈번히 발생된다.

문자열의 길이를 확인하는 메소드

리턴 타입메소드 시그니처설명
intlength()문자열의 길이를 리턴한다.

배열을 제외한 모든 클래스는 length() 메소드를 이용해 문자열의 길이를 확인한다. 배열의 경우엔 메소드는 없는 특수한 객체이므로, 괄호가 없는 length 를 사용한다.

당연한 이야기지만, 공백도 길이에 포함된다. 한글의 경우에도 길이는 1로 반환된다. (바이트 길이와는 다르다.)

문자열이 비어 있는지 확인하는 메소드

리턴 타입메소드 시그니처설명
booleanisEmpty문자열이 비어 있는지를 확인한다. 비어 있으면 true를 리턴한다.

만약 문자열이 공백 하나로 되어 있더라도, 이 메소드는 false(비어있지 않음)로 리턴한다.

문자열이 같은지 비교하는 메소드

이름은 상이하지만, 아래의 모든 메소드들은 매개 변수로 넘어온 값과 String 객체가 같은지 비교하기 위한 메소드다.

IgnoreCase가 붙은 메소드들은 대소문자 구분을 할지 안할지 여부만 다르다.

리턴 타입메소드 시그니처
booleanequals(Object anObject)
booleanequalsIgnoreCase(String anotherStr)
intcompareTo(String anotherStr)
intcompareToIgnoreCase(String str)
booleancontentEquals(CharSequence cs)
booleancontentEquals(StringBuffer sb)

==과 equals()

먼저, 예제와 함께 본다.

  1. “”로 선언한 text와 text2를 동등 연산자(==)로 비교
  2. “”로 선언한 text와 new로 생성한 text3를 동등 연산자(==)로 비교
  3. “”로 선언한 text를 equals를 이용해 비교
String text="Check value";
String text2="Check value";
String text3=new String("Check value");

if(text==text2) {
	System.out.println("text==text2 result is same.");
} else {
	System.out.println("text==text2 result is different.");
}
// text==text2 result is same.

if(text==text3) {
	System.out.println("text==text3 result is same.");
} else {
	System.out.println("text==text3 result is different.");
}
// text==text3 result is different.

if(text.equals("Check value")) {
	System.out.println("text.equals(text2) result is same.");
}
// text.equals(text2) result is same.

Constant Pool

자바에서는 객체들을 재사용하기 위해서 Constant Pool이라는 것이 만들어져 있다. String의 경우 동일한 값을 갖는 객체가 있으면 이미 만든 객체를 사용한다.

따라서, text와 text2 객체는 실제로는 같은 객체다.

text3처럼 new String() 로 String 객체를 생성하면 값이 같은 String 객체를 생성한다고 하더라도 Constant Pool의 값을 재활용하지 않고 별도의 객체를 생성한다.

따라서, "text==text3 result is different." 가 출력되는 것이다.

💡 좀 더 자세히 부연 설명해보자면.

String 은 한번 사용이 되면 또다시 재사용될 확률이 높다. 따라서 Heap 영역 내에 문자열 상수의 Pool(String Constant Pool)을 유지하고, 해당 Pool 에 사용자가 정의한 변수의 value 들을 담는다.

1) 따옴표(””)로 선언했을 경우 (Literal 리터럴이라고도 한다)
해당 문자열은 Constant Pool에서 있는지 확인 후 있으면 해당 메모리를 가리키도록 하고, 없으면 Constant Pool에 넣고 그곳을 가리키도록 한다.
1. Sring text = “java” 선언
2. Constant Pool에 있는지 확인
=> 없으므로 “java”를 넣고 text는 해당 메모리를 가리킴
3. String text2 = “java” 선언
4. Constant Pool에 있는지 확인
=> 있으므로, text2는 해당 메모를 가리킴

따라서, 동등 연산자(==)로 검사하면 같은 메모리 주소를 공유하므로 “같은 객체”라고 나오는 것이다.

2) new String()로 선언했을 경우
Java Heap영역에 새로운 메모리를 할당받는다. 같은 문자열을 선언해도, 새로 만든다.

따라서, 동등 연산자(==)로 검사하면 다른 메모리 주소를 가지고 있으므로 “다른 객체”라고 나오는 것이다.

compareTo()

compareTo() 메소드는 Comparable 인터페이스에 선언되어 있다. 보통 정렬(sorting)을 할 때 사용한다.

비교하려는 매개 변수로 넘겨준 String 객체가 알파벳 순으로 앞에 있으면 양수를, 뒤에 있으면 음수를 리턴한다.

대소문자 구분을 하지 않고 compareTo() 메소드를 수행하는 결과를 내고 싶으면 compareToIgnoreCase() 메소드를 사용한다.

contentEquals()

매개 변수로 넘어오는 CharSequence와 StringBuffer 객체가 String 객체가 같은지 비교하는 데 사용된다.

이 장의 마지막 절에서 별도로 확인한다.

특정 조건에 맞는 문자열이 있는지를 확인하는 메소드

리턴 타입
메소드 시그니처
설명
booleanstartsWith(String prefix)매개 변수로 넘겨준 값으로 시작하는지를 확인한다.
booleanstartsWith(String prefix, int toffset)매개 변수로 넘겨준 값으로 시작하는지를 확인한다.
booleanendsWith(String suffix)매개 변수로 넘어온 값으로 해당 문자열이 끝나는지를 확인한다.
booleancontains(CharSequence s)매개 변수로 넘어온 값이 문자열에 존재하는지를 확인한다.
booleanmatches(String regex)매개 변수로 넘어온 값이 문자열에 존재하는지를 확인한다. 이때, 매개 변수로 넘어오는 값이 정규 표현식으로 되어 있어야 한다.
booleanregionMatches(boolean ignoreCase, int toffset, String other, int ooffset, int len)문자열의 toffset부터 len까지의 내용과 other의 ooffset부터 len까지의 내용이 일치하는지를 반환한다. 대소문자를 구분할지 여부를 결정할 수 있다.
booleanregionMatches(int toffset, String other, int ooffset, int len)문자열의 toffset부터 len까지의 내용과 other의 ooffset부터 len까지의 내용이 일치하는지를 반환한다.

💡 정규 표현식 Reqular Expression
이메일을 점검하거나, 웹 페이지의 URL을 점검하는 등의 작업을 쉽게 하기 위해서 공식에 따라 만든 식을 말한다.
자세한 내용은 인사이트의 <손의 잡히는 정규 표현식>이라는 책을 참고.

regionMatches()가 무조건 false로 나오는 조건

  • toffset이 음수일 때
  • ooffset이 음수일 때
  • toffset+len이 비교 대상의 길이보다 클 때
  • ooffset+len이 other 객체의 길이보다 클 때
  • ignoreCase가 false인 경우에는 비교 범위의 문자들 중 같은 위치(index)에 있는 char가 다를 때
  • ignoreCase가 true인 경우에는 비교 범위의 문자들을 모두 소문자로 변경한 후 같은 위치(index)에 있는 char가 달라야 한다.

String 내에서 위치를 찾아내는 방법

indexOf()와 lastIndexOf

리턴 타입메소드 시그니처
intindexOf(int ch)
intindexOf(int ch, int fromIndex)
intindexOf(String str)
intindexOf
intlastIndexOf(int ch)
intlastIndexOf(int ch, int fromIndex)
intlastIndexOf(String str)
intlastIndexOf(String str, int fromIndex)

이 메소드를 사용하면 해당 객체의 특정 문자열이나 char가 있는 위치를 알 수 있다. 만약 찾고자 하는 문자열이나 char가 없으면 이 메소드는 -1을 반환한다.

startWith()와 비슷한 기능이지만, indexOf() 메소드는 문자열의 모든 내용을 다 확인해 봐야 한다는 단점이 있다.

  • indexOf()는 앞에서부터(가장 왼쪽부터) 문자열이나 char를 찾는다.
  • lastIndexOf()는 뒤에서부터(가장 오른쪽부터) 문자열이나 char를 찾는다.

이 두 메소드는 매개 변수로 아래 것들을 받는다.

  • int ch
  • int ch, int fromIndex
  • String str
  • String str, int fromIndex

여기서 int 타입을 받는 것은, char가 정수형이기 때문이다. fromIndex는 문자열의 몇 번째(fromIndex) 자리부터 검사할 것인지를 정하는 검색 시작 위치이다.

검색 시작 위치는 가장 왼쪽에서부터의 위치를 말한다.

String의 값을 일부 추출

문자열의 위치를 찾는 이유는 여러 가지다. 보통 그 위치부터 어떤 값을 추출해 내거나, 그 값이 존재하는지를 확인할 때 사용한다.

char 단위의 값을 추출하는 메소드

리턴 타입메소드 시그니처설명
charcharAt(int index)특정 위치의 char를 리턴한다.

다른 메소드들도 있지만 그리 많이 사용되지 않는다.

char 배열의 값을 String으로 변환하는 메소드

리턴 타입
메소드 시그니처
설명
static StringcopyValueOf(char[] data)char 배열을 문자열로 변환한다.
static StringcopyValueOf(char[] data, int offset, int count)char 배열을 offset 위치부터 count까지의 개수만큼만 문자열로 변환한다.

이 메소드는 static 메소드이기 때문에, 다음과 같이 static하게 호출하여 사용해야 한다.

char values[] = new char[] {'J', 'A', 'V', 'A'};
String javaText=String.copyValueOf(values);

String의 값을 char 배열로 변환하는 메소드

리턴 타입메소드 시그니처설명
char[]toCharArray()문자열을 char 배열로 변환하는 메소드

Java8까지의 String 클래스에서는 String 문자열의 값을 char 배열로 저장해 왔다. 하지만, 성능과 메모리 활용성을 고려하여 Java9부터는 byte 배열로 내부적으로 저장한다.

해당 내용은 2권 16장에서 자세하게 살펴본다.

문자열의 일부 값을 잘라내는 메소드

리턴 타입
메소드 시그니처
설명
Stringsubstring(int beginIndex)beginIndex부터 끝까지 자른다.
Stringsubstring(int beginIndex, int endIndex)beginIndex부터 endIndex까지 자른다.
CharSequencesubSequence(int beginIndex, int endIndex)beginIndex부터 endIndex까지 자른다.

이때, endIndex는 데이터의 길이가 아닌 subString이 끝나는 위치(index)를 말한다.

보통 indexOf() 메소드와 같이 사용된다.

문자열을 여러 개의 String 배열로 나누는 메소드

리턴 타입
메소드 시그니처
설명
String[]split(String regex)regex에 있는 정규 표현식에 맞추어 문자열을 잘라 String 배열로 리턴한다.
String[]split(String regex, int limit)regex에 있는 정규 표현식에 맞추어 문자열을 잘라 limit 보다 작은 크기의 String 배열로 리턴한다.

자바에서 문자열을 여러 개의 문자열의 배열로 나누는 방법은 두 가지가 있다.

  • String 클래스에 선언된 split() 메소드 사용
  • java.util.StringTokenizer라는 클래스를 사용

만약 정규 표현식을 사용하여 문자열을 나누려면 split을, 그렇지 않고 그냥 특정 String으로 문자열을 나누려고 한다면 StringTokenizer 클래스를 사용하는 것이 편하다.

특정 알파벳이나 기호 하나로 문자열을 나누려고 한다면 둘 중 어떤 것을 써도 상관없다.

StringTokenizer은 2권 부록 10장에서 자세히 다룬다.

String의 값을 변경

문자열을 합치는 메소드와 공백을 없애는 메소드

리턴 타입
메소드 시그니처
설명
Stringconcat(String str)매개 변수로 받은 str을 기존 문자열의 우측에 붙인 새로운 문자열 객체를 생성하여 리턴한다.
Stringtrim()문자열의 맨 앞과 맨 뒤에 있는 공백들을 제거한 문자열 객체를 리턴한다.

concat() 메소드의 경우, 자바에서는 + 로 문자열을 더할 수 있기 때문에 쓸 일이 없다. 이렇게 문자열을 계속 더할 일이 있다면 StringBuffer나 StringBuilder를 사용해 문자열을 더하기 바란다.

trim() 메소드는 공백을 제거할 때 매우 유용하게 많이 사용된다.

내용을 교체(replace)하는 메소드

리턴 타입
메소드 시그니처
설명
Stringreplace(char oldChar, char newChar)해당 문자열에서 oldChar의 값을 newChar로 대체한다.
Stringreplace(CharSequence target, CharSequence replacement)해당 문자열에서 target의 값을 replacement로 대체한다.
StringreplaceAll(String regex, String replacement)해당 문자열 중 regex에 표현된 정규 표현식에 포함되는 모든 내용을 replacement로 대체한다.
StringreplaceFirst(String regex, String replacement)해당 문자열 중 regex에 표현된 정규 표현식에 포함되는 모든 내용을 replacement로 대체한다.

이 메소드를 수행한다고 해서 기존 문자열의 값은 바뀌지 않는다. 즉, 기존 문자열의 값을 바꿔주려면 =로 대입해줘야 한다.

이 메소드들은 대소문자를 구분한다.

특정 형식에 맞춰 값을 치환하는 메소드

리턴 타입
메소드 시그니처
설명
static Stringformat(String format, Object… args)format에 있는 문자열의 내용 중 변환해야 하는 부분을 args의 내용으로 변경한다.
static Stringformat(Locale l, String format, Object… args)format에 있는 문자열의 내용 중 변환해야 하는 부분을 args의 내용으로 변경한다. 단, locale 타입의 l에 선언된 지역에 맞추어 출력한다.

💡 Locale은 지역적으로 다른 표현 형식을 제공하기 위한 것이다. 보통 Locale을 지정하지 않으면 기본적으로 자바 프로그램이 수행되는 OS의 지역 정보를 기본으로 따른다.

format() 메소드는 정해진 기준에 맞춘 문자열이 있으면, 그 기준에 있는 내용을 변환한다.

  • %s : String
  • %d : 정수형
  • %f : 소수점이 잇는 숫자
  • %% : %

java.util.Formatter 클래스의 API 문서에 사용하는 방법이 자세히 설명되어 있다.

String text="제 이름은 %s이며 %d권의 책을 썼고, 하루에 %f %%만큼의 시간을 씁니다.";
String realText=String.format(text, "홍길동", 7, 10.5);

출력만을 위해서는 System.out.format() 라는 메소드가 있어서 굳이 이렇게 사용하지는 않는다.

또한, 대치해야 할 문자열을 오버(Over)해서 쓰는 건 괜찮지만 적게 쓰는 건 안 된다. 위 예제에서는 3개의 문자열을 대치하는데, 4개 이상을 써도 괜찮지만 2개를 쓰면 예외가 출력된다.

대소문자를 바꾸는 메소드

리턴 타입
메소드 시그니처
설명
StringtoLowerCase()모든 문자열의 내용을 소문자로 변경한다.
StringtoLowerCase(Locale locale)지정한 지역 정보에 맞추어 모든 문자열의 내용을 소문자로 변경한다.
StringtoUpperCase()모든 문자열의 내용을 대문자로 변경한다.
StringtoUpperCase(Locale locale)지정한 지역 정보에 맞추어 모든 문자열의 내용을 대문자로 변경한다.

기본 자료형을 문자열로 변환하는 메소드

리턴 타입메소드 시그니처
static StringvalueOf(boolean b)
static StringvalueOf(char c)
static StringvalueOf(char[] data)
static StringvalueOf(char[] data, int offset, int count)
static StringvalueOf(double d)
static StringvalueOf(float f)
static StringvalueOf(int i)
static StringvalueOf(long l)
static StringvalueOf(Object obj)

valueOf() 메소드는 기본 자료형을 String 타입으로 변환한다.

  • 대부분 기본 자료형을 String 타입으로 변환할 필요가 있을 때는 String 과 합치는 과정을 거친다.
    이럴 경우, 별도로 valueOf() 메소드를 사용할 필요까지는 없다.
    ```java
    byte b=1;
    
    // 아래 두 가지는 같은 결과
    String byte1 = String.valueOf(b);
    String byte2 = b+"";
    ```
  • 하지만, String으로 변환만 해놓고 별도의 문자열과 합치는 과정이 없을 경우 valueOf() 메소드를 사용하는 것을 권장한다.
  • Object를 넘겨주면 toString()의 결과를 리턴해준다. Object가 null이라면, 문자열 “null”을 리턴한다.

절대 사용해서는 안되는 메소드

intern() 이라는 메소드가 있다. 이 메소드는 C로 구현되어 있는 native 메소드 중 하나인데, 시스템의 심각한 성능 저하를 발생시킬 수도 있다.

intern() 메소드를 사용하면, new String으로 생성한 문자열 객체라고 할지라도 풀에 해당 값이 있으면 풀에 있는 값을 참조하는 객체를 리턴한다. 만약 동일한 문자열이 존재하지 않으면 해당 값을 추가한다.

equals() 메소드로 비교하는 것보다는 ==로 비교하는 것이 훨씬 빠르다. 하지만, 만약 새로운 문자열을 쉴새 없이 만드는 프로그램에서 intern() 메소드를 사용하여 억지로 문자열 풀에 값을 할당하도록 만들면, 저장되는 영역은 한계가 있기 때문에 그 영역에 대해서 별도로 메모리를 청소하는 단계를 거치게 된다.

따라서, 작은 연산 하나를 빠르게 하기 위해서 전체 자바 시스템의 성능에 영향을 주게 된다.

StringBuffer와 StringBuilder

String은 Immutable한(불변하는) 객체다. 다시 말해서 한 번 만들어지면 더 이상 그 값을 바꿀 수 없다.

String에 문자열을 더하면 새로운 String 객체가 생성되고, 기존 객체는 버려진다. 그러므로 계속 하나의 String을 더하는 작업을 한다면 계속 쓰레기를 만들게 된다.

버려진 객체는 나중에 GC(Garbage Collection, 가비지 컬렉션)의 대상이 된다.

이러한 String 클래스의 단점을 보완하기 위해서 나온 클래스가 StringBuffer와 Stringbuilder 이다.

두 클래스에서 제공하는 메소드는 동일하다. 하지만, StringBuffer는 Thread safe하고 Stringbuilder는 Thread safe하지 않다는 차이가 있다.

속도는 Thread safe하지 않은 클래스가 더 빠르다(Thread safe한지 체크할 필요가 없으니).

append()

두 클래스는 문자열을 더하더라도 새로운 객체를 생성하지 않는다. 그렇다고 + 연산자를 사용할 수 있다는 말은 아니다.

이 두 클래스에서 가장 많이 사용하는 메소드는 append() 라는 메소드로, 매개 변수로 모든 기본 자료형과 참조 자료형을 포함한다.

따라서, 어떤 값이라도 이 메소드의 매개 변수로 들어갈 수 있다.

StringBuilder sb = new StringBuilder();
sb.append("Hello");
sb.append(" world");
// 이렇게 쓸 수도 있다
sb.append("Hello").append("Hello").append("Hello");

JDK 5 이상에서는 String 더하기 연산을 할 경우, 컴파일할 때 자동으로 해당 연산을 StringBuilder로 변환해 준다.

따라서 일일이 더하는 작업을 변환해 줄 필요는 없으나, for 루프와 같이 반복 연산을 할 때에는 자동으로 변환을 해주지 않으므로 꼭 필요하다.

String vs StringBuilder vs StringBuffer

이 세 클래스의 공통점은 모두 문자열을 다루고, CharSequence 인터페이스를 구현했다는 점이다.

따라서, 이 세 가지 중 하나의 클래스를 사용하여 매개 변수로 받는 작업을 할 때 CharSequence 타입으로 받는 것이 좋다.

일반적으로 하나의 메소드 내에서 문자열을 생성하여 더할 경우에는 StringBuilder를 사용해도 전혀 상관 없다.

하지만, 어떤 클래스에서 문자열을 생성하여 더하기 위한 문자열을 처리하기 위한 인스턴스 변수가 선언되었고, 여러 쓰레드에서 이 변수를 동시에 접근하는 일이 있을 경우에는 반드시 StringBuffer를 사용해야만 한다.

정리해 봅시다.

Q. String 클래스는 final 클래스인가요? 만약 그렇다면, 그 이유는 무엇인가요?

Me: 더 확장하지 못하게 하려고. 캐싱과 보안 상의 문제가 있다고 한다.

Q. String 클래스가 구현한 인터페이스에는 어떤 것들이 있나요?

Me: Serializable, Comparable, CharSequence

Q. String 클래스의 생성자 중에서 가장 의미없는 (사용할 필요가 없는) 생성자는 무엇인가요?

Me: String()

Q. String 문자열을 byte 배열로 만드는 메소드의 이름은 무엇인가요?

Me: getBytes()

Q. String 문자열의 메소드를 호출하기 전에 반드시 점검해야 하는 사항은 무엇인가요?

Me: null 체크

Q. String 문자열의 길이를 알아내는 메소드는 무엇인가요?

Me: length()

Q. String 클래스의 equals() 메소드와 compareTo() 메소드의 공통점과 차이점은 무엇인가요?

Me: 매개 변수로 받은 값과 문자열이 같은지를 비교하는데, equals()는 boolean 타입으로 리턴하고 compareTo는 int 타입으로 순서를 리턴한다

Q. 문자열이 "서울시"로 시작하는지를 확인하려면 String의 어떤 메소드를 사용해야 하나요?

Me: startsWith()

Q. 문자열에 "한국"이라는 단어의 위치를 찾아내려고 할 때에는 String의 어떤 메소드를 사용해야 하나요?

Me: contains(), indexOf(), valueOf()

Q. 위의 문제의 답에서 "한국"이 문자열에 없을 때 결과값은 무엇인가요?

Me: false 혹은 -1

Q. 문자열의 1번째부터 10번째 위치까지의 내용을 String으로 추출하려고 합니다. 어떤 메소드를 사용해야 하나요?

Me: subString()

Q. 문자열의 모든 공백을 * 표시로 변환하려고 합니다. 어떤 메소드를 사용하는 것이 좋을까요?

Me: replaceAll()

Q. String의 단점을 보완하기 위한 두 개의 클래스는 무엇인가요?

Me: StringBuffer, StringBuilder

Q. 문제의 답에서 문자열을 더하기 위한 메소드의 이름은 무엇인가요?

Me: append()

질문

💡 책에 있는 내용이 아닙니다.

책을 읽으며 설명이 더 필요하거나, 추가로 궁금한 점에 대해 질문 형식으로 작성 후, 답을 구해보고 있습니다.
참고한 사이트나 영상은 [출처]로 달아두었으며, 오류 지적은 언제나 환영합니다.

Q. Constant Pool의 종류

String Constant Pool

String Constant Pool 또는 String Pool이라고 부른다.

JVM의 Perm/Metaspace 영역에 존재하는 영역으로, Java에서 문자열 리터럴을 저장하는 독립된 영역이다. 일반적으로 GC 대상이 되지 않는다.

문자열 참조 대상이 없는 경우 선택적으로 GC 대상이 되기도 한다.
-XX:+UseStringDeduplication옵션 사용을 통해 중복 문자열에 대해 GC를 수행하도록 트리거를 줄 수 있다.

String을 리터럴로 생성 시, 이 String Constant Pool에 같은 문자열이 저장되어 있다면 그 문자열을 재사용할 수 있다.

동적으로 생성되는 문자열의 경우에는 String constant pool에 저장되는 대상이 아니다.
ex) DB에서 조회한 String, File에서 읽어 들인 String 등

이런 동적인 문자열은 동일한 문자열이어도 동일연산(==)의 결과는 false이므로 문자열 비교는 동등연산(equals)을 통해 진행해야 한다.

Constant Pool (Class file)

컴파일 시 클래스 파일 내부에 존재하는 영역으로, 클래스 로더에 의해 JVM에 로드될 때 메모리에 로드된다.

주로 클래스의 구성요소(상수, 문자열, 클래스/인터페이스 참조) 데이터를 저장하고 있다.

Runtime Constant Pool (JVM)

상수 풀, Runtime Constant Pool이라고도 부르며 Java 8 이전에는 JVM - Perm영역에 저장되었고, Java8 출시 이후부터는 JVM - Metaspace 영역에 저장된다.

앞서 설명한 Class file constant pool이 클래스 로더에 의해 클래스를 로딩할 때 이 영역에 저장된다.

주로 클래스와 관련된 메타데이터를 저장하고 클래스 구조, 필드, 메서드와 같은 데이터를 저장한다.

Q. Concat 메소드는 기존 값을 수정하는가?

기존 값은 그대로 두고 새 배열에 복사해서 반환하고 있다. String은 불변이어서 그런 것 같다.

더하려는 값을 new String()으로 새로 만드므로 붙일 때마다 주소 값을 할당받는다.

Q. replace()는 기존 값을 수정하는가?

실제로 교체가 발생한 경우에만 새 문자열을 생성한다.

String.replaceAll(regex, replacement) 메소드를 사용하면 한 번의 호출로 교체를 수행 할 수 있다. 하지만, 내부적으로 많은 추가 GC(Garbage Collection) 대상 객체가 생성된다.

Q. String a + b + c에서 stringBuilder로 변환 횟수

아래는 변환 예제이다.

String a="test", b="test", c="test";
String str = a + b + c;
=> String str3 = (new StringBuilder()).append(a).append(b).append(c).toString();

String str4 = "";
str4 += 0;
=> str4 = (new StringBuilder()).append(str4).append(0).toString();

참고 사이트

String class가 final인 이유, String의 불변성 (Immutable)

[Java] 많이 헷갈려하는 String constant pool과 Runtime Constant pool, Class file constant pool

Java – Constant pool과 String pool

java string replace and memory

Baekjoon 9935 문자열 폭발 - 메모리 초과

String, StringBuffer, StringBuilder

CharSequence와 String의 차이 (feat. StringSpannableBuilder) - Dudmy's Devprint

profile
책을 읽거나 강의를 들으며 공부한 내용을 정리합니다. 가끔 개발하는데 있었던 이슈도 올립니다.

0개의 댓글