자바

[ Java ] String은 기본타입과는 달라요~

do_it_zero 2024. 10. 24. 17:10

자바에서 문자를 다루는 대표적인 타입은 char , String 2가지가 있다.

기본형인 char는 문자 하나를 다룰 때 사용한다.

char를 사용해서 여러 문자를 나열하려면 char[ ]을 사용해야 한다.

하지만 char[ ] 을 직접 다루는 방법은 매우 불편하기 때문에 자바는 문자열을 매우 편리하게 다룰 수 있는 String 클래스를 제공한다.

String을 통해 문자열을 생성하는 2가지 방법

String srt1 = "hello";
String str2 = new String("hello");

 

String은 클래스이다.

기본형이 아니라 참조형이라는 것이다.

str1의 변수에는 인스턴스의 참조값만 들어갈 수 있는데, 문자열은 매우 자주 사용됨으로 편의상 쌍따옴표로 문자열을 감싸면 자바 언어에서 "hello"를 new String("hello")로 변경해준다.

String 클래스 구조

String도 클래스이기 때문에 필드와 메서드를 갖는다.

  • 필드 : private final byte[ ] value;
  • 메서드 : length, charAt,trim 등

String 클래스와 참조형

String 클래스는 기본형이 아니라 참조형이다.

참조형은 변수에 계산할 수 있는 값이 들어있는 것이 아니라 x001과 같이 계산할 수 없는 참조값이 들어있다.

따라서 원칙적으로 + 같은 연산을 할 수 없다.

하지만 문자열은 너무 자주 다루어지기 때문에 자바 언어에서는 편의상 특별히 + 연산을 제공한다.

String a = "hello";
String b = " java";

System.out.println(a+b); // hello java

String 클래스 - 비교

String 클래스를 비교할 때는 == 비교가 아니라 항상 equals() 비교를 해야한다. 동등성비교!

 

String str1 = new String("hello"); // x001
String str2 = new String("hello") // x002

str1 == str2 // false 왜냐하면 참조 값이 다르기 때문 (동일성 비교) 
str1.equals(str2); // true (동등성 비교)

String str3 = "hello"; // x003
String str4 = "hello"; // x003
str3 == str4 // true 문자열 풀에 같은 인스턴스 참조값을 가리키므로 참이다.
str3.equals(str4) // true

 

String str3 = "hello"; 와 같이 문자열 리터럴을 사용하는 경우

자바는 메모리 효율성과 성능 최적화를 위해 문자열 풀을 사용한다.

자바가 실행되는 시점에 클래스에 문자열 리터럴이 있으면 문자열 풀에 String 인스턴스를 미리 만들어둔다.

이 때 같은 문자열이 있으면 새로운 인스턴스를 만들지 않는다.


String str3 = "hello"와 같이 문자열 리터럴을 사용하면 문자열 풀에서 "hello"라는 문자를 가진 String 인스턴스를 찾는다. 그리고 인스턴스의 참조값을 반환한다.

String str4 = "hello";의 경우 "hello" 문자열 리터럴을 사용하므로 문자열 풀에서 str3와 같은 x003 참조값을 사용한다.

문자열 풀 덕분에 같은 문자를 사용하는 경우 메모리 사용을 줄이고 문자를 만드는 시간도 줄어들기 때문에 성능도 최적화 할 수 있다.

문자열 풀이 있는 이유

풀은 자원이 모여있는 곳을 의미한다.

프로그래밍에서 풀은 공용 자원을 모아둔 곳을 뜻한다.

여러 곳에서 함께 사용할 수 있는 객체를 필요할 때 마다 생성하고, 제거하는 것은 비효율적이다.

대신 문자열 풀에 필요한 String 인스턴스를 미리 만들어두고 여러곳에서 재사용할 수 있다면 성능과 메모리를 더 최적화 할 수 있는 것이다.

 

참고로 문자열 풀은 힙 영역을 사용하며 문자열 풀에서 문자를 찾을 떄는 해시 알고리즘을 사용하기 때문에 매우 빠른 속도로 원하는 String 인스턴스를 찾을 수 있다.

그럼 왜 문자열 비교시 ==동일성 비교가 아니라 equals동등성 비교를 하는게 좋은것일까??

왜냐하면 리터럴로 만든 경우가 있을 수 있고 new String() 생성자로 만드는 경우가 있을 수 있기 때문이다.

 

동일성 비교로 두 String 인스턴스를 비교하는 메서드가 있다하면

private static boolean isSame(String a, Stirng b){
	return a==b;
}

isSame(str1,str2) // false
isSame(str3,str3) // true

이렇게 될 수 있으므로 따라서 Sring 인스턴스 비교시에는 항상 equals를 사용해야한다.

String 객체는 불변 객체

String 클래스를 보면 필드가 private final byte[ ] value;로 되어 있음을 알 수 있다.

즉, String 인스턴스가 생성되면 불변 객체로 생성됨을 알 수 있다.

 

그렇다면 왜 String 인스턴스를 불변 객체로 만든걸까?

만약 String 인스턴스가 불변객체가 아니라면 문자열 풀에 있는 인스턴스 값이 중간에 바뀔 경우 같은 문자열을 참고하는 다른 변수의 값도 함께 변경될 수 있기 때문이다.

즉, 의도치 않은 사이드 이펙트가 발생할 수 있기 때문이다.

따라서 String 인스턴스를 불변 객체로 만들어 추후 사이드 이펙트가 발생하지 않도록 만든 것이다.

StringBuilder 가변 String

String 객체가 불변인 것이기 사이드 이펙트를 막을 수 있는 장점이 있지만, 문자들을 더 해야만 하는 경우 단점이 될 수도 있다.

왜냐하면 불변인 String 객체는 내부 값을 변경할 수 없기 때문이다.

따라서 변경된 값을 기반으로 새로운 String 객체를 생성한다.

객체를 생성할 때마다 자원을 낭비하게 되는 것이다.

 

이 문제를 해결하기 위해 불변이 아닌 가변 String 객체를 생성할 수 있는 StringBuilder 클래스를 쓰면 된다.

물론 가변의 경우 사이드 이펙트에 주의해서 사용해야 한다.

그래서 StringBuilder는 보통 문자열을 변경하는 동안만 사용하다가 문자열 변경이 끝나면 안전한(불변) String으로 변환하는 것이 좋다.