String 클래스
자바에는 문자열을 다룰 수 있는 String 클래스를 제공한다. String 클래스를 통해 문자열을 생성하는 방법은 2가지가 있다.
- 쌍따옴표를 사용 : " "
- 객체를 생성 : new String(" ")
int, char같은 기본형은 소문자로 시작하지만 String은 대문자로 시작하는데 이는 참조형을 뜻한다. 참조형이라는 건 다른 말로 변수 하나에 String 인스턴의 참조값만 들어갈 수 있다는 말이다. 따라서 String 클래스를 사용할 때 객체를 생성해야 하지만 자바는 성능 최적화를 위해 문자열 풀을 사용하여 쌍따옴표(" ")만을 사용할 수 있게 했다.
기능(메서드)
String 클래스에는 속성과 기능을 가진다. 따라서 문자열로 처리할 수 있는 다양한 기능을 제공한다. 주요 메서드는 다음과 같다.
- length() : 문자열의 길이를 반환한다.
- charAt(int index) : 특정 인덱스의 문자를 반환한다.
- substring(int beginIndex, int endIndex) : 문자열의 부분 문자열을 반환한다.
- indexOf(String str) : 특정 문자열이 시작되는 인덱스를 반환한다.
- toLowerCase() , toUpperCase() : 문자열을 소문자 또는 대문자로 변환한다.
- trim() : 문자열 양 끝의 공백을 제거한다.
- concat(String str) : 문자열을 더한다.
※ String 클래스는 참조형이므로 원칙적으로 + 같은 연산을 사용할 수 없다. 하지만 문자열이 너무 자주 다루어지기 때문에 자바에서는 편의상 특별히 + 연산을 제공한다.
String str = "hello" + "java";
문자열 비교
String 클래스는 비교할 때 '==' 비교가 아니라 항상 equals() 비교를 해야 한다. 이는 동일성과 동등성의 차이에서 알 수 있다.
- 동일성(Identity) : == 연산자를 사용해서 두 객체의 참조가 동일한 객체를 가리키고 있는지 확인
- 동등성(Equality) : equals() 메서드를 사용하여 두 객체가 논리적으로 같은지 확인
아래 코드에서 동일성을 사용했을 때와 동등성을 사용했을 때의 차이를 알아보자.
String str1 = new String("hello"); //x001
String str2 = new String("hello"); //x002
str1과 str2 는 new String() 을 사용해서 각각 인스턴스를 생성했다. 서로 다른 인스턴스이므로 동일성( == ) 비교에 실패하므로 결과는 false이다. 그러나 둘은 내부에 같은 "hello" 값을 가지고 있기 때문에 논리적으로 같기 때문에 동등성(equals()) 비교에 성공하므로 결과는 true이다.
문자열 풀 사용
String str3 = "hello";
String str4 = "hello";
문자열 리터럴을 사용하는 경우 자바는 메모리 효율성과 성능 최적화를 위해 문자열 풀을 사용한다. 자바가 실행되는 시점에 클래스에 문자열 리터럴이 있으면 문자열 풀에 String 인스턴스를 미리 만들어둔다. 이때 같은 문자열이 있으면 만들지 않는다. 따라서 str3과 str4는 같은 주소값을 가지게 돼서 동일성과 동등성을 비교했을 때 모두 같은 결과인 true가 나온다.
※ 풀(Pool)은 자원이 모여있는 곳을 의미한다. 프로그래밍에서 풀(Pool)은 공용 자원을 모아둔 곳을 뜻한다.
🔎 하지만,
되도록이면 문자열 비교는 항상 equals()를 사용해서 동등성 비교를 하자.
String은 불변 객체
String은 불변 객체이다. 따라서 생성 이후에 절대로 내부의 문자열 값을 변경할 수 없다. 이 말은 concat()을 사용하여 문자열을 합칠 때 반환된 결과를 받아줘야 한다.
String str1 = "hello";
String str2 = str1.concat(" java");
만일 str2가 없으면 concat을 아무리 사용해도 결과는 hello일 뿐이다.
String 클래스는 변경이 필요한 경우 기존 값을 변경하지 않고, 대신에 새로운 결과를 만들어서 반환한다. 즉 str1과 str2는 각각 다른 주소값을 가지고 있다는 것. String 클래스가 불변으로 설계된 이유는 사이드 이펙트 문제 때문이다.
불변 객체의 단점
불변인 String 클래스의 단점은 문자를 더하거나 변경할 때 마다 계속해서 새로운 객체를 생성해야 한다는 점이다. 문자를 자주 더하거나 변경해야 하는 상황이라면 더 많은 String 객체를 만들고, GC해야 한다. 결과적으로 컴퓨터의 CPU, 메모리를 자원을 더 많이 사용하게 된다. 그리고 문자열의 크기가 클수록, 문자열을 더 자주 변경할수록 시스템의 자원을 더 많이 소모한다.
해결 방법 - StringBuilder
이 문제를 해결하는 방법은 불변이 아닌 가변 String 이 존재하면 된다. 가변은 내부의 값을 바로 변경하면 되기 때문에 새로운 객체를 생성할 필요가 없다. 따라서 성능과 메모리 사용면에서 불변보다 더 효율적이다. 이런 문제를 해결하기 위해 자바는 StringBuilder 라는 가변 String 을 제공한다. 물론 가변의 경우 사이드 이펙트에 주의해서 사용해야 한다.
※ StringBuilder는 보통 문자열을 변경하는 동안만 사용하다가 문자열 변경이 끝나면 아래와 같이 안전한(불변) String으로 변환하는 것이 좋다.
String string = sb.toString();
❗StringBuilder를 사용하지 않아도 될 경우가 존재
그러나 자바가 최적화를 처리해주기 때문에 지금처럼 간단한 경우에는 StringBuilder 를 사용하지 않아도 된다. 대신에 문자열 더하기 연산( + )을 사용하면 충분하다. 그렇다고 꼭 StringBuilder를 사용하지 말라는 건 아니다. 오히려 사용하는 것이 더 좋은 경우도 존재한다.
StringBuilder를 직접 사용하는 것이 더 좋은 경우
- 반복문에서 반복해서 문자를 연결할 때
- 조건문을 통해 동적으로 문자열을 조합할 때
- 복잡한 문자열의 특정 부분을 변경해야 할 때
- 매우 긴 대용량 문자열을 다룰 때
Method Chaining
마치 메서드가 체인으로 연결된 것처럼 메서드를 호출하는 것을 메서드 체이닝이라고 한다. 메서드가 끝나는 시점에 바로 .(dot) 을 찍어서 변수명을 생략할 수 있다. 메서드 체이닝이 가능한 이유는 자기 자신의 참조값을 반환하기 때문이다. 이 참조값에 . 을 찍어서 바로 자신의 메서드를 호출할 수 있다
ValueAdder adder = new ValueAdder();
int result = adder.add(1).add(2).add(3).getValue();
메서드 체이닝을 사용하지 않았을 때는 add 메서드의 호출과 반환값을 여러번 작성하여야 해서 불편하다.
ValueAdder adder = new ValueAdder();
ValueAdder adder1 = adder.add(1);
ValueAdder adder2 = adder.add(2);
ValueAdder adder3 = adder.add(3);
int result = adder3.getValue();
따라서 메서드 체이닝 기법을 사용하여 코드를 간결하고 읽기 쉽게 만들 수 있다. 이러한 장점을 가진 메서드 체이닝 기법을 제공하는 클래스가 있다. 바로 StringBuilder이다. StringBuilder 에서 문자열을 변경하는 대부분의 메서드(append(), insert(), delete(), reverse(), 등)는 메서드 체이닝 기법을 제공하기 위해 자기 자신을 반환한다.
'자바 > 자바 중급' 카테고리의 다른 글
[자바 중급] 06. 날짜와 시간 (1) | 2024.10.09 |
---|---|
[자바 중급] 05. 열거형 Enum (0) | 2024.10.08 |
[자바 중급] 04. Wrapper class (2) | 2024.09.30 |
[자바 중급] 02. 불변 객체 (0) | 2024.09.28 |
[자바 중급] 01. Object 클래스 (1) | 2024.09.28 |