[Java] String 클래스

artp·2025년 2월 24일
0

java

목록 보기
17/31
post-thumbnail

java.lang.String

자바에서는 문자열을 위한 String이라는 클래스를 별도로 제공합니다.

String 클래스에는 문자열과 관련된 작업을 할 때 유용하게 사용할 수 있는 다양한 메서드가 포함되어 있습니다. 이러한 String 클래스는 java.lang 패키지에 포함되어 제공됩니다.

String 인스턴스는 한 번 생성되면 그 값을 읽기만 할 수 있고, 변경할 수는 없습니다. 이러한 객체를 자바에서는 불변 객체(immutable object)라고 합니다. 예를 들어, 자바에서 덧셈(+) 연산자를 이용하여 문자열 결합을 수행하면, 기존 문자열의 내용이 변경되는 것이 아니라 내용이 합쳐진 새로운 String 인스턴스가 생성되는 것입니다.

String 클래스

자바에서는 문자열을 다룰 때 String 클래스를 사용합니다.
자바의 String 클래스는 불변 객체(Immutable Object)이며, String Pool을 이용한 최적화 기능을 제공합니다.

가변 객체(Mutable Object)와 Side Effect

가변 객체(Mutable Object)

자바에서 객체의 상태 변경 가능 여부에 따라 객체를 가변 객체(Mutable Object)불변 객체(Immutable Object)로 나눌 수 있습니다.

  • 객체 내부의 상태(필드 값)을 변경할 수 있는 객체
  • setter 메서드를 통해 값 변경 가능
  • 동일한 객체를 여러 변수가 공유하면 예기치 않은 변경(Side Effect)이 발생할 수 있음

Side Effect(부작용)

Side Effect(부작용)란 주된 작업 외에 추가적인 부수적 효과를 일으키는 것으로, 프로그램의 특정 부분에서 발생한 변경이 의도치 않게 다른 부분에 영향을 미치는 경우를 의미합니다.
쉽게 말하면, 객체를 공유하는 여러 변수 중 하나가 값을 변경하면, 다른 변수에도 영향을 미치는 현상입니다.
CopyByReference(참조에 의한 복사) 때문에 발생하며, 예상치 못한 버그를 유발할 가능성이 있습니다.

  • CopyByReference(참조에 의한 복사)
    메서드에서 A 인스턴스의 주소값을 B 인스턴스에 복사하고 나서 A의 인스턴스 변수값만 변경해도 B의 인스턴스 변수값이 같이 변경됩니다.

예제: 가변 객체의 Side Effect

class Person {
    String name;
    
    Person(String name) {
        this.name = name;
    }
    
    void setName(String name) {
        this.name = name;
    }
}

public class MutableExample {
    public static void main(String[] args) {
        Person p1 = new Person("홍길동");
        Person p2 = p1;  // p2는 p1과 같은 객체를 참조 (Copy by Reference)

        p2.setName("안중근");  // p2에서 이름을 변경

        System.out.println(p1.name);  // "안중근" (p1도 영향을 받음)
        System.out.println(p2.name);  // "안중근"
    }
}
  • p1과 p2가 동일한 객체를 참조하므로, p2에서 setName("안중근")을 호출하면 p1.name도 "안중근"으로 변경됩니다.

불변 객체(Immutable Object)

불변 객체란 객체 내부의 값, 필드, 멤버 변수가 변하지 않는 객체를 의미합니다. 클래스 작성 시 인스턴스 변수 선언에 final 예약어를 붙여주고, setter를 작성하지 않는 방법으로 불변 객체를 생성합니다.

  • 객체가 생성된 후 내부 상태(필드 값)을 변경할 수 없는 객체
  • Side Effect를 방지할 수 있음
  • final 키워드를 사용하여 필드를 변경할 수 없도록 함
  • setter 메서드를 만들지 않음

String 클래스는 불변 객체로 설계되어 있습니다.

예제: 불변 객체

final class ImmutablePerson {
    private final String name;  // 필드를 final로 선언

    public ImmutablePerson(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
}
  • name 필드는 final 이므로 한 번 설정되면 변경할 수 없습니다.
  • setter 메서드가 없어서 객체가 한 번 생성되면 상태가 바뀌지 않습니다.

예제: 불변 객체를 사용하여 Side Effect 방지

public class ImmutableExample {
	public static void main(String[] args) {
    	ImmutablePerson p1 = new ImmutablePerson("홍길동");
        // p1.setName("안중근"); // 불가능 (setter 없음)
    }
}
  • 불변 객체가 생성된 후에는 내부 상태가 변경되지 않습니다.

String Pool

자바에서 String은 특별한 메모리 관리 기법을 사용하는데, 이를 "String Pool” 이라고 합니다.
이는 자주 사용되는 문자열을 공유하여 메모리를 절약하고 성능을 향상시키는 역할을 합니다.

  • JVM의 Method Area(메서드 영역)에 존재하는 특수한 메모리 영역
  • 동일한 문자열 리터럴이 중복 저장되지 않고 공유됨
  • String 리터럴("hello", "java" 등)이 String Pool에 저장되며, 같은 값이 다시 사용될 경우 기존 객체를 재사용함
  • new String("hello")를 사용하면 String Pool을 사용하지 않고, Heap에 새로운 객체를 생성

자바의 String은 특별한 클래스로, 문자열 리터럴이 동일하면 같은 객체를 참조하도록 동작합니다.
하지만 new String("hello")로 생성하면 항상 새로운 객체가 생성됩니다.

String Pool의 동작 원리

문자열 리터럴(String Pool 사용)

String s1 = "hello";
String s2 = "hello";

System.out.println(s1 == 2); // true
  • "hello"라는 문자열이 처음 등장하면, String Pool에 저장됨
  • s1은 "hello"를 참조
  • s2도 "hello"를 참조하여, 새로운 객체가 저장되지 않고 기존 객체를 공유
  • s1 == s2 → 같은 객체를 참조하므로 true

new String()을 사용하면 Heap에 새로운 객체가 생성됨

String s3 = new String("hello");
String s4 = new String("hello");

Systme.out.println(s3 == s4); // false
  • new String("hello")를 사용하면 Heap 메모리에 새로운 String 객체가 생성됨
  • s3와 s4는 각각 다른 Heap 객체를 참조하므로 s3 == s4는 false

intern() 메서드를 사용하여 String Pool에 추가

intern() 메서드를 사용하면 Heap에 생성된 String 객체를 String Pool에 추가할 수 있습니다.

String s5 = new String("world").intern();
String s6 = "world";

System.out.println(s5 == s6); // true
  • new String("world") → Heap에 새로운 String 객체 생성
  • intern() 호출 → "world"가 String Pool에 있는지 확인 후, 존재하면 기존 객체를 반환하고, 없으면 String Pool에 추가
  • s6 = "world" → "world"는 이미 String Pool에 있으므로 기존 객체를 참조
  • s5 == s6 → 둘 다 String Pool의 "world"를 참조하므로 true

String Pool의 사용 이유

1. 메모리 절약

  • 동일한 문자열을 여러 번 생성하지 않고 공유하여 메모리 사용량을 줄일 수 있습니다.

2. 성능 향상

  • 같은 문자열을 여러 번 생성할 필요가 없어 객체 생성 비용이 줄어듭니다.

3. 불변성으로 안전한 공유 가능

  • String이 불변이므로 여러 참조 변수에서 공유해도 안전합니다.

String Pool의 위치

String Pool은 예전에는 Method Area(메서드 영역)에 있었지만, 현재는 Heap에 포함됩니다.

  • 예전에는 Method Area에 String Pool이 있었음 (JDK 6까지)
  • Method Area는 클래스, 메서드 정보, 상수 풀(Constant Pool) 등을 저장하는 영역임
  • JDK 6까지는 String Pool도 Method Area의 일부로 관리되었음
  • 하지만, Method Area의 크기가 제한적이어서 메모리 부족 문제가 발생할 수 있었음

JDK 7부터는 String Pool이 Heap 영역으로 이동했습니다.

  • JDK 7부터 String Pool은 Heap의 일부로 포함됨
  • 메모리 관리가 유연해졌고, 가비지 컬렉션(GC)으로 관리 가능해짐
  • String Pool이 Heap에 위치하면서 메모리 부족 문제를 줄일 수 있음

String Pool이 Heap으로 이동한 이유는 다음과 같습니다.

  • Method Area는 크기가 제한적이지만, Heap은 동적으로 크기를 조절할 수 있음
  • String이 너무 많이 생성되면 Method Area의 String Pool이 가득 차서 OutOfMemoryError 발생 가능
  • Heap에 String Pool을 두면 GC(가비지 컬렉션)로 불필요한 문자열을 정리할 수 있음

String 비교: == vs equals()

자바에서는 문자열을 비교할 때 ==equals()를 사용합니다.

1. == 연산자 (참조 비교)

==는 문자열의 “내용”이 아니라, 객체의 “참조값(메모리 주소)“을 비교합니다.

두 개의 문자열이 "같은 객체"인지 비교합니다. 메모리 주소(참조값)를 비교하여, 동일한 객체일 경우 true를 반환합니다.

public class StringComparison {
	public static void main(String[] args) {
    	String s1 = "hello";
        String s2 = "hello";
        String s3 = new String("hello");
        
        System.out.println(s1 == s2); // true (String Pool에서 공유)
        System.out.println(s1 == s3); // false (s3는 Heap에서 생성된 객체)
    }
}
  • "hello" 리터럴이 처음 등장하면 String Pool에 저장됨
  • s1과 s2는 String Pool에서 같은 객체를 참조하므로 s1 == s2는 true
  • s3 = new String("hello")를 사용하면 Heap에 새로운 객체가 생성되므로 s1 == s3는 false

2. equals() 메서드 (내용 비교)

equals()문자열 값(내용)을 비교할 때 사용합니다.

문자열의 "내용"을 비교하여 같으면 true를 반환합니다. 문자열이 동일한 값이면 true, 다르면 false를 반환합니다.

public class StringComparison {
	public static void main(String[] args) {
    	String s1 = "hello";
        String s2 = new String("hello");
        
        System.out.println(s1.equals(s2)); // true (문자열 내용이 같음)
    }
}
  • s1과 s2는 서로 다른 객체이지만, "hello"라는 동일한 값을 가짐
  • equals()문자열의 내용을 비교하므로, s1.equals(s2)는 true

3. == vs equals() 차이점 정리 표

비교 방식설명예제 결과
== (참조 비교)두 객체가 같은 메모리 주소를 가리키는지 확인"hello" == "hello" → true (String Pool 공유)
equals() (내용 비교)문자열의 "값"을 비교"hello".equals(new String("hello")) → true

StringBuilder와 StringBuffer

String은 불변 객체이므로 문자열을 자주 변경하면 새로운 객체가 계속 생성되어 비효율적입니다.
자바에서 문자열을 자주 변경해야 하는 경우, String 대신 StringBuilder 또는 StringBuffer를 사용하는 것이 효율적입니다.

String vs StringBuilder vs StringBuffer 차이점

클래스변경 가능 여부스레드 안전(Thread-Safe)성능
String불변안전느림 (새 객체 생성)
StringBuilder가변비안전빠름
StringBuffer가변안전느림 (synchronized 사용)
  • StringBuilder → 가장 빠르고 효율적
  • StringBuffer → 멀티스레드 환경에서 안전하게 조작 가능
  • String → 변경이 적고, equals() 비교가 필요한 경우 사용

String (불변 객체, 변경할 수 없음)

String은 변경할 수 없고, 새로운 객체가 계속 생성됩니다.

public class StringExample {
	public static void main(String[] args) {
    	String str = "Hello"; // String Pool에 저장됨
        Str += " World"; // 새로운 문자열 객체 생성됨 (Heap)
        
        System.out.println(str); // "Hello World"
    }
}
  • "Hello"라는 String 객체가 String Pool에 저장됨
  • "Hello World"를 만들기 위해 새로운 String 객체가 생성됨
  • str이 새로운 객체를 가리키고, 기존 "Hello"는 더 이상 참조되지 않음 → 가비지 컬렉션 대상

    즉, String은 한 번 생성되면 내부 값을 변경할 수 없고, 새로운 객체를 생성해야 합니다. 문자열을 자주 변경하는 경우 String은 성능이 느려질 수 있습니다.

StringBuilder (가변 객체, 변경 가능)

StringBuilder는 기존 객체를 수정하기 때문에 String보다 성능이 좋습니다.

public class StringBuilderExample {
	public static void main(String[] args) {
    	StringBuilder sb = new StringBuilder("Hello");
        sb.append(" World"); // 기존 객체에 " World" 추가
        
        System.out.println(sb); // "Hello World"
    }
}
  • "Hello"라는 StringBuilder 객체가 Heap에 저장됨
  • .append("World")를 호출하면 기존 객체를 직접 수정하여 "Hello World"가 됨
  • 새로운 객체가 생성되지 않으므로 String보다 성능이 좋음

    즉, StringBuilder는 문자열을 여러 번 수정할 때 효율적입니다. 단일 스레드 환경에서 가장 빠르게 문자열을 조작할 수 있습니다.

StringBuffer (멀티스레드 환경에서 안전)

StringBuffer는 synchronized(동기화)를 사용하여 멀티스레드 환경에서도 안전하게 문자열을 수정할 수 있습니다.

public class StringBufferExample {
	public static void main(String[] args) {
    	StringBuffer sb = new StringBuffer("Hello");
        sb.append(" World"); // 동기화된 메서드 (멀티스레드 환경에서도 안전)
        
        System.out.println(sb); // "Hello World"
    }
}
  • "Hello"라는 StringBuffer 객체가 Heap에 저장됨
  • .append(" World")를 호출하면 기존 객체를 직접 수정하여 "Hello World"가 됨
  • 하지만, StringBuffer는 synchronized(동기화)가 적용되어 있기 때문에 여러 스레드에서 동시에 접근해도 안전함

    즉, StringBuffer는 멀티스레드 환경에서도 안전하지만, 동기화 때문에 StringBuilder보다 속도가 느립니다.

StringBuffer와 스레드 안전(Thread-Safe)

스레드 안전이란 멀티스레드 환경에서 동시에 실행할 때 데이터의 일관성이 보장되는 것입니다.

  • 여러 개의 스레드(작업 단위)가 동시에 같은 객체에 접근할 때, 문제가 발생하지 않도록 하는 것
  • 동기화(Synchronization)을 사용하여 여러 스레드가 안전하게 접근하도록 보장함

스레드가 안전하지 않으면

  • 여러 스레드가 동시에 같은 변수 값을 변경하면 데이터 충돌이 발생할 수 있음
  • 데이터가 예상과 다르게 변경되거나, 프로그램이 비정상적으로 동작할 위험이 있음

StringBuffer는 동기화(synchronized)를 사용하여 멀티스레드 환경에서도 안전하게 사용 가능하지만, StringBuilder는 단일 스레드에서만 안전하며, 멀티스레드 환경에서는 충돌이 발생할 수 있습니다.

StringBuffer의 동기화 메서드 예제

StringBuffer는 synchronized 키워드를 사용하여 동기화를 적용합니다.
한 번에 하나의 스레드만 StringBuffer 메서드를 실행할 수 있도록 제한합니다.

public class StringBufferExample {
    public static void main(String[] args) {
        StringBuffer sb = new StringBuffer("Hello");

        sb.append(" World"); // 동기화된 메서드 (스레드 안전)
        System.out.println(sb); // "Hello World"
    }
}
  • StringBuffer의 append(), insert(), delete() 같은 메서드는 내부적으로 synchronized 처리되어 있음
  • 멀티스레드 환경에서도 자동으로 동기화되어 안전하게 실행됨
  • synchronized따로 명시하지 않아도 스레드 안전(Thread-Safe)이 보장됨
  • 따라서, 여러 스레드가 동시에 StringBuffer를 수정해도 데이터 충돌이 발생하지 않음
profile
donggyun_ee

0개의 댓글