
String 클래스가 있다.new 연산자를 사용하면 안 되는 이유String s1 = new String("bikini"); // 새로운 객체 생성 (비효율적)
String s2 = "bikini"; // 기존 문자열 재사용 (권장)
"bikini"를 new String("bikini")로 생성하면 매번 새로운 객체가 생성되어 메모리 낭비가 발생한다."bikini"라는 문자열이 이미 존재하면 s2는 같은 객체를 재사용한다."bikini")을 사용하면 같은 JVM 내에서는 항상 동일한 객체를 재사용한다.(String pool)
String Pool이란?
- String Pool은 JVM이 문자열 리터럴을 저장하고 재사용하는 메모리 공간이다.
String s = "bikini";와 같은 리터럴 방식으로 생성된 문자열은 String Pool에 저장되고, 같은 문자열이 요청되면 기존 객체를 재사용한다.- 반면에
new String("hello")는 String Pool을 사용하지 않고 새로운 객체를 생성한다.
- String Pool은 정확하게는 Method Area 내부에 위치한다고 한다. 내가 이해한 대로 JVM의 메모리 구조에 맞춰서 각각 어떻게 저장되는지 그려보았다.
Boolean.valueOf(String)Boolean b1 = Boolean.valueOf("true"); // 기존 객체 재사용
Boolean b2 = new Boolean("true"); // 새로운 객체 생성 (비효율적)
new Boolean("true")는 매번 새로운 객체를 생성하지만, Boolean.valueOf("true")는 미리 캐싱된 인스턴스를 반환한다.캐싱(Caching)*이란?
캐싱이란 자주 사용하는 데이터를 미리 저장해두고 재사용하는 기법을 의미한다.
String.matches()를 직접 쓰면 매번 Pattern 객체를 새로 생성하게 되어 비효율적이다.static boolean isRomanNumeral(String s) {
return s.matches("^(?=.)M*(C[MD]|D?C{0,3})"
+ "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
}
Pattern.compile()이 호출되며, 매번 새로운 Pattern 인스턴스가 생성된다.isRomanNumeral이 자주 호출되면 불필요한 Pattern 객체가 계속 생성되어 성능이 저하된다. 아래의 이미지는 matches 메서드가 어떻게 작성되어있는지 확인하기 위해서 자바 코드를 캡쳐해온 것이다.

성능을 개선하기 위해서 불변인 Pattern 인스턴스를 클래스 초기화(정적 초기화) 과정에서 직접 생성해두고, 메서드를 호출할 때 인스턴스를 재사용하도록 한다.
static boolean isRomanNumeral(String s) {
private static final Pattern ROMAN = Pattern.compile(
"^(?=.)M*(C[MD]|D?C{0,3})"
+ "(X[CL]|L?X{0,3})(I[XV]|V?I{0,3})$");
static boolean isRomanNumeral(String s) {
return ROMAN.matcher(s).matches();
}
}
// Target 인터페이스
interface MediaPlayer {
void play(String audioType, String fileName);
}
// Adaptee 클래스 (기존 기능 제공)
class AdvancedMediaPlayer {
void playMp3(String fileName) {
System.out.println("Playing MP3 file: " + fileName);
}
}
// Adapter 클래스(Target을 상속)
class MediaAdapter implements MediaPlayer {
private final AdvancedMediaPlayer advancedMediaPlayer; // 기존 기능 포함
public MediaAdapter() {
this.advancedMediaPlayer = new AdvancedMediaPlayer(); // 기존 객체 재사용
}
@Override
public void play(String audioType, String fileName) {
if ("mp3".equalsIgnoreCase(audioType)) {
advancedMediaPlayer.playMp3(fileName); // 기존 기능 내부 호출
}
}
}
// 클라이언트 코드
public class AdapterPatternExample {
public static void main(String[] args) {
MediaPlayer player = new MediaAdapter(); // 어댑터 사용
player.play("mp3", "song.mp3"); // 내부에서 기존 AdvancedMediaPlayer를 사용
}
}
MediaAdapter가 MediaPlayer 인터페이스를 구현하고, AdvancedMediaPlayer 객체를 내부에서 호출하는 방식이다.AdvancedMediaPlayer 기능을 재사용하면서도 MediaPlayer 인터페이스를 지원할 수 있다. MediaPlayer 인터페이스만 보고 사용하면 내부에서 어떤 객체를 쓰는지 신경 쓸 필요 없게 된다.