개발 학습 실습실

이 사이트의 실습은 자바스크립트를 켜야 움직입니다. 아래는 같은 내용을 글로 적은 것입니다.

공부 › Java › 자주 쓰는 클래스

String, StringBuilder

문자열 메서드를 불렀는데 왜 원본이 그대로인가

String은 한번 만들어지면 내용이 바뀌지 않는 객체입니다. concat, replace, toUpperCase 같은 메서드는 원본을 고치지 않고 새 문자열을 만들어 돌려주므로, 돌려준 값을 받지 않으면 아무 일도 없었던 것처럼 보입니다. 반대로 StringBuilder는 객체 하나의 내용을 직접 고쳐 씁니다. 두 변수가 같은 객체를 가리키는지를 ==로 확인하면서 차이를 살펴봅니다.

실행으로 대조 · JDK 21에서 열여덟 가지 조합을 실행해 대조

이 실습에 나오는 개념

String의 불변성
String 객체는 한번 만들어지면 그 안의 글자를 바꿀 수 없다는 성질이다. concat, replace, toUpperCase, trim 같은 메서드는 원본을 고치지 않고 결과를 담은 String을 돌려준다. 그래서 s.toUpperCase()처럼 호출만 하고 돌려준 값을 받지 않으면 s는 그대로이며, s = s.toUpperCase()처럼 다시 받아야 결과를 쓸 수 있다. 이때 바뀌는 것은 문자열 객체가 아니라 변수 s가 가리키는 대상이다. 내용이 바뀌지 않으므로 여러 곳에서 같은 문자열을 함께 가리켜도 안전하고, HashMap의 키로 쓰기에도 알맞다.
불변 객체
생성된 뒤에는 상태가 바뀌지 않는 객체다. 값이 바뀌지 않으므로 여러 스레드가 동시에 사용해도 안전하고, 다른 곳에 넘겨도 변경될 걱정이 없으며, 캐싱하거나 해시 컬렉션의 키로 쓰기에 적합하다. 필드를 private final로 선언하고 상태를 바꾸는 메서드를 두지 않으며, 클래스를 final로 해 하위 클래스가 동작을 바꾸지 못하게 한다. 필드가 가변 객체(List, Date 등)를 가리키면 생성자와 getter에서 복사본을 주고받아(방어적 복사) 밖에서 내부를 바꾸지 못하게 한다.
StringBuilder와 StringBuffer
내용을 바꿀 수 있는 가변 문자열 클래스다. String은 불변이라 연결할 때마다 새 객체가 만들어지지만, 이 둘은 내부 버퍼에 이어 붙여 객체 생성을 줄인다. StringBuffer는 메서드가 동기화되어 여러 스레드에서 안전하고, StringBuilder는 동기화가 없어 단일 스레드에서 더 빠르다.
동일성과 동등성
동일성은 두 참조가 메모리상 같은 객체를 가리키는지를 뜻하며 == 연산자로 비교한다. 동등성은 두 객체가 논리적으로 같은 값을 가지는지를 뜻하며 equals() 메서드로 비교한다. 문자열이나 값 객체를 비교할 때 ==를 쓰면 내용이 같아도 false가 나올 수 있다.

이렇게 바꿔 보세요

처음 설정으로 실행하면

  1. String s = "java": 내용이 "java"인 문자열 객체가 있고, 변수 s가 그 객체를 가리킵니다.
  2. String first = s: first에 s가 가리키는 곳을 그대로 넣습니다. 내용을 복사한 것이 아니라 두 변수가 같은 객체를 가리키게 된 것입니다. 처음 객체가 나중에 바뀌는지 보려고 둡니다.
  3. s.toUpperCase(): 원래 객체 "java"는 그대로 두고, 새 문자열 "JAVA" 객체를 만들어 돌려줍니다. 그런데 돌려준 것을 받는 변수가 없습니다. 새 문자열은 어느 변수에도 남지 않습니다.
  4. System.out.println(s): s가 가리키는 객체의 내용인 java 한 줄이 출력됩니다. 메서드를 불렀지만 s는 처음 객체를 그대로 가리키고 있어 아무 일도 없었던 것처럼 보입니다.
  5. System.out.println(first): first가 가리키는 처음 객체의 내용인 java 한 줄이 출력됩니다. 처음 객체의 내용은 바뀌지 않았습니다.
  6. s == first: 두 변수가 같은 객체를 가리키므로 true입니다.

실행 결과

코드

String s = "java";
String first = s;                 // 처음 객체를 기억해 둔다

s.toUpperCase();                 // 돌려준 값을 받지 않는다

System.out.println(s);
System.out.println(first);
System.out.println(s == first);   // 같은 객체인가

이 모형의 전제

열여덟 가지 조합(숨겨진 설정을 빼면 서로 다른 경우는 열 가지)의 출력을 이 PC의 JDK 21에서 같은 코드를 실행한 결과와 대조했습니다. "새 객체가 만들어졌다"는 것은 == 비교로 직전 객체와 다른 객체임을 확인한 것입니다. 실행 중에 실제로 몇 개의 객체가 메모리에 만들어지는지는 JVM의 최적화에 따라 달라질 수 있어 단정하지 않았습니다. 여기서 고른 메서드와 값은 모두 내용이 달라지는 경우입니다. 내용이 달라지지 않는 호출(예: 바꿀 글자가 없는 replace)에서는 String이 새 객체를 만들지 않고 자기 자신을 돌려주기도 합니다. StringBuilder가 안에서 글자를 담는 공간을 어떻게 늘리는지는 이 모형에서 다루지 않습니다.

앞 실습: equals와 hashCode · 다음 실습: 날짜와 시간

이 실습에 나오는 개념

이 실습에서 보는 것

설정

코드


      
    

그림

실행 비교

이렇게 바꿔 보세요