면접 스터디 : String

NtoZ·2023년 6월 18일
0

Interview

목록 보기
1/2
post-thumbnail

🚩 String

📌 String str ="i"와 String str = new String("i")가 동일합니까?

💡 리터럴로 생성한 String과 new 연산자로 생성한 String 객체는 동일하지 않다.

    String strNew1 = new String("ABC"); //(1)
    String strNew2 = new String("ABC"); //(2)

    String strLiteral1 = "ABC"; //(3)
    String strLiteral2 = "ABC"; //(4)
  • 위의 네 개의 String 변수에는 "ABC"라는 같은 값이 할당되어 있다.
    그렇다면 (1), (2), (3), (4) 모두 같은 객체라고 말할 수 있을까?

    정답은 그렇지 않다. 대소 비교 연산자 ==로 확인해볼 수 있다.
    ==는 비교하는 대상이 객체인 경우라면 같은 주소값(같은 객체인지)의 여부를 비교해준다.
public static void main(String[] args) {
        String strNew1 = new String("ABC"); //(1)
        String strNew2 = new String("ABC"); //(2)

        String strLiteral1 = "ABC"; //(3)
        String strLiteral2 = "ABC"; //(4)

        System.out.println(strNew1 == strNew2);  //⭐ false
        System.out.println(strLiteral1 == strLiteral2); //⭐ true

        System.out.println(strNew1 == strLiteral1); // false
        System.out.println(strNew1 == strLiteral2); // false

    }
  • 위의 결과에서 중요한 것은 모든 String 변수에 같은 값 "ABC"가 할당되어 있더라도 new로 만들었는지 또는 ""(큰따옴표) 리터럴로 만들었는지 여부에 따라서 같은 객체가 아닐 수도 있다는 것이다.

  • 그 이유는 String strNew1 = new String("ABC");String strLiteral1 = "ABC";이 객체를 만드는 과정에서 차이가 발생하기 때문이다.

  • new 연산자로 생성된 객체는 일반적으로 JVM의 HEAP 메모리에 저장된다.
    new로 생성한 String 객체도 예외없이 해당 공식을 따른다.

  • 그러나 String strLiteral1 = "ABC";와 같이 문자열 리터럴로 생성한 객체는 HEAP 메모리 내부의 상수 풀(String Constant Pool)에 별도로 저장되어 관리한다.

    아래와 같은 코드를 살펴보자.

	String s1 = "Cat";  // (1) 
    String s2 = "Cat";  // (2)
  • (1) String 참조변수 s1에 "Cat"이라는 문자열 리터럴을 할당했다. (1)은 최초의 시도이므로, 자바 메모리 영역 Heap의 상수 풀(Constant Pool)에 생성된다.

    그렇다면 s2로 "Cat"을 생성했을 때는 어떻게 될까?
    해당 값이 JVM의 Heap, 그 중에서도 상수 풀에서 이미 관리되고 있는 객체인지 여부를 판단한다.

    ➡️ (ㄱ) 이미 관리되고 있다면 (기존에 값을 할당했다면) 새로운 객체를 생성하지 않고 해당 메모리 주소를 반환한다.
    ➡️ (ㄴ) 반면에 기존에 생성한 적 없는 새로운 값이라면 새 객체를 생성하고 해당 메모리 주소를 반환한다.

    위의 경우에는 (ㄱ)의 경우이므로 참조변수 s2에는 s1과 일치하는 메모리 주소값이 반환되는 것이다.
    따라서 System.out.println(s1 == s2)는 true라는 값을 가지게 된다.

    반면에 new 연산자로 생성한 String s3 = new String("Cat");의 경우 같은 값을 가지고 있더라도 당연히 해당 객체의 내부 변수 값을 확인하지 않는다.
    String s4 = new String("Cat"),
    String s5 = new String("Cat") ...
    s3, s4, s5 모두 다른 객체임을 알 수 있다.

  • 따라서 메모리 관리 측면에서는 String 객체를 생성할 때 HEAP 메모리 안에 new 연산자로 항상 새로운 객체를 생성하는 것보다, 리터럴으로 생성하여 상수풀에서 관리 받도록 함이 적절하다.

주소 값이 아닌 내장된 리터럴 값을 비교하는 방법

  • equals()를 사용했을 때는 두 문자열의 내용(“abc”)을 비교하기 때문에 (원래는 두 객체의 주소값을 비교하는 메서드이지만 String 메서드에서는 두 String 객체의 value 값을 비교하도록 오버라이딩 했기 때문) 서로 다른 객체인지 여부를 비교하는 것이 아니라 문자열 내용을 비교하고 싶다면 equals() 메서드를 사용한다.

(Advanced) 상수 풀은 GC가 될까요?

참고자료




📌 자바에서 문자열을 조작하는 클래스는 무엇이 있습니까? 각 클래스의 차이점은 뭘까요?

  • String 클래스는 문자열을 저장하고 이를 다루는 데 필요한 메서드를 제공하는 클래스이다.

변경 불가능한 (immutable) 클래스, String

  • String 클래스에는 문자열을 저장하기 위해서 문자형 배열 참조변수(char[ ]) value를 인스턴스 변수로 정의한다. 인스턴스 생성 시 생성자의 매개변수로 입력받는 문자열은 이 인스턴스 변수(value)에 문자형 배열(char[ ])로 저장되는 것이다.
	public final class String implements java.io.Serializable, Comparable {
    	private char[ ] value;
        	...
      String s = "abcd"; // 최초로 String 참조변수 s에 "abcd" 값 할당
      s = "abcdef"; // s에 "abcdef" 재할당
  • 기존의 다른 일반적인 mutable(불변가능한) 객체나 자료형들은 해당 멤버변수의 값 자체를 재할당한다.
    그러나 String 클래스는 immutable 클래스이므로 값에 변형이 발생했을 때 기존 값을 heap 메모리에 남겨두고 별도의 새로운 값을 만들어 해당 값을 참조하도록 한다.
    비연결된 나머지 객체는 garbage collector가 메모리에서 지워준다.

String의 불변 가능성, 생각해볼만한 점

  • 덧셈 연산자 '+'를 사용해서 문자열을 결합하는 것은 매 연산 시 마다 새로운 문자열을 가진 String 인스턴스가 생성되어 메모리공간을 차지하게 되므로 (성능 저하를 막기위해) 가능한 한 결합횟수를 줄이는 것이 좋다.

  • concat() 또는 + 연산자를 사용해서 새로운 객체를 계속해서 만든다면 성능 저하가 발생할 수 있기 때문에 mutable 클래스인 StringBuilder, StringBuffer를 생성한다.

    • 단, JDK 1.5 이후로는 + 연산자를 사용하더라도 StringBuilder 처럼 mutable한 방식으로 밸류를 변형할 수 있다는 의견이 존재한다.
      https://www.slipp.net/questions/271

변경 가능한 클래스 - StringBuffer, StringBuilder

  • StringBuffer, StringBuilder 클래스는 String 클래스와 달리 지정된 문자열의 변경이 가능하다. 즉, mutable하다.
    출처 : https://ifuwanna.tistory.com/221

  • StringBuffer는 내부적으로 문자열 편집을 위한 버퍼(buffer) 를 가지고 있으며, StringBuffer인스턴스를 생성할 때 그 크기를 지정할 수 있다.
    (버퍼buffer는 StringBuffer나 StringBuilder 등의 동적배열에 사용될 '크기'라고 볼 수 있다.) 편집할 문자열의 길이를 고려하여 버퍼의 길이를 충분히 잡아주는 것이 좋다.

  • 동일한 API를 가지고 있는 StringBuffer, StringBuilder의 차이점:

    • 가장 큰 차이점은 동기화의 유무로써 StringBuffer는 동기화 키워드를 지원하여 멀티쓰레드 환경에서 안전하다는 점(thread-safe) 입니다. 참고로 String도 불변성을 가지기때문에 마찬가지로 멀티쓰레드 환경에서의 안정성(thread-safe)을 가지고 있습니다.
    • 반대로 StringBuilder는 동기화를 지원하지 않기때문에 멀티쓰레드 환경에서 사용하는 것은 적합하지 않지만 동기화를 고려하지 않는 만큼 단일쓰레드에서의 성능은 StringBuffer 보다 뛰어납니다.
  • StringBuffer의 사용 예시

class Ex9_12 {
	public static void main(String[] args) {
		StringBuffer sb = new StringBuffer("01"); //⭐ 내부 문자열값으로 "01"을 갖는 SB 객체 생성
		StringBuffer sb2 = sb.append(23);	// ⭐sb = sb2 = "0123"
		sb.append('4').append(56);	//⭐ sb = sb2 = "0123456" 
		
		StringBuffer sb3 = sb.append(78); // ⭐ sb = sb2 = sb3 = "012345678"
		sb3.append(9.0); // ⭐ sb, sb2, sb3 모두 "0123456789.0"

		System.out.println("sb ="+sb);	//0123456789.0
		System.out.println("sb2="+sb2); //0123456789.0
		System.out.println("sb3="+sb3); //0123456789.0

		// ⭐ 10인덱스인 .빼고 다시 sb리턴 "01234567890"
		System.out.println("sb ="+sb.deleteCharAt(10));
		// ⭐ 3~5 인덱스 제외한 sb리턴 "01267890"
		System.out.println("sb ="+sb.delete(3,6));
		// ⭐ 3인덱스 자리에 "abc" 추가 동객체 리턴 "012abc67890"
		System.out.println("sb ="+sb.insert(3,"abc"));
		// ⭐ 6인덱스부터 sb의 길이-1인덱스까디 "END"로 대체 "012abcEND"
		System.out.println("sb ="+sb.replace(6, sb.length(), "END"));

		System.out.println("capacity="+sb.capacity());
		// ⭐ "012abcEND".length = 9
		System.out.println("length="+sb.length());
	}
}
  • StringBuffer(버퍼 크기) / StringBuffer("문자열") : new와 더불어 새 객체 생성
  • append() : 추가
  • insert() : 삽입
  • delete() : 삭제
  • toString() : StringBuffer 객체에서 String 객체로 변환.

정리

  • 컴파일러에서 분석 할때 최적화에 따라 다른 성능이 나올 수도 있지만 일반적인 경우에는 아래와 같은 경우에 맞게 사용한다.

  • String : 문자열 연산이 적고 멀티쓰레드 환경일 경우

  • StringBuffer : 문자열 연산이 많고 멀티쓰레드 환경일 경우

  • StringBuilder : 문자열 연산이 많고 단일쓰레드이거나 동기화를 고려하지 않아도 되는 경우

참고자료 & 출처

profile
9에서 0으로, 백엔드 개발블로그

0개의 댓글