🙄String 클래스 - 기본

  • ✔️String
    • String은 클래스다. int, boolean과 같은 기본형이 아니라 참조형이다.
    • new 키워드를 통한 객체 생성보다 성능 최적화를 위해 문자열 풀을 사용하는 것이 좋다.

✔️속성(필드)

private final char[] value;

여기에는 String의 실제 문자열 값이 보관된다. 문자 데이터 자체는 char[]에 보관된다.
String클래스는 개발자가 직접 다루기 불편한 char[]을 내부에 감추어 String클래스를 사용하는 개발자가 편리하게 문자열을 다룰 수 있도록 다양한 기능을 제공한다. 그리고 메서드 제공을 넘어서 자바 언어 차원에서도 여러 편의 문법을 제공한다.

❗참고: 자바 9 이후 String 클래스 변경 사항

private final byte[] byte;

자바에서 문자 하나를 표현하는 char는 2byte를 차지한다. 그런데 영어, 숫자는 보통 1byte로 표현이 가능하다. 그래서 단순 영어, 숫자로만 표현된 경우 1byte를 사용하고 그렇지 않은 나머지의 경우 2byte인 UTF-16 인코딩을 사용한다. 따라서 메모리를 더 효율적으로 사용할 수 있게 변경되었다.

✔️기능(메서드)
String클래스는 문자열로 처리할 수 있는 다양한 기능을 제공한다. 기능이 방대하므로 필요한 기능이 있다면 API문서를 참고할 수 있도록....

🙄String 클래스 - 비교

public class StringEqualMainV1 {
	public static void main(String[] args) {
		String str1 = new String("Hello!");
		String str2 = new String("Hello!");
		System.out.println("str1 == str2 → " + (str1 == str2));
		System.out.println("str1.equals(str2) → " + str1.equals(str2));
	}
}

실행 결과

str1 == str2 → false
str1.equals(str2) → true

  • ✔️정리
    • str1str2new String()을 사용해 각각 인스턴스를 생성했다. 서로 다른 인스턴스이기에 == 비교에 실패한다.
    • 둘은 내부에 같은 Hello!을 가지고 있기 때문에 논리적으로 같다. 따라서 동등성(equals()) 비교에 성공한다. 참고로 String클래스는 내부 문자열 값을 비교하도록 equals() 메서드를 재정의 해두었다.

  • ✔️문자열 풀(String Pool)
    • 문자열 리터럴을 사용하는 경우 자바는 메모리 효율성과 성능 최적화를 위해 문자열 풀을 사용한다.
    • 자바가 실행되는 시점에 클래스에 문자열 리터럴이 있으면 문자열 풀에 String 인스턴스를 미리 만들어둔다. 이 때, 같은 문자열이 있으면 만들지 않는다.
    • String str3 = "hello"와 같이 문자열 리터럴을 사용하면 문자열 풀에서 "hello"라는 문자를 가진 String 인스턴스를 찾는다. 그리고 찾은 인스턴스의 참조를 반환한다.
    • String str4 = "hello"의 경우 "hello" 문자열 리터럴을 사용하므로 문자열 풀에서 str3과 같은 참조를 사용한다.
    • 문자열 풀 덕분에 같은 문자를 사용하는 경우 메모리 사용을 줄이고 문자를 만드는 시간도 줄어들기 때문에 성능도 최적화 할 수 있다.
    • 따라서 문자열 리터럴을 사용하는 경우 == 비교를 할 때 같은 참조값을 가지므로 true를 반환하게 된다.

❗참고: 풀(Pool)
풀(Pool)은 자원이 모여있는 곳을 의미한다. 프로그래밍에서 풀(Pool)은 공용 자원을 모아둔 곳을 뜻한다. 여러 곳에서 함께 사용할 수 있는 객체를 필요할 때마다 생성하고, 제거하는 것은 비효율적이다. 대신에 이렇게 문자열 풀에 필요한 String 인스턴스를 미리 만들어두고 여러 곳에서 재사용할 수 있다면 성능과 메모리를 더 최적화 할 수 있다.
참고로 문자열 풀은 힙 영역을 사용한다. 그리고 문자열 풀에서 문자를 찾을 때는 해시 알고리즘을 사용하기 때문에 매우 빠른 속도로 String 인스턴스를 찾을 수 았다.

🙄String 클래스 - 불변 객체

String은 불변 객체이다. 따라서 생성 이후에 절대로 내부의 문자열 값을 변경할 수 없다.

🙄StringBuilder - 가변 String

불변인 String 클래스에도 단점이 있다. 불변인 String의 내부 값은 변경할 수 없다. 따라서 변경된 값을 기반으로 새로운 String 객체를 생성한다.

불변인 String 클래스의 단점은 문자를 더하거나 변경할 때마다 계속해서 새로운 객체를 생성해야 한다는 점이다. 문자를 자주 더하거나 변경해야 하는 상황이라면 더 많은 String 객체를 만들고, GC해야 한다. 결과적으로 컴퓨터의 CPU, 메모리, 자원을 더 많이 사용하게 된다. 그리고 문자열의 크기가 클수록, 문자열을 더 자주 변경할수록 시스템의 자원을 더 많이 소모한다.

이런 문제를 해결하기 위해 바로 불변이 아닌 가변 String이 존재하면 된다. 가변은 내부의 값을 변경하면 되기 때문에 새로운 객체 생성이 필요 없다. 따라서 성능과 메모리 사용면에서 불변보다 더 효율적이다. 자바는 StringBuilder라는 가변 String을 제공한다. 물론 가변의 경우 사이드 이펙트라는 점에 주의해서 사용해야 한다. StringBuilder는 내부에 final이 아닌 변경할 수 있는 byte[]을 가지고 있다.

public class StringBuilderMain1 {
	public static void main(String[] args) {
		StringBuilder sb = new StringBuilder();
		sb.append("A");
		sb.append("B");
		sb.append("C");
		sb.append("D");
		System.out.println("sb = " + sb);

		sb.insert(4, "Java");
		System.out.println("sb = " + sb);

		sb.delete(4, 8);
		System.out.println("sb = " + sb);

		sb.reverse();
		System.out.println("sb = " + sb);

		String string = sb.toString();
		System.out.println("string = " + string);
	}
}
  • ✔️가변(Mutable) vs 불변(Immutable)
    • String은 불변하다. 즉, 한 번 생성되면 그 내용을 변경할 수 없다. 따라서 문자열에 변화를 주려고 할 때마다 새로운 String 객체가 생성되고, 기존 객체는 버려진다. 이 과정에서 메모리의 처리 시간을 더 많이 소모한다.
    • 반면에 StringBuilder는 가변적이다. 하나의 StringBuilder 객체 안에서 문자열을 추가, 삭제, 수정할 수 있으며, 이 때마다 새로운 객체를 생성하지 않는다. 이로 인해 메모리 사용을 줄이고 성능을 향상시킬 수 있다. 단 사이드 이펙트를 주의해야 한다.
    • StringBuilder는 보통 문자열을 변경하는 동안만 사용하다가 문자열 변경이 끝나면 안전한 불변 String으로 변환하는 것이 좋다.

❗참고: StringBuilder vs StringBuffer
StringBuilder와 똑같은 기능을 수행하는 StringBuffer 클래스가 있다. StringBuffer 클래스는 내부에 동기화가 되어 있어 멀티 쓰레드 상황에서는 안전하지만 동기화 오버헤드로 인해 성능이 느리다. StringBuilder는 멀티 쓰레드 상황에서 안전하지 않으나 오버헤드가 없으므로 속도가 빠르다. StringBuffer와 동기화에 관한 내용은 멀티 쓰레드를 학습하면 이해할 수 있다.

🙄메서드 체이닝(Method Chaining)

public class ValueAdder {
	private int value;

	public ValueAdder(int value) {
		this.value = value;
	}

	public int getValue() {
		return value;
	}

	public ValueAdder add(int value) {
		this.value += value;
		return this;
	}
}
public class MethodChainingMain {
	public static void main(String[] args) {
		ValueAdder valueAdder = new ValueAdder(3);

		ValueAdder result = valueAdder.add(1).add(2).add(3);
		System.out.println(result.getValue());
	}
}

메서드 호출의 결과로 자기 자신의 참조값을 반환하면 반환된 참조값을 사용해서 메서드 호출을 계속 이어갈 수 있다. 코드를 보면 메서드를 계속 연결해서 사용하는 것을 볼 수 있다. 이러한 기법을 메서드 체이닝이라 한다.

public class StringBuilderMain2 {
	public static void main(String[] args) {
		StringBuilder sb = new StringBuilder();
		sb.append("A").append("B").append("C").append("D");

		String string = sb.toString();
		System.out.println(string);
	}
}

0개의 댓글

Powered by GraphCDN, the GraphQL CDN