Notice
Recent Posts
Recent Comments
Link
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Archives
Today
Total
관리 메뉴

봄디의 개발일지

[JAVA] String이란? (String이 불변 객체인 이유 / String 메소드 정리) 본문

자바

[JAVA] String이란? (String이 불변 객체인 이유 / String 메소드 정리)

bomdy 2024. 9. 6. 01:01

1️⃣ String  사용법

public class StringBasicMain {

    public static void main(String[] args) {
        String str1 = "hello";//기존
        String str2 = new String("hello"); //객체 생성 가능(자바에서 이렇게 변경)

        System.out.println("str1 = " + str1);
        System.out.println("str2 = " + str2);
    }
}

 

String 을 사용하는 방법은 크게 두 가지가 있습니다. 

  • 쌍따옴표 사용: "hello" 
  • 객체 생성: new String("hello")

2️⃣ String 클래스 - 비교

두 방법의 가장 큰 차이점은 동일성과 동등성 비교에 있습니다

  • 동일성(Identity) : == 연산자를 사용해서 두 객체의 참조가 동일한 객체를 가리키고 있는지 확인하는 것
  • 동등성(Equaltiy): equals() 메서드를 사용해서 두 객체가 논리적으로 같은지 확인하는 것
public class StringEqualsMain1 {

    public static void main(String[] args) {
        String str1 = new String("hello"); //x001
        String str2 = new String("hello"); //x002
        System.out.println("new String() == 비교: " + (str1 == str2));
        System.out.println("new String() equals 비교: " + (str1.equals(str2)));

        String str3 = "hello";
        String str4 = "hello";
        System.out.println("리터럴 == 비교: " +(str3 == str4));
        System.out.println("리터럴 equals 비교: " + (str3.equals(str4)));
    }
}

String의 동일성과 동등성 비교 (실행화면)

 

 위와 같은 실행 결과가 나온 이유는 무엇일까요 ?

string 객체 생성 참조 그림

위의 코드에서 str1 과 str2는 객체 생성을 통해 "hello" 라는 문자열을 만들었으므로 서로 다른 인스턴스를 참조합니다. 

따라서 == 비교(동일성 비교)를 하면 false 가 나오고,equals() (동등성 비교) 는 논리적으로 인스턴스 안의 값이 같은 지를 확인하므로 true가 나옵니다.

 

string 문자열 풀 참조 그림

 

반면에, str3과 str4 는 쌍따옴표를 사용해서 "hello" 와 같이 문자열 리터럴을 사용해서 생성한 경우에는, 자바는 메모리 효율성과 성능 최적화를 위해 문자열 풀을 사용합니다. 

자바가 실행되는 시점에 클래스에 문자열 리터럴이 있으면 문자열 풀에 String 인스턴스를 미리 생성해둡니다. 같은 문자열이 이미 있는 경우에는 만들지 않습니다. 

str4의 경우 "hello" 라는 문자열 리터럴을 사용하는데 이미 문자열 풀에 해당 문자열이 있으므로 str3와 같은 인스턴스를 참조하게 됩니다. 따라서 str3와 str4는 같은 인스턴스를 참조하게 되어 ==(동일성 비교) 을 하여도 값이 true로 나오게 되는 것 입니다.  

문자열 풀을 사용하여 메모리 사용을 줄이고 문자를 만드는 시간도 줄일 수 있어 성능도 최적화 할 수 있습니다. 


3️⃣ String은 불변 객체이다 !

 String 은 불변 객체입니다. 따라서 내부의 문자열 값을 변경할 수 없습니다. 

 

불변 객체인 이유는 위에서 설명한 문자열 풀과 관련이 있습니다. 

String 은 문자열 풀을 통해 최적화를 합니다. 만약, String이 불변 객체가 아닌 가변 객체 (변경할 수 있는) 였다면 문자열 풀에서 같은 문자를 참조하는 변수의 모든 문자가 함께 변경되는 문제가 발생합니다. 

즉, 문자열 풀 그림에서 str3와 str4는 같은 "hello" 인스턴스를 참조하고 있는데, str3가 안에 문자열을 바꿀 수 있다면 str4 역시 바뀐 문자열을 참조하게 되는 것입니다.  이러한 사이드 이펙트 문제가 발생하기에 String 은 불변 객체로 설계되어 있습니다. 

public class StringImmutable1 {

    public static void main(String[] args) {
        String str = "hello";
        str.concat("java");
        System.out.println("str = " + str);
    }
}

 위의 코드의 실행 결과는 어떻게 나올까요?

실행 결과는 str = hello 로 나오게 됩니다. 

 

그 이유는, String은 불변 객체이기에 str.concat 을 한다고 해서 str 의 값은 바뀌지 않습니다. str.concat("java") 를 받는 다른 String이 필요하게 됩니다.

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);
    }
}

 

이렇게 코드를 작성해야 str1에는 hello가, str2에는 hellojava 가 들어가게 됩니다. 

 

이렇게 String은 내부의 값을 바꿀 수 없기 때문에 문자를 자주 더하거나 변경해야할 때는 계속 String 객체를 만들어내게되고, GC해야 합니다. 결과적으로 컴퓨터의 CPU, 메모리 자원을 더 많이 사용하게 됩니다. 또한 시간도 오래걸립니다. 

이러한 문제를 해결하기 위한 방법이 가변 String인 StringBuilder 입니다. 


4️⃣ String 메소드

✅ 문자열 정보 조회

  • length() :  문자열의 길이를 반환한다. 
  • isEmpty(): 문자열이 비어 있는지 확인한다. (위의 코드에서 str은 비어 있지 않으므로 false를 반환하고, 세번 째 "" 에 isEmpty() 를 하면 true를 반환하는 것을 확인할 수 있다.)
  • charAt(int index): 문자열에서 index 번째의 문자를 반환한다.
public class test {
    public static void main(String[] args) {
        String str = "Hello World!";
        System.out.println("문자열 길이: " + str.length());
        System.out.println("문자열이 비어 있는지: " + str.isEmpty());
        System.out.println("문자열이 비어 있는지: " + "".isEmpty());

        char ch1 = str.charAt(0); //0번째 인덱스에 있는 H 반환
        char ch2 = str.charAt(6); //5번째 인덱스에 있는 W 반환
        System.out.println("ch1 = " + ch1);
        System.out.println("ch2 = " + ch2);
    }
}

문자열 정보 조회 코드 실행결과

✅ 문자열 비교

  • equals(Object anObject) : 두 문자열이 동일한지 비교한다. 
  • equalsIgnoreCase(String anotherString): 두 문자열을 대소문자 구분 없이 비교한다. 
  • compareTo(String anotherString): 두 문자열을 사전 순으로 비교한다. (코드 예에서 c는 a보다 2개 뒤에 있으므로 2를 리턴한다.)
  • compareToIgnoreCase(String str): 두 문자열을 대소문자 구분 없이 사전적으로 비교한다. 
  • startsWith(String prefix): 문자열이 특정 prefix 로 시작하는지 확인한다. 
  • endsWith(String suffix): 문자열이 특정 suffix 로 끝나는지 확인한다. 
public class StringComparisonMain {

    public static void main(String[] args) {
        String str1 = "Hello, Java!";
        String str2 = "hello, java!";
        String str3 = "Hello, World!";

        System.out.println("str1 equals str2: " + str1.equals(str2));
        System.out.println("str1 equalsIgnoreCase str2: " + str1.equalsIgnoreCase(str2));
        
        System.out.println("'c' compareTo 'a' : " + "c".compareTo("a"));
        System.out.println("str1 compareTo Str3: " + str1.compareTo(str3));
        System.out.println("str1 compareToIgnoreCase str2: " + str1.compareToIgnoreCase(str2));

        System.out.println("str1 start with 'Hello': " + str1.startsWith("Hello"));
        System.out.println("str1 end with 'Java!': " + str1.endsWith("Java!"));
    }
}

문자열 비교 코드 실행결과

✅ 문자열 검색

  • contains(CharSequence s): 문자열이 특정 문자열을 포함하고 있는지 확인한다. 
  • indexOf(String ch) / indexOf(String ch, int fromIndex): 문자열이 처음 등장하는 위치를 반환한다. 
  • lastIndexOf(String ch): 문자열이 마지막으로 등장하는 위치를 반환한다. 
public class test {
    public static void main(String[] args) {
        String str = "Hello welcome, welcome to the show";

        System.out.println("문자열에 Hello 가 포함되어 있는지: " + str.contains("Hello"));
        System.out.println("'welcome' 의 첫 번째 인덱스: " + str.indexOf("welcome"));
        System.out.println("인덱스 13부터 'welcome'의 인덱스: " + str.indexOf("welcome", 13));
        System.out.println("'welcome' 의 마지막 인덱스: " + str.lastIndexOf("welcome"));
    }
}

문자열 검색 코드 실행결과

 

✅ 문자열 조작 및 변환

  • substring(int beginIndex) / substring(int beginIndex, int endIndex): 문자열의 부분 문자열을 반환한다. 
  • concat(String str): 문자열의 끝에 다른 문자열을 붙인다. 
  • replace(CharSequence target, CharSequence replacement): 특정 문자열을 새 문자열로 대체한다.
  • toLowerCase() / toUpperCase(): 문자열을 소문자나 대문자로 변환한다. 
  • trim(): 문자열 양쪽 끝의 공백을 제거한다. 
public class StringChangeMain1 {

    public static void main(String[] args) {
        String str = "Hello, Java! Welcome to Java";
        String strWithSpaces = "     Java Programming";

        System.out.println("인덱스 7부터의 부분 문자열: " + str.substring(7));
        System.out.println("인덱스 7부터 12까지의 부분 문자열: " + str.substring(7,12));

        System.out.println("문자열 결합: " + str.concat("!!!"));
        System.out.println("'Java'를 'World'로 대체: " + str.replace("Java", "World"));

        System.out.println("소문자로 변환: " + strWithSpaces.toLowerCase());
        System.out.println("대문자로 변환: " + strWithSpaces.toUpperCase());

        System.out.println("공백 제거(trim): " + strWithSpaces.trim());

    }
}

문자열 조작 및 변환 코드 실행결과

✅ 문자열 분할 및 조합

  • split(String regex): 문자열을 정규 표현식을 기준으로 분할한다. 
  • join(CharSequence delimiter, CharSequence...elements): 주어진 구분자로 여러 문자열을 결합한다. 
public class StringSplitJoinMain {

    public static void main(String[] args) {
        String str = "Apple,Banana,Orange";

        //split()
        String[] splitStr  = str.split(",");
        for (String s : splitStr) {
            System.out.println(s);
        }
        
        //join()
        String joinedStr = String.join("-", "A", "B", "C");
        System.out.println("연결된 문자열 = " + joinedStr);

        //문자열 배열 연결
        String result  = String.join("-", splitStr);
        System.out.println("result = " + result);
    }
}

문자열 분할 및 조합 코드 실행결과


 

🔍 참고글

인프런 [김영한의 실전 자바 - 중급 1편]  String 클래스 강의를 참고하여 작성했습니다.