String 이란?
💡 Java에서 문자를 다루는 대표적인 타입은 char, String 2가지가 있다.
String은 문자열이라고 불리는 클래스이다.
⬇️ 하단 예제를 통해 간단하게 알아보려한다.
public class CharArrayMain {
public static void main(String[] args) {
// 기본형인 char은 문자를 하나씩 다룰 때 사용한다. char 배열이 들어오면, 연결해서 표현해준다.
char[] charArr = new char[]{'h', 'e', 'l', 'l', 'o'};
System.out.println(charArr);
String str = "hello";
System.out.println("str = " + str);
}
}
실행 결과
hello
str = hello
기본형인 char는 문자 하나를 다룰 때 사용한다.
char를 사용해서 여러 문자를 나열하려면 char[]을 사용해야 한다.
하지만 이렇게 char[]을 직접 다루는 방법은 매우 불편하기 때문에,
자바는 문자열을 매우 편리하게 다룰 수 있는 String 클래스를 제공한다.
생성 방법
💡 String 클래스를 통해 문자열을 생성하는 방법은 2가지가 있다.
public class StringBasicMain {
public static void main(String[] args) {
// Java에서 new String("hello")와 같이 변경해준다.
String str1 = "hello"; // 기존
String str2 = new String("hello"); // 편의 변경
System.out.println("str1 = " + str1);
System.out.println("str2 = " + str2);
}
}
실행 결과
hello
hello
➡️ 쌍따옴표 사용 : "hello";
➡️ 객체 생성 : new String("hello");
예제 설명
💡 String은 클래스다. int, boolean 같은 기본형이 아니라 참조형이다.
따라서 str1 변수에는 String 인스턴스의 참조값만 들어갈 수 있다. 따라서 다음 코드는 뭔가 어색하다.
String str1 = "hello";
문자열은 매우 자주 사용된다.
그래서 편의상 쌍따옴표로 문자열을 감싸면 Java에서 new String("hello")와 같이 변경해준다.
String str1 = "hello"; // 기존
String ste2 = new String("hello"); // 편의 변경
클래스 구조
public final class String {
//문자열 보관
private final char[] value;// 자바 9 이전
private final byte[] value;// 자바 9 이후
//여러 메서드
public String concat(String str) {...}
public int length() {...}
...
}
➡️ 클래스이므로 속성과 기능을 가진다.
구조 설명
private final char[] value;
String의 실제 문자열 값이 보관된다. 문자 데이터 자체는 char[]에 보관된다.
String 클래스는 개발자가 직접 다루기 불편한 char[]을 내부에 감추고,
String 클래스를 사용하는 개발자가 편리하게 문자열을 다룰 수 있도록 다양한 기능을 제공한다.
그리고 메서드 제공을 넘어서 Java 언어 차원에서도 여러 편의 문법을 제공한다.
⚠️ Java 9부터 String 클래스에서 char[] 대신에 byte[]을 사용한다.
자바에서 문자를 표현하는 char는 2byte를 차지한다.
영어, 숫자는 보통 1byte로 표현이 가능하다. 그래서 단순 영어, 숫자로만 된 경우 1byte를 사용하고
나머지의 경우 2byte를 사용한다. 따라서 메모리를 더 효율적으로 사용할 수 있게 변경되었다.
private final byte[] value;
기능 (메서드)
💡 String 클래스는 문자열로 처리할 수 있는 다양한 기능을 제공한다.
기능이 방대하므로, 추후 다음 포스팅에서 다룰 예정이다.
주요 메서드는 하단과 같다.
➡️ length() : 문자열의 길이를 반환한다.
➡️ charAt (int index) : 특정 인덱스의 문자를 반환한다.
➡️ substring (int beginIndex, int endIndex) : 문자열의 부분 문자열을 반환한다.
➡️ indexOf (String str) : 특정 문자열이 시작되는 인덱스를 반환한다.
➡️ toLowerCase(), toUpperCase() : 문자열을 소문자 또는 대문자로 변환한다.
➡️ trim() : 문자열 양 끝의 공백을 제거한다.
➡️ concat (String str) : 문자열을 더한다.
비교
💡 String 클래스 비교할 때는 == 비교가 아니라 항상 equals() 비교를 해야한다.
➡️ 동일성 (Identity) : == 연산자를 사용해서 두 객체의 참조가 동일한 객체를 가리키고 있는지
➡️ 동등성 (Equality) : equals() 메서드를 사용하여 두 객체가 논리적으로 같은지 확인
public class StringEqualsMain1 {
public static void main(String[] args) {
// new String()을 사용해서 각각 인스턴스를 생성.
String str1 = new String("hello"); // x001
String str2 = new String("hello"); // x002
System.out.println("new String() == 동일성 비교 : " + (str1 == str2)); // false
System.out.println("new String() equals 동등성 비교 : " + (str1.equals(str2))); // true
// 리터럴 문법의 경우, 성능 최적화를 위해 문자열 풀 (String Pool)을 사용한다.
String str3 = "hello";
String str4 = "hello";
System.out.println("리터럴 == 비교 : " + (str3 == str4)); // true
System.out.println("리터럴 equals 비교 : " + (str3.equals(str4))); // true
}
}
실행 결과
new String() == 비교: false
new String() equals 비교: true
리터럴 == 비교: true
리터럴 equals 비교: true
⬇️ 하단 그림을 통해 자세히 알아보려한다.
➡️ str1과 str2는 new String()을 사용해서 각각 인스턴스를 생성했다.
서로 다른 인스턴스이므로 동일성 (==) 비교에 실패한다.
➡️ 둘은 내부에같은 "hello" 값을 가지고 있기 때문에 논리적으로 같다.
따라서 동등성 (equals()) 비교에 성공한다.
➡️ String str3 = "hello"와 같이 문자열 리터럴을 사용하는 경우, Java는 메모리 효율성과 성능 최적화를 위해
문자열 풀을 사용한다.
➡️ Java가 실행되는 시점에 클래스에 문자열 리터럴이 있으면 문자열 풀에 String 인스턴스를 미리 만들어둔다.
이때 같은 문자열이 있으면 만들지 않는다.
➡️ String str3 = "hello"와 같이 문자열 리터럴을 사용하면 문자열 풀에서 "hello"라는 문자를 가진
String 인스턴스를 찾는다. 그리고 찾은 인스턴스의 참조 (x003)를 반환한다.
➡️ String str4 = "hello"의 경우 "hello" 문자열 리터럴을 사용하므로 문자열 풀에서 str3과 같은
x003 참조를 사용한다.
➡️ 문자열 풀 덕분에 같은 문자를 사용하는 경우 메모리 사용을 줄이고 문자를 만드는 시간도 줄어들기 때문에
성능도 최적화 할 수 있다.
💡 풀 (Pool) 이란?
자원이 모여있는 곳을 의미한다. 프로그래밍에서 풀은 공용 자원을 모아둔 곳을 뜻한다.
여러 곳에서 함께 사용할 수 있는 객체를 필요할 때 마다 생성하고, 제거하는 것은 비효율적이다.
대신에 이렇게 문자열 풀에 필요한 String 인스턴스를 미리 만들어두고 여러곳에서 재사용할 수 있다면
성능과 메모리를 더 최적화 할 수 있다.
참고로 문자열 풀은 힙 영역을 사용한다. 그리고 문자열 풀에서 문자를 찾을 때는 해시 알고리즘을
사용하기 때문에 매우 빠른 속도로 원하는 String 인스턴스를 찾을 수 있었다.
그렇다면 문자열 리터럴을 사용하면 == 비교를 하고, new String()을 직접 사용하는 경우에만
equals() 비교를 사용하면 되지 않을까? 하는 의문이 생기게 되었다.
⬇️ 하단 코드를 통해 알아보려한다.
public class StringEqualsMain2 {
public static void main(String[] args) {
// new String()을 사용해서 각각 인스턴스를 생성.
String str1 = new String("hello"); // x001
String str2 = new String("hello"); // x002
System.out.println("메서드 호출 비교 1 : " + isSame(str1, str2));
String str3 = "hello";
String str4 = "hello";
System.out.println("메서드 호출 비교 2 : " + isSame(str3, str4));
}
private static boolean isSame(String x, String y) {
// return x == y;
// 어떤 값이 들어올지 모르기때문에, 항상 동등성 비교를 사용한다.
return x.equals(y);
}
}
실행 결과
메서드 호출 비교1: false
메서드 호출 비교2: true
main() 메서드를 만드는 개발자와 isSame() 메서드를 만드는 개발자가 서로 다르다고 가정할 때,
isSame()의 경우 매개변수로 넘어오는 String 인스턴스가 new String()으로 만들어진 것인지,
문자열 리터럴로 만들어 진 것인지 확인할 수 있는 방법이 없다.
⚠️ 따라서 문자열 비교는 항상 equals()를 사용해서 동등성 비교를 해야한다.
불변 객체
💡 String은 불변 객체이다. 따라서 생성 이후에 절대로 내부의 문자열 값을 변경할 수 없다.
⬇️ 하단 예시를 통해 간단하게 알아보려 한다.
public class StringImmutable1 {
public static void main(String[] args) {
String str = "hello";
str.concat(" java");
System.out.println("str = " + str);
}
}
➡️ String.concat() 메서드를 사용하면 기존 문자열에 새로운 문자열을 연결해서 합칠 수 있다.
실행 결과
str = hello
실행 결과를 보면 원하던 결과가 출력되지 않았다.
public class StringImmutable2 {
public static void main(String[] args) {
String str1 = "hello";
String str2 = str1.concat(" java");
System.out.println("str1 = " + str1);
System.out.println("str2 = " + str2);
}
}
➡️ String은 불변 객체이다. 따라서 변경이 필요한 경우 기존 값을 변경하지 않고, 새로운 결과를 만들어서 반환한다.
실행 결과
str1 = hello
str2 = hello java
⬇️ 하단 그림을 통해 자세히 알아보려한다.
➡️ String.concat()은 내부에서 새로운 String 객체를 만들어서 반환한다.
➡️ 따라서 불변과 기존 객체의 값을 유지한다.
💡 String이 불변 객체로 설계된 이유는 다음과 같다.
문자열 풀에 있는 String 인스턴스의 값이 중간에 변경되면 같은 문자열을 참고하는
다른 변수의 값도 함께 변경된다.
➡️ String은 Java 내부에서 문자열 풀을 통해 최적화를 한다.
➡️ 만약 String 내부의 값을 변경할 수 있다면, 문자옆 풀에서 같은 문자를 참조하는 변수의 모든 문자가
함께 변경되는 문제가 발생한다.
다음의 경우 str3이 참조하는 문자를 변경하면 str4도 함께 변경되는 사이드 이펙트 문제가 발생한다.
String str3 = "hello"
String str4 = "hello"
⚠️ String 클래스는 불변으로 설계되어서 이러한 사이드 이펙트 문제가 발생하지 않는다.
OUTRO
오늘은 String에 대해서 알아봤습니다.
String에 기본적인 부분들에 대해 알아봤으니,
String Class의 다양한 기능들에 대해서 다룰 예정입니다.
그러면 다음 포스팅에서 뵙겠습니다.
위 포스팅 글은 김영한님의 실전 자바 - 중급 1편을 참고했습니다.
김영한의 실전 자바 - 중급 1편 | 김영한 - 인프런
김영한 | 실무에 필요한 자바의 다양한 중급 기능을 예제 코드로 깊이있게 학습합니다., [사진]국내 개발 분야 누적 수강생 1위, 제대로 만든 김영한의 실전 자바[사진][임베딩 영상]단순히 자바
www.inflearn.com
'Language > Java' 카테고리의 다른 글
[Java] 조건문 (if문, if-else, switch-case) (0) | 2024.04.16 |
---|---|
[Java] 불변 객체 (Immutable Object)란? (0) | 2024.04.08 |
[Java] 기본형과 참조형(Primitive Type & Reference Type) (0) | 2024.04.06 |
[Java] 클래스, 객체, 메서드, 생성자 (0) | 2024.03.30 |
[Java] 연산자 (Operator) (0) | 2024.03.29 |