본문 바로가기
딥다이브

StringBuffer가 문자열 반환을 해야하는 이유(.equals() 깊게 파헤치기)

by 보보트레인 2023. 6. 15.

사건의 발단은 이렇다.

사건의 발단문제

StringBuffer를 사용하여  reverse()메서드로 문자열의 순서를 뒤집고

reversed라는 문자열에 값을 담고자 하는데,

맨 뒤에 .toString();을 왜 붙혀야 하는지 의문이 생겼다.

(분명 같은 문자열이거늘... 버퍼를 사용할때는 다른 제한사항이 있는 건가?? 너무 궁금했다. )

.toString()을 제외하고 코드를 써보았지만 여지없이 오류가 떴다.

 

StringBuffer를 사용할 때에는 왜 마지막에 항상 .toString()으로 문자열 반환을 해야 문자열에 값을 제대로 담을 수 있는지 이해해보고자 공부를 시작했다.

 

 

String과 StringBuffer,StringBuilder의 차이점 

String 객체는 한번 생성되면 할당된 공간이 변하지 않지만 StringBuffer나 StringBuilder의 경우 객체의 공간이 부족해지는 경우 버퍼의 크기를 유연하게 늘려준다.

 

이러한 특징을 일컬어 String은 불변(immutable)하다고 한다. 즉 기존 문자열에 새로운 문자열을 추가하고자 하는 경우

메모리에서 문자열이 추가되는 것이 아니라, 새로운 메모리가 할당되어 문자열이 생성된다.

 

StringBuffer와 StringBuilder는 가변(mutable)하다라고 합니다.

 

보통 메모리의 효율적 재고를 위해 StringBuilder와 StringBuffer를 사용한다. ( 재활용 가능 )

 

그럼 왜? StringBuffer는 문자열 반환을 해야하나?

요약 : 클래스내에 포함하고있는 equals()메소드의 오버라이딩 여부때문에...

 

String클래스에서는 equals메소드를 오버라이딩한다.

StringBuffer클래스에서의 equals메소드는 오버라이딩하지 않는다.

더 자세하게 설명하자면 StringBuffer클래스의 equals메소드는 Object클래스의 equals메소드를 의미하며, 등가비교연산자(==)로 비교한 것과 같은 결과를 얻는다. ( 오류 뜬다는 뜻. )

 

반면에 .toString()은 오버라이딩 기능을 포함하고 있어서 StringBuffer인스턴스에 toString()을 호출하면, 담고있는 문자열을 String으로 반환한다.

 

결론 : StringBuffer인스턴스에 담긴 문자열을 비교하기 위해서는 StringBuffer인스턴스에 toString()을 호출해서 String인스턴스를 얻은 다음, String클래스의 equals메소드를 사용해서 비교해야 한다.

 

 

 

 

<+@ 노파심에 적는 추가 내용>

1. 클래스와 인스턴스의 차이 : 클래스란 객체를 정의하고 만들어 내기 위한 설계도 혹은 틀을 말한다. 클래스 안에는 객체를 만들어내기 위해 필요한 변수와 메서드들이 존재한다. 객체란 클래스에 선언된 모양 그대로 생성된 실체를 말하며 '클래스의 인스턴스'라고 부른다. 인스턴스란 클래스를 통해서 구현해야할 대상 (객체)이 실제로 구현된 구체적인 실체를 말한다.

 

2. equals() 와 == 의 차이점 : 메소드와 연산자의 차이.

  • equals() : Object 클래스에 정의된 메소드.
  • == : 자바 언어 자체의 연산자.

다시 말해서, equals() 메소드는 오버라이딩 가능하지만, == 는 오버라이딩이 불가능하다.

 

  • equals() : 기본적으로 값 비교를 한다. 기본 타입에는 적용 불가.
  • == : 기본적으로 주소값 비교.(기본 타입에 대해서는 값 비교.)
    -> 참조 타입/기본 타입 전부 사용가능 !

아래의 예제를 참고하자.

// == 연산자 예제.
package study.test.java;

public class TestExample {
	public static void main(String[] args) {
		int a = 10;
		int b = 10;
		System.out.println(a == b); // true
		
		Person person1 = new Person();
		Person person2 = new Person();
		Person person3 = person2;
		
		System.out.println(person1 == person2); // false
		System.out.println(person2 == person3); // true
        	System.out.println(person1.equals(person2)); //true
	}
}

위의 코드를 완벽히 이해하려면 call by reference와 call by value의 차이에 대해 이해해야 한다.

 

3. call by reference와 call by value의 차이 : reference는 heap영역의 참조값을 의미하고 value는 stack영역의 기본타입을 의미한다.  대표적인 call by reference는 문자열(String)을 예시로 들 수 있다.

 

String1 = "사람" , String2 = "사람" 두개의 문자열을 비교할 때,

두 문자열의 내부의 값은 같지만 heap영역의 참조값이 다르기에 String1 != String2가 됨을 이해해야 한다.

 

4. String 클래스의 equals() 메소드 : 문자열 연산시 == 대신 equals()를 사용해야 하는 이유를 이해해야한다. 

위 3번에서 말했듯이 문자열을 비교할때는 ==를 쓸 수 없다. (참조 값이 다르기 때문에...)

반면에 equals()는 참조값이 아닌 stack영역의 기본값만을 비교하기 때문에  문자열 비교시 equals()를 사용할 수 있다.

 

5.Object클래스(최상위 클래스)의 eqauls() 메소드 : Object 에 포함된 기본 equals() 메소드의 코드를 보면, 객체 자체를 관계 연산자 (==) 으로 비교한다. 따라서 내부적으로 같은 값을 가진 객체 2개라도, 다른 메모리 주소를 가지기 때문에 서로 다른 값으로 인식한다.

    public boolean equals(Object obj) {
        return (this == obj);
    }

즉, 자기 자신을 비교하지 않는 이상 무조건 다른 값이 되는 것이다.
우리는 Object클래스의 equals()를 그대로 사용할 경우 올바른 비교가 되지 않음을 알아야한다.

실제 사용시에는 해당 클래스에 맞도록 오버라이딩 하여 사용한다(String클래스의 equals() 메소드 처럼...). → 4번참고

반응형

'딥다이브' 카테고리의 다른 글

[javascript/java] Optional Channing  (0) 2023.09.04
객체화를 위한 래퍼(Wrapper) 클래스  (0) 2023.06.16