소개
이 랩에서는 Java 프로그래밍에서 불변 (immutable) 문자열을 사용하는 이점에 대해 배웁니다. Java 에서 문자열이 기본적으로 불변인 이유와 이 특성이 애플리케이션의 전반적인 성능과 보안을 어떻게 향상시킬 수 있는지 이해하게 될 것입니다. 이 랩이 끝나면 Java 프로그램에서 불변 문자열을 생성하고 효과적으로 사용하는 방법을 알게 될 것입니다.
이 랩에서는 Java 프로그래밍에서 불변 (immutable) 문자열을 사용하는 이점에 대해 배웁니다. Java 에서 문자열이 기본적으로 불변인 이유와 이 특성이 애플리케이션의 전반적인 성능과 보안을 어떻게 향상시킬 수 있는지 이해하게 될 것입니다. 이 랩이 끝나면 Java 프로그램에서 불변 문자열을 생성하고 효과적으로 사용하는 방법을 알게 될 것입니다.
이 단계에서는 ~/project 디렉토리에 ImmutableStringDemo.java라는 새 Java 파일을 생성합니다. 이 파일에는 이 랩의 모든 Java 코드가 포함됩니다.
먼저 ~/project 디렉토리에 있는지 확인합니다. pwd 명령을 사용하여 현재 디렉토리를 확인할 수 있습니다.
pwd
다음과 유사한 출력이 표시되어야 합니다.
/home/labex/project
이제 touch 명령을 사용하여 ImmutableStringDemo.java 파일을 생성합니다.
touch ImmutableStringDemo.java
파일을 생성한 후 WebIDE 편집기에서 열어 Java 코드를 작성할 수 있습니다.
Java 에서 String 객체는 불변 (immutable) 입니다. 즉, 생성된 후에는 값을 변경할 수 없습니다. 이러한 설계 선택은 보안, 스레드 안전성 및 문자열 풀을 통한 성능 최적화를 포함한 여러 이점을 제공합니다.
문자열 풀 (String pool) 은 힙 (heap) 내의 특별한 메모리 영역으로, 문자열 리터럴 (literal) 을 저장합니다. 문자열 리터럴을 생성할 때 Java 는 먼저 풀에 동일한 문자열이 이미 존재하는지 확인합니다. 존재한다면 기존 문자열의 참조가 반환되어 메모리가 절약됩니다. 존재하지 않는다면 풀에 새 문자열이 생성되고 해당 참조가 반환됩니다.
Java 클래스의 기본 구조를 추가하고 문자열 생성을 시연해 보겠습니다. WebIDE 에서 ImmutableStringDemo.java를 열고 다음 코드를 추가합니다.
// ~/project/ImmutableStringDemo.java
public class ImmutableStringDemo {
public static void main(String[] args) {
// 문자열 리터럴 생성
String name = "LabexUser";
System.out.println("Initial name: " + name);
// 문자열 풀을 사용할 수 있는 문자열 생성
String str1 = "Hello";
String str2 = "Hello"; // 이것은 문자열 풀에서 str1 과 동일한 객체를 참조할 가능성이 높습니다.
System.out.println("str1: " + str1);
System.out.println("str2: " + str2);
System.out.println("str1 == str2: " + (str1 == str2)); // 동일한 객체를 참조하는지 확인
// 'new' 키워드를 사용하여 문자열 생성
// 이것은 리터럴이 풀에 존재하더라도 항상 힙에 새 객체를 생성합니다.
String str3 = new String("Hello");
System.out.println("str3: " + str3);
System.out.println("str1 == str3: " + (str1 == str3)); // 동일한 객체를 참조하는지 확인
}
}
코드를 추가한 후 파일을 저장합니다.
이제 Java 프로그램을 컴파일하고 실행하여 출력을 확인합니다. 터미널에서 다음 명령을 실행합니다.
javac ImmutableStringDemo.java
java ImmutableStringDemo
다음과 유사한 출력이 표시되어야 합니다.
Initial name: LabexUser
str1: Hello
str2: Hello
str1 == str2: true
str3: Hello
str1 == str3: false
이 출력은 str1과 str2가 문자열 풀에서 동일한 객체를 참조하는 반면, str3(new String()으로 생성됨) 은 내용이 동일하더라도 힙에 별도의 객체임을 보여줍니다.
불변성 (Immutability) 은 Java 에서 보안 및 스레드 안전성을 위해 매우 중요합니다. 문자열이 불변이면, 생성 후 프로그램의 어떤 부분에서도 해당 값을 변경할 수 없습니다. 이는 악성 코드가 비밀번호나 URL 과 같은 민감한 데이터를 저장하는 문자열을 변경하는 것을 방지합니다.
또한, 불변 객체는 본질적으로 스레드 안전합니다. 여러 스레드가 외부 동기화 없이 불변 문자열에 동시에 접근할 수 있습니다. 이는 한 스레드가 문자열을 수정하는 동안 다른 스레드가 읽는 위험이 없기 때문입니다.
문자열에 final 키워드를 추가하고 간단한 스레드 안전 예제를 만들어 이를 시연해 보겠습니다.
ImmutableStringDemo.java 파일을 다음과 같이 수정합니다. ThreadSafeTask 클래스에서 접근할 수 있도록 클래스 수준에서 final 문자열을 선언할 것입니다.
// ~/project/ImmutableStringDemo.java
import java.util.HashMap;
public class ImmutableStringDemo {
// 보안 및 스레드 안전성을 위한 final 문자열 선언
private static final String SECURE_PASSWORD = "MySecurePassword123";
// 스레드 안전성을 시연하기 위한 내부 클래스
static class ThreadSafeTask implements Runnable {
private final String password; // 비밀번호는 불변 문자열로 전달됩니다.
public ThreadSafeTask(String password) {
this.password = password;
}
@Override
public void run() {
System.out.println("Thread " + Thread.currentThread().getName() + " accessing password: " + password);
// 작업 시뮬레이션
try {
Thread.sleep(50);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public static void main(String[] args) {
// ... (이전의 name, str1, str2, str3 코드는 여기에 유지됩니다.) ...
String name = "LabexUser";
System.out.println("Initial name: " + name);
String str1 = "Hello";
String str2 = "Hello";
System.out.println("str1: " + str1);
System.out.println("str2: " + str2);
System.out.println("str1 == str2: " + (str1 == str2));
String str3 = new String("Hello");
System.out.println("str3: " + str3);
System.out.println("str1 == str3: " + (str1 == str3));
System.out.println("\n--- 보안 및 스레드 안전성 시연 ---");
// final 문자열 사용
System.out.println("Secure Password: " + SECURE_PASSWORD);
// 불변 비밀번호로 스레드 안전성 시연
for (int i = 0; i < 3; i++) {
Thread thread = new Thread(new ThreadSafeTask(SECURE_PASSWORD), "Thread-" + (i + 1));
thread.start();
}
// 스레드가 완료될 때까지 대기 (선택 사항, 더 깔끔한 출력을 위해)
try {
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
참고: final 키워드는 SECURE_PASSWORD 변수에 한 번만 할당될 수 있음을 보장합니다. String 자체가 불변이므로, 이는 SECURE_PASSWORD의 내용을 변경할 수 없게 만듭니다. ThreadSafeTask 클래스는 여러 스레드에서 이 불변 문자열에 안전하게 접근합니다.
파일을 저장합니다. 그런 다음 프로그램을 다시 컴파일하고 실행합니다.
javac ImmutableStringDemo.java
java ImmutableStringDemo
스레드 접근 메시지가 비결정적인 순서로 나타나는 것을 포함하여 다음과 유사한 출력이 표시되어야 합니다.
Initial name: LabexUser
str1: Hello
str2: Hello
str1 == str2: true
str3: Hello
str1 == str3: false
--- Demonstrating Security and Thread Safety ---
Secure Password: MySecurePassword123
Thread Thread-1 accessing password: MySecurePassword123
Thread Thread-2 accessing password: MySecurePassword123
Thread Thread-3 accessing password: MySecurePassword123
문자열의 불변성은 HashMap 및 HashSet과 같은 해시 기반 컬렉션에서 키로 사용하는 데 이상적입니다. 문자열의 값은 절대 변경되지 않으므로, hashCode()를 한 번만 계산하여 캐싱할 수 있습니다. 이후 hashCode() 호출 시 캐싱된 값을 반환하여, 특히 문자열이 키로 자주 사용될 때 상당한 성능 향상을 가져옵니다.
만약 문자열이 가변적이라면, HashMap에 삽입된 후 해시코드가 변경될 수 있어 원래 해시코드를 사용하여 객체를 검색하는 것이 불가능해집니다.
HashMap에서 문자열 사용을 시연하기 위해 ImmutableStringDemo.java에 코드를 추가해 보겠습니다.
스레드 안전성 시연 후, ImmutableStringDemo.java 파일의 main 메서드에 다음 코드를 추가합니다.
// ~/project/ImmutableStringDemo.java
import java.util.HashMap; // 이 import 문이 파일 상단에 있는지 확인하세요.
public class ImmutableStringDemo {
private static final String SECURE_PASSWORD = "MySecurePassword123";
static class ThreadSafeTask implements Runnable {
private final String password;
public ThreadSafeTask(String password) {
this.password = password;
}
@Override
public void run() {
System.out.println("Thread " + Thread.currentThread().getName() + " accessing password: " + password);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public static void main(String[] args) {
// ... (이전의 name, str1, str2, str3 및 스레드 안전성 코드는 여기에 유지됩니다.) ...
String name = "LabexUser";
System.out.println("Initial name: " + name);
String str1 = "Hello";
String str2 = "Hello";
System.out.println("str1: " + str1);
System.out.println("str2: " + str2);
System.out.println("str1 == str2: " + (str1 == str2));
String str3 = new String("Hello");
System.out.println("str3: " + str3);
System.out.println("str1 == str3: " + (str1 == str3));
System.out.println("\n--- Demonstrating Security and Thread Safety ---");
System.out.println("Secure Password: " + SECURE_PASSWORD);
for (int i = 0; i < 3; i++) {
Thread thread = new Thread(new ThreadSafeTask(SECURE_PASSWORD), "Thread-" + (i + 1));
thread.start();
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("\n--- Demonstrating Hashcode Caching ---");
HashMap<String, Integer> studentScores = new HashMap<>();
String studentName1 = "Alice";
String studentName2 = "Bob";
String studentName3 = "Alice"; // 이것은 문자열 풀의 동일한 "Alice"를 참조합니다.
studentScores.put(studentName1, 95);
studentScores.put(studentName2, 88);
studentScores.put(studentName3, 92); // 이것은 "Alice"의 값을 업데이트합니다.
System.out.println("Student Scores: " + studentScores);
System.out.println("Alice's score: " + studentScores.get("Alice"));
System.out.println("Bob's score: " + studentScores.get("Bob"));
// 문자열을 "변경"하려고 해도 새 문자열이 생성됨을 시연합니다.
String originalString = "Java";
System.out.println("Original String: " + originalString + ", HashCode: " + originalString.hashCode());
String modifiedString = originalString.concat(" Programming"); // 새 문자열 생성
System.out.println("Modified String: " + modifiedString + ", HashCode: " + modifiedString.hashCode());
System.out.println("Original String (after concat): " + originalString + ", HashCode: " + originalString.hashCode());
}
}
파일을 저장합니다. 그런 다음 프로그램을 다시 컴파일하고 실행합니다.
javac ImmutableStringDemo.java
java ImmutableStringDemo
HashMap 및 해시코드 시연을 포함하여 다음과 유사한 출력이 표시되어야 합니다.
Initial name: LabexUser
str1: Hello
str2: Hello
str1 == str2: true
str3: Hello
str1 == str3: false
--- Demonstrating Security and Thread Safety ---
Secure Password: MySecurePassword123
Thread Thread-1 accessing password: MySecurePassword123
Thread Thread-2 accessing password: MySecurePassword123
Thread Thread-3 accessing password: MySecurePassword123
--- Demonstrating Hashcode Caching ---
Student Scores: {Bob=88, Alice=92}
Alice's score: 92
Bob's score: 88
Original String: Java, HashCode: 2301506
Modified String: Java Programming, HashCode: -1479700901
Original String (after concat): Java, HashCode: 2301506
concat이 호출된 후에도 originalString의 해시 코드가 동일하게 유지되는 것을 알 수 있습니다. 이는 concat이 새로운 String 객체를 반환하고 원본은 그대로 두기 때문입니다.
이 실습에서는 Java 프로그래밍에서 불변 문자열 (immutable Strings) 의 근본적인 개념을 학습했습니다. 문자열이 기본적으로 불변인 이유와 이러한 설계 선택이 문자열 풀 (String pool) 을 통한 메모리 최적화에 어떻게 기여하는지 살펴보았습니다. 또한, 민감한 데이터에 대한 보안 강화, 동시 작업에 대한 내재된 스레드 안전성, 해시코드 캐싱으로 인한 해시 기반 컬렉션에서의 성능 향상 등 불변성의 중요한 이점을 이해했습니다. 실습 과제를 완료함으로써 Java 애플리케이션에서 불변 문자열을 효과적으로 생성하고 활용하는 실질적인 경험을 쌓았습니다.