[Java 응용] java.lang 패키지 - 2. String 클래스

Kyung Jae, Cheong·2024년 8월 25일
0
post-thumbnail

0. Java.lang 패키지 개요

  • java.lang 패키지는 Java 프로그래밍 언어의 핵심 클래스를 포함하고 있는 패키지로, 자주 사용되는 기본 클래스들을 포함하고 있습니다.
    • 이 패키지는 자동으로 모든 Java 프로그램에 import되어 있으며, 별도의 import 구문 없이 사용할 수 있습니다.
  • java.lang 패키지의 대표적인 클래스들로는 Object, String, 래퍼 클래스들(Integer, Boolean, 등), Class, System 등이 있습니다.
  • lang 패키지에서 중요하게 다룰 수 있는 내용들이 상당히 많고 복잡하기 때문에 우선은 필요한 개념들을 하나씩 차례대로 정리해볼 예정입니다.
    1. Object 클래스
    2. String 클래스
    3. 래퍼 클래스들(Integer, Double, Boolean, 등)
    4. 그외 클래스들(Class, System, Math, 등)
  • 이번 포스트에서는 java.lang 패키지의 핵심 클래스 중 String 클래스에 대해 자세히 살펴보겠습니다.

2. String 클래스

  • java.lang 패키지에 포함된 String 클래스는 Java 프로그래밍에서 가장 기본적이고 중요한 클래스 중 하나입니다.
  • String 클래스는 텍스트 데이터를 다루기 위해 사용되며, char 값의 시퀀스를 나타냅니다.
    • 참고로 자바 9 이전에는 private final char[] value;로 정의됐었지만, 자바 9 이후엔 private final byte[] value;로 기본적으로 정의됩니다. (다만, Latin-1 인코딩처럼 영어나 숫자로만 정의된 경우에 해당하는 것이고, 만약 한글 등을 포함한 UTF-16으로 인코딩된 경우엔 기존대로 char타입으로 정의됩니다.)

2.1. String 클래스의 특징 & 구조

  1. 참조형 데이터 타입
  • String 클래스는 참조형 데이터 타입입니다.
  • 기본 데이터 타입(예: int, char)과 달리, String 객체는 힙(Heap) 메모리 영역에 생성된 객체의 주소를 참조합니다.
  1. 불변(Immutable) 객체
  • String 객체는 불변 객체입니다.
    • 즉, 한 번 생성된 String 객체는 수정할 수 없습니다.
  • 만약 문자열을 변경하려고 하면, 새로운 String 객체가 생성됩니다.
    • 예를 들어, String s = "Hello";로 문자열을 선언한 후 s = s + " World";와 같이 문자열을 변경하면 새로운 객체가 생성됩니다.
  1. 문자열 풀(String Pool)
  • Java는 문자열의 메모리 효율성을 위해 문자열 풀(Pool)이라는 메모리 공간을 사용합니다.
    • 참고로 문자열 풀은 힙 메모리 영역에 위치해 있습니다.
  • 동일한 내용의 문자열 리터럴이 여러 번 사용되면, 새로운 객체를 생성하는 대신 문자열 풀에서 이미 생성된 객체를 재사용하여 메모리 낭비를 줄일 수 있습니다.
    • 참고로 Literal로 선언한 경우에 문자열 풀을 활용하게 되고, new 연산자로 선언한 경우엔 인스턴스로써 힙 영역에 저장됩니다. (아래에서 다시 설명합니다)

String 클래스 선언 방법

  • String 클래스는 다양한 방식으로 선언 및 초기화할 수 있습니다.

1. 리터럴(literal) 사용

  • 문자열을 선언할 때 가장 일반적인 방법은 문자열 리터럴을 사용하는 것입니다.
    • 이 방법은 JVM이 문자열 풀에 객체를 생성하므로, 메모리 효율적입니다.
String str1 = "Hello, World!";
  • 여기서 "Hello, World!"는 문자열 풀에 저장되며, 동일한 리터럴이 다시 사용될 경우 새로운 객체를 생성하지 않고, 기존 객체를 재사용합니다.
    • 참고로 같은 리터럴로 선언된 경우 == 연산자로 비교하면 true가 반환됩니다.

2. new 연산자 사용

  • new 키워드를 사용하여 명시적으로 String 객체를 생성할 수도 있습니다.
    • 이 방법은 문자열 풀을 사용하지 않으며, 항상 새로운 객체를 힙 메모리에 생성합니다.
String str2 = new String("Hello, World!");
  • 이 경우 "Hello, World!"라는 문자열이 힙 메모리 내에 새로운 객체로 생성됩니다. 따라서 동일한 내용의 문자열이라도 == 연산자로 비교하면 다른 객체로 인식됩니다.

3. 문자 배열(char array) 사용

  • 문자 배열을 사용하여 String 객체를 생성할 수도 있습니다.
    • 이 방법은 배열의 특정 부분만을 String으로 변환할 때 유용합니다.
char[] chars = {'H', 'e', 'l', 'l', 'o'};
String str3 = new String(chars);
  • 위의 코드는 문자 배열 chars를 이용해 "Hello"라는 문자열을 바로 생성합니다.

4. String 클래스의 다른 생성자들

  • 다양한 생성자를 활용하여 String 객체를 생성할 수 있습니다.
    • 예를 들어, 바이트 배열을 사용하거나 문자열의 일부분만을 추출해 새로운 String 객체를 만들 수 있습니다.
byte[] bytes = {72, 101, 108, 108, 111}; // ASCII 값
String str4 = new String(bytes); // "Hello" 출력

String str5 = new String(chars, 1, 3); // "ell" 출력 (부분 추출)

String의 불변성(Immutable)

  • String 객체는 불변성(immutable)을 가지며, 이는 한 번 생성된 문자열은 수정될 수 없음을 의미합니다.
    • 이 특성 덕분에 String 객체는 안전하게 여러 스레드에서 공유될 수 있습니다.
public class StringImmutableExample {
    public static void main(String[] args) {
        String str1 = "Hello";
        String str2 = str1;

        // str1에 새로운 값을 할당
        str1 = str1 + ", World!";
        
        // str1과 str2의 값 출력
        System.out.println("str1: " + str1); // "Hello, World!"
        System.out.println("str2: " + str2); // "Hello"
    }
}
  • 이 예제에서 str1"Hello, World!"로 변경되었지만, 원래의 str1이 가리키던 "Hello" 문자열은 변경되지 않습니다. 대신, str1은 이제 새로운 "Hello, World!" 객체를 참조하게 됩니다. 따라서 str2는 여전히 "Hello"를 참조합니다.

2.2. String 클래스의 비교

  • String 객체를 비교할 때는 항상 equals() 메서드를 사용해야 합니다.
    • Java에서 문자열 비교는 두 가지 방법으로 이루어질 수 있는데, 각각의 방법은 다른 결과를 가져올 수 있습니다.

== 연산자로 비교

  • == 연산자는 두 객체의 참조 주소를 비교합니다.
    • 따라서 두 String 객체가 동일한 내용을 가지고 있더라도 서로 다른 메모리 주소를 참조하면 == 연산자는 false를 반환합니다.
String s1 = new String("Hello");
String s2 = new String("Hello");
System.out.println(s1 == s2); // false, 서로 다른 객체 참조

equals() 메서드

  • equals() 메서드는 두 객체의 실제 문자열 값을 비교합니다.
    • 문자열 내용이 동일하다면 true를 반환합니다.
    • 참고로 Object 클래스의 equals() 메서드의 오버라이딩이 이미 된 메서드라서 굳이 다시 오버라이딩 할 필요가 없습니다.
String s1 = new String("Hello");
String s2 = new String("Hello");
System.out.println(s1.equals(s2)); // true, 문자열 내용이 동일

문자열 풀과의 관계

  • 문자열 리터럴을 사용하면 Java는 문자열 풀에서 동일한 문자열이 이미 존재하는지 확인하고, 존재하면 해당 문자열을 참조하도록 합니다.
    • 이 경우 == 연산자도 true를 반환할 수 있습니다.
String s1 = "Hello";
String s2 = "Hello";
System.out.println(s1 == s2); // true, 동일한 객체 참조
  • 따라서, String 객체를 비교할 때는 항상 equals() 메서드를 사용해야 합니다.

2.3. String 클래스의 메서드 정리

1. 문자열 정보 조회 및 비교

문자열의 길이 및 상태 조회

  • length(): 문자열의 길이를 반환합니다.
  • isEmpty(): 문자열이 비어있는지(길이가 0인지) 확인합니다.
  • isBlank(): 문자열이 공백 문자로만 구성되어 있는지 확인합니다.
    • isBlank()는 문자열이 비어있어도 true를 반환합니다.

문자열 비교

  • equals(Object another): 두 문자열이 동일한지 비교합니다.
  • equalsIgnoreCase(String another): 대소문자를 무시하고 두 문자열이 동일한지 비교합니다.
  • compareTo(String another): 두 문자열을 사전 순서로 비교합니다.
    • compareTo()는 문자열 앞부터 비교해서 처음으로 달라지는 문자(char)의 ASCII코드 혹은 유니코드 값(정수)의 차이를 반환합니다. (완전히 같으면 0 반환)
public class StringInfoExample {
    public static void main(String[] args) {
        String str = "Hello World";
        String emptyStr = "";
        String blankStr = "   ";

        // 문자열의 길이 및 상태 조회
        System.out.println("str.length(): " + str.length()); // 11
        System.out.println("emptyStr.isEmpty(): " + emptyStr.isEmpty()); // true
        System.out.println("blankStr.isBlank(): " + blankStr.isBlank()); // true

        // 문자열 비교
        String anotherStr = "HELLO WORLD";
        System.out.println("str.equals(anotherStr): " + str.equals(anotherStr)); // false
        System.out.println("str.equalsIgnoreCase(anotherStr): " + str.equalsIgnoreCase(anotherStr)); // true
        System.out.println("str.compareTo(anotherStr): " + str.compareTo(anotherStr)); // > 0 (사전순으로 더 크다)
    }
}

2. 문자열 및 인덱스 검색 및 포함 여부

특정 문자 또는 부분 문자열 조회

  • charAt(int index): 지정한 인덱스에 위치한 문자를 반환합니다.
  • substring(int beginIndex, int endIndex): 지정한 범위 내의 부분 문자열을 반환합니다.

문자열 내 위치 검색

  • indexOf(String str): 특정 문자나 문자열이 처음으로 등장하는 위치를 반환합니다.
  • lastIndexOf(String str): 특정 문자나 문자열이 마지막으로 등장하는 위치를 반환합니다.

문자열 포함 여부 및 패턴 일치

  • contains(CharSequence sequence): 특정 문자나 문자열이 포함되어 있는지 확인합니다.
  • startsWith(String prefix): 문자열이 지정한 접두사로 시작하는지 확인합니다.
  • endsWith(String suffix): 문자열이 지정한 접미사로 끝나는지 확인합니다.
  • matches(String regex): 문자열이 지정한 정규 표현식과 일치하는지 확인합니다.
public class StringSearchExample {
    public static void main(String[] args) {
        String str = "Hello, World!";

        // 특정 문자 또는 부분 문자열 조회
        System.out.println("str.charAt(7): " + str.charAt(7)); // W
        System.out.println("str.substring(7, 12): " + str.substring(7, 12)); // World

        // 문자열 내 위치 검색
        System.out.println("str.indexOf('o'): " + str.indexOf('o')); // 4
        System.out.println("str.lastIndexOf('o'): " + str.lastIndexOf('o')); // 8

        // 문자열 포함 여부 및 패턴 일치
        System.out.println("str.contains('World'): " + str.contains("World")); // true
        System.out.println("str.startsWith('Hello'): " + str.startsWith("Hello")); // true
        System.out.println("str.endsWith('!'): " + str.endsWith("!")); // true
        System.out.println("str.matches('.*World.*'): " + str.matches(".*World.*")); // true (정규식으로 포함 여부 확인)
    }
}

3. 문자열 조작 및 변환

문자열 조합 및 치환

  • concat(String str): 두 문자열을 결합합니다.
  • replace(CharSequence target, CharSequence replacement): 문자열 내의 특정 부분을 다른 문자열로 대체합니다.
  • replaceAll(String regex, String replacement): 정규 표현식에 일치하는 모든 부분을 대체합니다.
  • replaceFirst(String regex, String replacement): 정규 표현식에 일치하는 첫 번째 부분만 대체합니다.

대소문자 변환 및 공백 제거

  • toLowerCase(): 문자열을 소문자로 변환합니다.
  • toUpperCase(): 문자열을 대문자로 변환합니다.
  • trim(): 문자열 양 끝의 공백을 제거합니다.
  • strip(): 공백 문자(공백, 탭 등)를 제거합니다. Java 11에서 도입되었으며, trim()과 달리 모든 종류의 공백 문자를 다룹니다.
public class StringManipulationExample {
    public static void main(String[] args) {
        String str = "Hello";
        String additionalStr = " World";

        // 문자열 조합 및 치환
        String combinedStr = str.concat(additionalStr);
        System.out.println("combinedStr: " + combinedStr); // Hello World

        String replacedStr = combinedStr.replace("World", "Java");
        System.out.println("replacedStr: " + replacedStr); // Hello Java

        // 대소문자 변환 및 공백 제거
        System.out.println("replacedStr.toUpperCase(): " + replacedStr.toUpperCase()); // HELLO JAVA
        String paddedStr = "   Trim me   ";
        System.out.println("paddedStr.trim(): " + paddedStr.trim()); // "Trim me"
        System.out.println("paddedStr.strip(): " + paddedStr.strip()); // "Trim me" (Java 11+)
    }
}

4. 문자열 분할 및 조합

문자열 분할

  • split(String regex): 정규 표현식을 기준으로 문자열을 분할하여 배열로 반환합니다.

문자열 조합

  • join(CharSequence delimiter, CharSequence... elements): 지정한 구분자를 사용해 여러 문자열을 하나로 결합합니다.
public class StringSplitJoinExample {
    public static void main(String[] args) {
        String str = "apple,banana,cherry";

        // 문자열 분할
        String[] fruits = str.split(",");
        for (String fruit : fruits) {
            System.out.println(fruit);
        }
        // 출력: apple, banana, cherry (각각 줄바꿈)

        // 문자열 조합
        String joinedStr = String.join(" & ", fruits);
        System.out.println("joinedStr: " + joinedStr); // apple & banana & cherry
    }
}

5. 문자열 포맷 및 변환

포맷 지정

  • format(String format, Object... args): 지정된 형식에 맞추어 문자열을 생성합니다.
    • String.format() 메서드는 다양한 포맷 지정자를 사용하여 문자열을 지정된 형식에 맞게 변환할 수 있도록 도와줍니다.
    • 포맷 지정자는 문자열 내에서 % 기호로 시작하며, 이후에 다양한 문자를 결합해 특정 타입의 데이터를 원하는 형태로 포맷팅할 수 있습니다.

      주요 format 지정자 (주로 쓰이는 것만 정리했습니다)

      1. 숫자 관련 포맷 지정자
        • %d: 정수형 데이터를 10진수로 포맷팅
        • %f: 실수형 데이터를 표시 (기본적으로 소수점 아래 6자리까지)
        • %.nf: 실수형 데이터를 소수점 아래 n자리까지 표시하며 포맷팅
      2. 문자 및 문자열 관련 포맷 지정자
        • %s: 문자열을 포맷팅
        • %b: 논리형(불리언)을 포맷팅
        • %n: 줄 바꿈 문자. OS에 따라 적절한 줄 바꿈을 삽입
      3. 날짜 및 시간 관련 포맷 지정자
        • %tY: 4자리 연도 (예: 2024)
        • %tm: 2자리 월 (예: 08)
        • %td: 2자리 일 (예: 25)
        • %tH: 2자리 시간 (24시간제, 예: 14)
        • %tM: 2자리 분 (예: 30)
        • %tS: 2자리 초 (예: 45)
      4. 플래그 및 너비 관련 지정자
        • %0nd: n자리 숫자로 포맷팅하며, 자리 수가 부족할 경우 앞에 0을 채웁
        • %+d: 정수에 부호를 포함하여 포맷팅
        • %-nd: n자리 숫자로 포맷팅하며, 왼쪽 정렬
        • %,d: 정수를 3자리마다 쉼표로 구분하여 포맷팅

다른 데이터 타입으로 변환

  • valueOf(Object obj): 다양한 데이터 타입을 문자열로 변환합니다.
  • toCharArray(): 문자열을 char 배열로 변환합니다.
public class StringFormatExample {
    public static void main(String[] args) {
        int number = 42;
        String text = "apples";

        // 포맷 지정
        String formattedStr = String.format("I have %d %s.", number, text);
        System.out.println("formattedStr: " + formattedStr); // I have 42 apples.

        // 다른 데이터 타입으로 변환
        char[] charArray = formattedStr.toCharArray();
        for (char c : charArray) {
            System.out.print(c + " "); // I   h a v e   4 2   a p p l e s .
        }
        System.out.println();

        String boolStr = String.valueOf(true);
        System.out.println("boolStr: " + boolStr); // true
    }
}

6. 기타 유틸리티

기타 유용한 메서드들

  • intern(): 문자열을 문자열 풀에 등록하고 해당 문자열을 반환합니다.
  • hashCode(): 문자열의 해시코드를 반환합니다.
  • codePointAt(int index): 지정한 인덱스의 유니코드 코드 포인트를 반환합니다.
public class StringUtilityExample {
    public static void main(String[] args) {
        String str = "Hello";
        String internedStr = str.intern();

        // intern() 사용
        System.out.println("str == internedStr: " + (str == internedStr)); // true (문자열 풀에서 동일 객체 참조)

        // hashCode()와 codePointAt()
        System.out.println("str.hashCode(): " + str.hashCode()); // 해시코드 출력
        System.out.println("str.codePointAt(1): " + str.codePointAt(1)); // 101 ('e'의 유니코드 포인트)
    }
}

2.4. StringBuilder 클래스

  • StringBuilder 클래스는 Java에서 문자열을 조작하는 데 매우 유용한 클래스입니다.
    • 불변 객체인 String 클래스와 달리, StringBuilder는 가변(mutable) 객체로, 문자열을 여러 번 수정하는 경우 String보다 훨씬 더 효율적입니다.
  • 문자열이 자주 변경되는 상황에서 StringBuilder를 사용하면 불필요한 객체 생성을 줄이고 성능을 향상시킬 수 있습니다.

주요 특징

  • 가변성(Mutable): StringBuilderString과 달리 문자열을 수정할 수 있습니다.
    • StringBuilder 객체에서 문자열을 수정해도 새로운 객체가 생성되지 않고, 기존 객체에서 바로 수정됩니다.
  • 성능 최적화: 문자열을 자주 변경하거나 연결하는 작업에서 StringBuilder를 사용하면 메모리와 성능을 최적화할 수 있습니다.
  • 동기화되지 않음: StringBuilder는 동기화되지 않아 멀티스레드 환경에서 안전하지 않습니다. 멀티스레드 환경에서는 StringBuffer를 사용하는 것이 적합합니다.
    • StringBufferStringBuilder의 주요 메서드들은 거의 동일합니다.

StringBuilder 클래스 주요 메서드

  • append(String str): 문자열을 StringBuilder 객체의 끝에 추가합니다.
  • insert(int offset, String str): 지정한 위치에 문자열을 삽입합니다.
  • delete(int start, int end): 지정한 범위의 문자열을 삭제합니다.
  • reverse(): 문자열을 역순으로 만듭니다.
  • toString(): StringBuilder 객체를 String 객체로 변환합니다.

예시 코드

public class StringBuilderExample {
    public static void main(String[] args) {
        // StringBuilder 생성
        StringBuilder sb = new StringBuilder("Hello");

        // 문자열 추가
        sb.append(" World");
        System.out.println("After append: " + sb.toString()); // Hello World

        // 문자열 삽입
        sb.insert(6, "Beautiful ");
        System.out.println("After insert: " + sb.toString()); // Hello Beautiful World

        // 문자열 삭제
        sb.delete(6, 16);
        System.out.println("After delete: " + sb.toString()); // Hello World

        // 문자열 역순
        sb.reverse();
        System.out.println("After reverse: " + sb.toString()); // dlroW olleH

        // StringBuilder를 String으로 변환
        String result = sb.toString();
        System.out.println("Final result: " + result); // dlroW olleH
    }
}

2.5. String 최적화 & Method Chaining

  • Java에서 문자열을 효율적으로 다루기 위해 다양한 최적화 기법과 메서드 체이닝(Method Chaining) 패턴을 활용할 수 있습니다.

String 최적화

  • 문자열 조작에서 최적화를 위해 다음과 같은 기법을 사용할 수 있습니다.
    • StringBuilder 사용: 앞서 설명한 것처럼, 문자열을 여러 번 변경해야 하는 경우 StringBuilder를 사용하는 것이 좋습니다.
      • 이렇게 하면 String처럼 불변 객체를 계속 생성하지 않아도 되어 메모리 사용을 줄이고 성능을 향상시킬 수 있습니다.
    • String Pool 활용: 동일한 문자열 리터럴을 반복적으로 사용할 경우, JVM의 문자열 풀을 활용하여 메모리를 최적화할 수 있습니다.
      • 문자열 리터럴을 재사용하도록 코드 작성 시 이를 염두에 두는 것이 좋습니다.
    • 불필요한 문자열 연결 피하기: 불필요한 문자열 연결이나 반복적인 + 연산을 피하고, 필요한 경우 StringBuilder 또는 StringBuffer를 사용하는 것이 효율적입니다.

Method Chaining

  • 메서드 체이닝은 객체의 메서드를 호출한 후, 그 결과로 반환된 객체에서 다시 메서드를 호출하는 패턴입니다.
    • 이 패턴은 코드 가독성을 높이고, 여러 연산을 간결하게 표현할 수 있습니다.
  • 참고로 일반적인 클래스에서 Method Chaining을 구현하여 활용하기 위해선 Class에서 메서드들을 정의할때 return this와 같이 본인의 객체를 반환하도록 설계해야합니다.
public class MethodChainingExample {
    public static void main(String[] args) {
        // StringBuilder를 이용한 메서드 체이닝
        String result = new StringBuilder("Hello")
                .append(" ")
                .append("World")
                .insert(6, "Beautiful ")
                .delete(6, 16)
                .reverse()
                .toString();

        System.out.println("Final result with method chaining: " + result); // dlroW olleH
    }
}
  • 위 코드에서 StringBuilder 객체에 대해 여러 메서드를 연속적으로 호출하면서 체이닝 패턴을 사용하였습니다. 이 패턴은 코드의 간결함과 직관성을 높여줍니다.

마무리

  • 이번 포스팅에서는 String 클래스의 특징과 구조, 주요 메서드, 최적화 기법 및 문자열 조작에 대한 내용을 다루었습니다.
  • Java에서 String 클래스는 가장 기본적이고 중요한 클래스 중 하나로, 문자열 데이터를 다루기 위해 널리 사용됩니다.
    • String의 불변성, StringBuilder의 가변성, 그리고 메서드 체이닝과 같은 패턴들은 Java에서 문자열을 효율적으로 관리하고 조작하는 데 필수적인 요소들입니다. 이러한 개념들을 잘 이해하고 활용하면, 더욱 효율적이고 성능 좋은 Java 프로그램을 작성할 수 있을 것입니다.
  • 다음에는 번외편으로 String.format() 메서드나 System.out.printf()에서 주로 활용되는 포멧 지정자에 대해서 정리해볼 예정이고, 이후엔 래퍼클래스와 Class클래스에 대해서 다루어볼 예정입니다.
profile
일 때문에 포스팅은 잠시 쉬어요 ㅠ 바쁘다 바빠 모두들 화이팅! // Machine Learning (AI) Engineer & BackEnd Engineer (Entry)

0개의 댓글