F-lab Java 1μ£Όμ°¨ / Phase 6 / Unit 6.1 본격 νμ΅ μλ£ β Phase 6 μμ!
9-μΉμ λ§μ€ν° ν둬ννΈ νμμΌλ‘ κΉμ΄ νν€μΉλ€.μ μ μ§μ: Phase 4 (JVM λ©λͺ¨λ¦¬), Phase 5 (GC)
λ€μ Unit: 6.2 β StringBuilder vs StringBufferμ΄ Unitμ μλ―Έ: μλ°μμ κ°μ₯ νΉλ³ν ν΄λμ€ β String.
λ©΄μ μ΅λ¨κ³¨ (==vsequals,new String()vs literal) μ μ νν λ΅.
Phase 4-5 μ λ©λͺ¨λ¦¬ μ΄ν΄ μμμ String μ νΉμ λμ°λ₯Ό νμ΄λ.
λμκ΄ μμ€ν μ μμν΄λ³΄μΈμ.
β μ΄κ² String Constant Pool μ λ³Έμ§.
μ΄λ¦νλ νλ² μΈμνλ©΄ λ°κΏ μ μμ΅λλ€:
β String μ λΆλ³μ± (Immutability) μ λ³Έμ§.
"String μ μλ°μμ κ°μ₯ νΉλ³ν ν΄λμ€ β λΆλ³ + κ³΅μ© ν (Pool) μΌλ‘ λ©λͺ¨λ¦¬ ν¨μ¨κ³Ό μμ μ±μ λμμ ν보νλ€."
λΉμ μ 리:
| λΉμ | μλ° κ°λ | μλ―Έ |
|---|---|---|
| κ³΅μ© λμκ΄ | String Constant Pool | κ°μ λ¬Έμμ΄μ 1κ°λ§ |
| μ΄λ¦ν | Immutable String | νλ² λ§λ€λ©΄ λ³κ²½ λΆκ° |
| λμκ΄ μΉ΄λ | μ°Έμ‘° (Reference) | κ°μ μ± κ°λ¦¬ν΄ |
| μ μ± μΈμ | new String() | Pool μ°ν, Heap μ μλ‘ |
μλ°μμ κ°μ₯ λ§μ΄ μ°λ κ°μ²΄ = String.
// μΌμ μ½λμ 90%
String name = "Alice";
String email = "alice@example.com";
String status = "ACTIVE";
log.info("μ²λ¦¬ μλ£: " + result);
λ§μ½ λͺ¨λ String μ΄ μΌλ° κ°μ²΄μ²λΌ λμνλ€λ©΄?
List<Customer> customers = customerRepository.findAll(); // 1λ§ λͺ
for (Customer c : customers) {
if (c.getStatus().equals("ACTIVE")) { // "ACTIVE" λ§€λ² μ κ°μ²΄?
// ...
}
}
Pool μλ μΈμ:
Pool μλ μΈμ:
// κ°μ μλλ¦¬μ€ β λ§μ½ String μ΄ κ°λ³μ΄λΌλ©΄
String userId = "alice";
Thread1: userId += "_admin"; // "alice_admin"
Thread2: log.info(userId); // μ΄λ€ κ°?
κ°λ³ String μ΄λΌλ©΄:
λΆλ³ String μ΄λΌλ©΄:
// 보μ κ²μ¦ μμ
public void connectDB(String url, String username, String password) {
if (isValidUser(username, password)) {
// κ²μ¦ ν΅κ³Ό
db.connect(url, username, password);
}
}
κ°λ³ String μ΄λΌλ©΄:
isValidUser() νΈμΆ ν β λ€λ₯Έ μ€λ λκ° password λ³κ²½λΆλ³ String μ΄λΌλ©΄:
Map<String, Customer> cache = new HashMap<>();
cache.put("alice", new Customer("Alice"));
// ...
Customer c = cache.get("alice");
κ°λ³ String μ΄λΌλ©΄:
λΆλ³ String μ΄λΌλ©΄:
μλ°λ λ κ°μ§λ₯Ό λμμ μ μ©:
| Java λ²μ | SCP μμΉ | λΉκ³ |
|---|---|---|
| Java 1~6 | PermGen | λ©λͺ¨λ¦¬ μ ν, OOM μν |
| Java 7 | Heap (Old Gen) | GC λμ β |
| Java 8+ | Heap | PermGen β Metaspace |
Java 7 μ λ³ν μμ:
"String μ νΉμ λμ° = μλ°μ κ°μ₯ μ리ν μ€κ³ κ²°μ μ€ νλ."
λΉλ²νκ² μ¬μ©λλ String μ νΉμ±μ μΈμνκ³ , λΆλ³μ± + Pool λ κ°μ§λ‘ λ©λͺ¨λ¦¬/μ±λ₯/보μ λͺ¨λ ν΄κ²°. μλ°μ λμμΈ μ² ν ("κ°λ°μκ° μ€μνκΈ° μ΄λ ΅κ² + μμ€ν μ΄ μμμ ν¨μ¨") μ μ μ.
// ILIC μ½λ
public class CustomerService {
public boolean isVipCustomer(Customer customer) {
// β λ²κ·Έ κ°λ₯ μ½λ
if (customer.getGrade() == "VIP") {
return true;
}
return false;
}
}
λ¬Έμ :
== λ μ°Έμ‘° λΉκ΅ (λ©λͺ¨λ¦¬ μ£Όμ)customer.getGrade() κ° DB μμ κ°μ Έμ¨ κ°μ΄λΌλ©΄ β Pool μ "VIP" μ λ€λ₯Έ κ°μ²΄== λΉκ΅ μ€ν¨μ¬λ°λ₯Έ μ½λ:
if ("VIP".equals(customer.getGrade())) { // β equals μ¬μ©
return true;
}
β String Pool μ΄ν΄ λͺ»νλ©΄ μ΄λ° λ²κ·Έ λ°μ.
String a = "hello";
String b = "hello";
String c = new String("hello");
System.out.println(a == b); // ?
System.out.println(a == c); // ?
System.out.println(a.equals(c)); // ?
System.out.println(a == c.intern()); // ?
Pool λͺ¨λ₯΄λ©΄:
Pool μλ©΄:
a == b: true (Pool μ κ°μ κ°μ²΄)a == c: false (c λ Heap μ μ κ°μ²΄)a.equals(c): true (κ° λΉκ΅)a == c.intern(): true (intern() μΌλ‘ Pool μ κ°μ²΄ λ°ν)// β μνν μ½λ
@RestController
public class ApiController {
@PostMapping("/log")
public void logUserAction(@RequestBody String userInput) {
userInput.intern(); // β μ¬μ©μ μ
λ ₯μ Pool μ!
// ...
}
}
λ¬Έμ :
Pool μ΄ν΄νλ©΄:
// 보μ κ²μ¦
public boolean isValidQuery(String userInput) {
if (userInput.contains("DROP TABLE")) {
return false;
}
return true;
}
κ°λ³ String μ΄μλ€λ©΄:
λΆλ³ String μ μμ :
// β λΉν¨μ¨ μ½λ
String result = "";
for (int i = 0; i < 10000; i++) {
result += "item" + i; // λ§€λ² μ String μμ±!
}
λΆλ³μ± λͺ¨λ₯΄λ©΄:
λΆλ³μ± μλ©΄:
// νλ§ μ½λ μ²λ¦¬
List<Cargo> cargos = ...;
for (Cargo c : cargos) {
if (c.getOriginPort() == "BUSAN") { // β μν!
processBusan(c);
}
}
λ²κ·Έ λ°μ μΌμ΄μ€:
== κ° false β λΆμ° μΆλ° νλ¬Ό μ²λ¦¬ μ λ¨μ¬λ°λ₯Έ μ½λ:
if ("BUSAN".equals(c.getOriginPort())) { // β
processBusan(c);
}
| μλλ¦¬μ€ | Pool λͺ¨λ₯΄λ©΄ | Pool μλ©΄ |
|---|---|---|
| equals λ²κ·Έ | μ΄μ μ¬κ³ | μ νν λΉκ΅ |
| λ©΄μ μ§λ¬Έ | νλ½ | μλμ΄ λ΅λ³ |
| intern μ€μ© | OOM | μ μ€ν μ¬μ© |
| 보μ | μ·¨μ½μ | μμ |
| μ±λ₯ | O(nΒ²) | StringBuilder |
| μ΄μ μ½λ | κ°νμ λ²κ·Έ | μμ |
β String μ΄ν΄λ μλ° μλμ΄μ κΈ°λ³Έ.
public final class String {
private final byte[] value; // μ€μ λ¬Έμ λ°μ΄ν°
private final byte coder; // μΈμ½λ© (LATIN1 or UTF16)
private int hash; // μΊμλ hashCode
// ... methods
}
ν΅μ¬ ν¬μΈνΈ:
1. final ν΄λμ€ β μμ λΆκ°
2. private final νλ β μΈλΆμμ λ³κ²½ λΆκ°
3. λͺ¨λ λ©μλ β μ String λ°ν (μμ X)
String s = "hello";
String s2 = s.toUpperCase(); // s λ κ·Έλλ‘
System.out.println(s); // "hello" (λ³νμ§ μμ!)
System.out.println(s2); // "HELLO" (μ String κ°μ²΄)
λͺ¨λ λ³ν λ©μλ λ μ String λ°ν:
toUpperCase(), toLowerCase()trim(), strip()replace(), concat()substring()| μ΄μ | μ€λͺ |
|---|---|
| Thread-Safe | λκΈ°ν λΆνμ |
| HashMap ν€ μμ | hashCode μμν κ°μ |
| 보μ | TOCTOU λ°©μ§ |
| μΊμ± κ°λ₯ | κ°μ κ° = κ°μ κ°μ²΄ |
| String Pool κ°λ₯ | 곡μ ν΄λ μμ |
JVM μμ νΉλ³ν μμ:
[JVM Heap]
βββ Young Generation
βββ Old Generation
β βββ String Constant Pool
β βββ "hello" (κ°μ²΄ 1κ°)
β βββ "world" (κ°μ²΄ 1κ°)
β βββ "VIP" (κ°μ²΄ 1κ°)
βββ Metaspace
String a = "hello"; // 리ν°λ΄ β Pool
String b = "hello"; // κ°μ 리ν°λ΄ β Pool μ κ°μ κ°μ²΄
String c = new String("hello"); // new β Heap μ μ κ°μ²΄
λ©λͺ¨λ¦¬ κ·Έλ¦Ό:
[Stack]
a (μ°Έμ‘°)
b (μ°Έμ‘°) βββ κ°μ κ³³μ κ°λ¦¬ν΄
c (μ°Έμ‘°)
[Heap]
βββ String Constant Pool
β βββ "hello" β a, b κ° μ°Έμ‘°
β
βββ μΌλ° Heap
βββ "hello" β c κ° μ°Έμ‘° (λ³λ κ°μ²΄)
κ²μ¦:
a == b // true (κ°μ Pool κ°μ²΄)
a == c // false (λ€λ₯Έ κ°μ²΄)
a.equals(c) // true (κ° λΉκ΅)
μν : Heap κ°μ²΄λ₯Ό Pool λ‘ κ°μ Έμ€κΈ° (λλ Pool μ κ°μ²΄ λ°ν)
String a = "hello"; // Pool
String c = new String("hello"); // Heap
String d = c.intern(); // Pool μ "hello" λ°ν
System.out.println(a == d); // true
System.out.println(c == d); // false (c λ μ¬μ ν Heap)
λμ μ리:
1. c.intern() νΈμΆ
2. JVM μ΄ Pool μ "hello" κ° μλμ§ νμΈ
3. μμΌλ©΄ κ·Έ κ°μ²΄ λ°ν / μμΌλ©΄ μΆκ° ν λ°ν
== vs equals βββ (λ©΄μ λ¨κ³¨)| λΉκ΅ | == | .equals() |
|---|---|---|
| λΉκ΅ λμ | μ°Έμ‘° (μ£Όμ) | κ° (λ΄μ©) |
| Pool μν₯ | λ°μ | μ λ°μ |
| κΆμ₯ | β | β |
String a = "hello";
String b = new String("hello");
a == b // false (λ€λ₯Έ κ°μ²΄)
a.equals(b) // true (κ°μ κ°)
μμΉ: String λΉκ΅ μ νμ equals() μ¬μ©.
String s = "Hello World";
// === λ³ν (μ String λ°ν) ===
s.toUpperCase() // "HELLO WORLD" (μ κ°μ²΄)
s.toLowerCase() // "hello world"
s.trim() // μμͺ½ 곡백 μ κ±°
s.replace("o", "0") // μΉν
s.substring(0, 5) // "Hello"
s.concat(" !") // "Hello World !"
// === λΉκ΅ ===
s.equals("Hello World") // true
s.equalsIgnoreCase("HELLO WORLD") // true
s.compareTo("Hello") // μμ (μ¬μ μ)
// === κ²μ¬ ===
s.contains("World") // true
s.startsWith("Hello") // true
s.endsWith("ld") // true
s.isEmpty() // false
s.isBlank() // false (κ³΅λ°±λ§ μμ΄λ true) [Java 11+]
// === μ 보 ===
s.length() // 11
s.charAt(0) // 'H'
s.indexOf("World") // 6
String a = "hello"; // μ»΄νμΌ μ Pool μ λ±λ‘ μμ½
String b = "hello"; // κ°μ 리ν°λ΄ β κ°μ Pool κ°μ²΄
// μ»΄νμΌλ .class νμΌμ:
// Constants Pool μ 보 ν¬ν¨
// β ν΄λμ€ λ‘λ© μ String Pool μ μΆκ°
ν΅μ¬: 리ν°λ΄μ ν΄λμ€ λ‘λ© μμ μ Pool μ λ±λ‘.
String c = new String("hello"); // Heap κ°μ²΄
String d = c.intern(); // Pool μ λ±λ‘ (μμΌλ©΄) + λ°ν
JVM λ΄λΆ ꡬν (HotSpot):
StringTable (HashTable)
βββ bucket[0] β "hello" β "world" β ...
βββ bucket[1] β "VIP"
βββ bucket[2] β ...
βββ bucket[N] β ...
κΈ°λ³Έ ν¬κΈ° (Java 8+): 60013 (μμ, μΆ©λ μ΅μν)
μ‘°μ : -XX:StringTableSize=N
+ μ°μ°μμ λμString a = "Hello";
String b = "World";
String c = a + " " + b; // μ΄λ»κ² λμ?
μ»΄νμΌλ¬ λ³ν (Java 8 μ΄μ ):
String c = new StringBuilder()
.append(a)
.append(" ")
.append(b)
.toString();
Java 9+ (Indify):
invokedynamic μΌλ‘ λ ν¨μ¨μ μ²λ¦¬String a = "Hello" + " " + "World"; // μ»΄νμΌ μ ν΄λ©
String b = "Hello World";
System.out.println(a == b); // true!
μ»΄νμΌλ¬ λμ:
"Hello" + " " + "World" β μ»΄νμΌ μ "Hello World" λ‘ λ³νμ£Όμ: λ³μκ° μμ΄λ©΄ ν΄λ© X
String s = "Hello";
String a = s + " World"; // λ°νμ μ°μ°
String b = "Hello World";
System.out.println(a == b); // false
λ¬Έμ : ASCII λ¬Έμμ΄λ UTF-16 μΌλ‘ μ μ₯ β λ©λͺ¨λ¦¬ 2λ°° λλΉ
ν΄κ²° (Java 9+):
public final class String {
private final byte[] value; // byte[] λ‘ λ³κ²½ (Java 8 κΉμ§λ char[])
private final byte coder; // LATIN1 (1 byte/char) or UTF16 (2 bytes/char)
}
ν¨κ³Ό:
μκ°:
곡κ°:
μν:
Java 6 μ΄μ : PermGen β GC κ±°μ μ λ¨
Java 7+: Heap β μ μ GC λμ
쑰건:
μ€λ¬΄: κ·Έλλ λμ intern μ μν.
public class CustomerService {
// β μν: == μ¬μ©
public boolean isVipUnsafe(Customer c) {
return c.getGrade() == "VIP"; // λ²κ·Έ κ°λ₯
}
// β μμ : equals μ¬μ©
public boolean isVipSafe(Customer c) {
return "VIP".equals(c.getGrade()); // null-safe
}
// β λ μ’μ λ°©λ²: Enum μ¬μ©
public boolean isVipBest(Customer c) {
return c.getGrade() == CustomerGrade.VIP; // Enum μ == μμ
}
}
enum CustomerGrade {
NORMAL, SILVER, GOLD, VIP
}
ν:
== λΉκ΅ μμ public class StringPoolDemo {
public static void main(String[] args) {
String a = "hello";
String b = "hello";
String c = new String("hello");
String d = c.intern();
System.out.println("a == b: " + (a == b)); // true
System.out.println("a == c: " + (a == c)); // false
System.out.println("a.equals(c): " + a.equals(c)); // true
System.out.println("a == d: " + (a == d)); // true
System.out.println("c == d: " + (c == d)); // false
// hashCode νμΈ
System.out.println("a hash: " + System.identityHashCode(a));
System.out.println("c hash: " + System.identityHashCode(c));
}
}
public class CompileTimeFoldingDemo {
public static void main(String[] args) {
String a = "Hello" + " " + "World"; // μ»΄νμΌ μ ν΄λ©
String b = "Hello World";
System.out.println(a == b); // true (ν΄λ©λ¨)
// λ³μκ° λ€μ΄κ°λ©΄?
String hello = "Hello";
String c = hello + " World"; // λ°νμ μ°μ°
System.out.println(b == c); // false (Heap κ°μ²΄)
System.out.println(b.equals(c)); // true (κ°μ κ°μ)
}
}
public class LargeStringDemo {
public static void main(String[] args) {
// 1000κ°μ λΉμ·ν String
List<String> strings = new ArrayList<>();
// β λΉν¨μ¨: λ§€λ² new
for (int i = 0; i < 1000; i++) {
strings.add(new String("ACTIVE")); // 1000κ° κ°μ²΄
}
// β ν¨μ¨: literal μ¬μ©
for (int i = 0; i < 1000; i++) {
strings.add("ACTIVE"); // Pool μ 1κ° κ°μ²΄ 곡μ
}
// β λμ λ°μ΄ν°λ₯Ό μ μ€ν intern
Set<String> uniqueDomains = new HashSet<>();
for (Customer c : customers) {
String domain = extractDomain(c.getEmail());
uniqueDomains.add(domain.intern()); // λλ©μΈμ νμ μ
}
}
}
public class PortService {
// ILIC μ νλ§ μ½λ β νμ λ μ§ν© (~μλ°± κ°)
private static final Set<String> KNOWN_PORTS = Set.of(
"BUSAN", "INCHEON", "ULSAN", "TOKYO", "SHANGHAI", "LA"
);
// β Pool νμ© β μμ ν λΉκ΅
public boolean isKoreanPort(String portCode) {
return "BUSAN".equals(portCode)
|| "INCHEON".equals(portCode)
|| "ULSAN".equals(portCode);
}
// β λ μ’μ λ°©λ²: Enum
public enum Port {
BUSAN("KR"), INCHEON("KR"), ULSAN("KR"),
TOKYO("JP"), SHANGHAI("CN"), LA("US");
private final String country;
Port(String country) {
this.country = country;
}
public boolean isKorean() {
return "KR".equals(country);
}
}
}
public class StringComparison {
// β NullPointerException μν
public boolean compareUnsafe(String a, String b) {
return a.equals(b); // a κ° null μ΄λ©΄ NPE!
}
// β μμκ° μμΌλ‘
public boolean compareSafe(String input) {
return "ACTIVE".equals(input); // input μ΄ null μ΄μ΄λ false λ°ν
}
// β Objects.equals (null-safe)
public boolean compareModern(String a, String b) {
return Objects.equals(a, b); // λ λ€ null μ΄λ©΄ true, νμͺ½λ§ null μ΄λ©΄ false
}
// β λμλ¬Έμ 무μ
public boolean caseInsensitive(String a, String b) {
return a != null && a.equalsIgnoreCase(b);
}
}
public class LogService {
// β μν: μ¬μ©μ μ
λ ₯ intern
public void logUserActionDangerous(String userInput) {
userInput.intern(); // OOM μν!
log.info(userInput);
}
// β μμ : intern μ ν¨
public void logUserAction(String userInput) {
log.info(userInput); // Pool μ μ λ£μ
}
// β νμ λ μ§ν©λ§ intern
public void recordEventType(String eventType) {
// μ΄λ²€νΈ νμ
μ νμ μ (μμ κ°)
Set<String> KNOWN_EVENTS = Set.of("LOGIN", "LOGOUT", "PURCHASE");
if (KNOWN_EVENTS.contains(eventType)) {
eventType = eventType.intern(); // μμ
}
// ...
}
}
public class ConcatBenchmark {
// β O(nΒ²) β λ§€λ² μ String
public String slowConcat(List<String> items) {
String result = "";
for (String item : items) {
result += item; // λ§€λ² μ κ°μ²΄!
}
return result;
}
// β O(n) β StringBuilder
public String fastConcat(List<String> items) {
StringBuilder sb = new StringBuilder();
for (String item : items) {
sb.append(item);
}
return sb.toString();
}
// β Stream + Collectors
public String streamConcat(List<String> items) {
return items.stream()
.collect(Collectors.joining());
}
// 100,000 κ° μ²λ¦¬ μ:
// slowConcat: μ λΆ (O(nΒ²))
// fastConcat: ~10ms (O(n))
}
β Unit 6.2 μμ StringBuilder κΉμ΄ λ€λ£Έ.
== λ‘ String λΉκ΅// β κ°μ₯ νν λ²κ·Έ
if (status == "ACTIVE") { ... }
λ¬Έμ : κ°μ²΄ μΆμ²μ λ°λΌ κ²°κ³Ό λ€λ¦.
ν΄κ²°: νμ equals() μ¬μ©.
if ("ACTIVE".equals(status)) { ... } // null-safe
String input = null;
if (input.equals("hello")) { ... } // NullPointerException!
ν΄κ²° 1: μμλ₯Ό μμ
if ("hello".equals(input)) { ... } // null μ΄λ©΄ false
ν΄κ²° 2: Objects.equals
if (Objects.equals(input, "hello")) { ... }
@PostMapping("/log")
public void log(@RequestBody String userInput) {
userInput.intern(); // β OOM μν
}
λ¬Έμ : 무ν λ€μν μ λ ₯ β Pool νλ°.
ν΄κ²°: intern μ νμ λ λ°μ΄ν°μλ§.
// β λΉν¨μ¨
String result = "";
for (int i = 0; i < 100000; i++) {
result += data[i]; // O(nΒ²)
}
ν΄κ²°: StringBuilder
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100000; i++) {
sb.append(data[i]);
}
String result = sb.toString(); // O(n)
μλ μλ° (~Java 6):
String huge = "..."; // 100MB
String small = huge.substring(0, 10); // 10μ
huge = null;
// κ·Έλ¬λ small μ΄ huge μ char[] μ°Έμ‘° β 100MB λͺ» νμ!
Java 7+: substring μ΄ μ char[] λ³΅μ¬ β ν΄κ²°λ¨.
byte[] bytes = "νκΈ".getBytes(); // β νλ«νΌ κΈ°λ³Έ charset
String s = new String(bytes); // λ€λ₯Έ νλ«νΌμμ κΉ¨μ§
ν΄κ²°:
byte[] bytes = "νκΈ".getBytes(StandardCharsets.UTF_8);
String s = new String(bytes, StandardCharsets.UTF_8);
// β λ¬Έμμ΄ μμ λ¨μ©
public class Constants {
public static final String STATUS_ACTIVE = "ACTIVE";
public static final String STATUS_INACTIVE = "INACTIVE";
public static final String STATUS_PENDING = "PENDING";
}
// μ¬μ©
if (Constants.STATUS_ACTIVE.equals(c.getStatus())) { ... }
λ¬Έμ :
ν΄κ²°: Enum
public enum Status {
ACTIVE, INACTIVE, PENDING;
}
if (c.getStatus() == Status.ACTIVE) { ... } // == μμ
[Unit 6.1: String + Constant Pool] β μ§κΈ μ¬κΈ° β
β
[Unit 6.2: StringBuilder vs StringBuffer] (λ€μ)
β
[Unit 6.3: ArrayList vs LinkedList]
β
[Unit 6.4: HashMap λ΄λΆ ꡬ쑰] β
β
β
β
[Unit 6.5: TreeMap, LinkedHashMap]
| Phase | μ°κ²° |
|---|---|
| 4.1 (JVM λ©λͺ¨λ¦¬) | Pool μ μμΉ (Heap, Old Gen) |
| 4.2 (Pass by Value) | String μ°Έμ‘° μ λ¬ |
| 5.1 (GC) | Pool λ GC λμ (Java 7+) |
| 5.2 (Heap ꡬ쑰) | Pool μ΄ Old Gen μ μμΉ |
| μλ£κ΅¬μ‘° | String νμ© |
|---|---|
| HashMap | ν€λ‘ μμ£Ό μ¬μ© β λΆλ³μ± νμ |
| HashSet | μ€λ³΅ μ κ±° β equals + hashCode |
| TreeMap | μ λ ¬ ν€ β compareTo |
| List | μμλ‘ μμ£Ό μ¬μ© |
| μΈμ΄ | String νΉμ± |
|---|---|
| Java | λΆλ³ + Pool |
| C# | λΆλ³ + Pool (Java μ λΉμ·) |
| Python | λΆλ³ + μΌλΆ μμ string interning |
| JavaScript | λΆλ³ (primitive) |
| C++ | κ°λ³ (std::string) |
| Rust | λΆλ³ (&str) + κ°λ³ (String) |
β μλ°μ μ νμ νμ€μ .
| μ§λ¬Έ | μ΄ Unit μμμ λ΅ |
|---|---|
== vs equals? | μ°Έμ‘° vs κ° λΉκ΅ |
new String("a") vs "a"? | Heap vs Pool |
| String μ΄ μ λΆλ³? | 4κ°μ§ μ΄μ (μ€λ λ/보μ/HashMap/μΊμ±) |
| intern() μΈμ μ°λ? | νμ λ λ°μ΄ν°, μ μ€νκ² |
| Pool μμΉλ? | Java 7+ Heap |
κ°μ Phase:
λ―Έλ μ£Όμ°¨:
1οΈβ£ String μ λΆλ³ (Immutable) + Pool λ‘ νΉμ λμ° λ°λ ν΄λμ€λ€.
finalν΄λμ€ +private finalνλλ‘ λ³κ²½ λΆκ°. λͺ¨λ λ³ν λ©μλλ μ String λ°ν. μ΄λ‘μ¨ Thread-Safe, HashMap ν€ μμ , 보μ, μΊμ± κ°λ₯ 4κ°μ§ μ΄μ ν보. λΉλ²νκ² μ¬μ©λλ String μ νΉμ±μ μΈμν μλ°μ μ리ν μ€κ³.2οΈβ£ String Constant Pool μ κ°μ 리ν°λ΄μ 1κ° κ°μ²΄λ‘ 곡μ νλ€.
리ν°λ΄ (
"hello") μ Pool μ λ±λ‘ β κ°μ 리ν°λ΄μ κ°μ κ°μ²΄ 곡μ .new String("hello")λ Heap μ μ κ°μ²΄.a == bλ μ°Έμ‘° λΉκ΅ β Pool μν₯ λ°μ.a.equals(b)λ κ° λΉκ΅ β νμ μμ . Java 7+ μμ Pool μμΉλ Heap (Old Gen) β GC κ°λ₯.3οΈβ£ μ€λ¬΄ μμΉ β equals μ¬μ©, λμ λ°μ΄ν° intern X, Enum νμ©.
==λ ν¨μ β νμequals()(λλObjects.equals). μ¬μ©μ μ λ ₯ λ± λμ λ°μ΄ν° intern() κΈμ§ (OOM μν). νμ λ λ¬Έμμ΄ μμλ Enum μΌλ‘ λ체 (μ»΄νμΌ νμ μμ +==λΉκ΅ κ°λ₯). ν° λ¬Έμμ΄ μ°κ²°μ StringBuilder (Unit 6.2). ILIC μ λ±κΈ/νλ§ μ½λ κ°μ λΉκ΅μμ μ΄ μμΉ μ§ν€λ©΄ μ΄μ μ¬κ³ λ°©μ§.
new String() μ μ°¨μ΄λ₯Ό λ©λͺ¨λ¦¬ κ·Έλ¦ΌμΌλ‘ 그릴 μ μλ€== μ equals() μ μ°¨μ΄λ₯Ό μ νν μλ€equals λλ Objects.equals μ¬μ©== vs equals λ΅λ³ κ°λ₯String s = "a" vs String s = new String("a") λ©λͺ¨λ¦¬ κ·Έλ¦ΌQ1: String s = "hello" μ String s = new String("hello") μ μ°¨μ΄λ₯Ό λ©λͺ¨λ¦¬ κ·Έλ¦ΌμΌλ‘ κ·Έλ €λ³΄λΌ.
ν μ€ λ΅: 리ν°λ΄μ Pool κ°μ²΄ μ°Έμ‘°, new λ Heap μ μ κ°μ²΄ λ₯Ό κ°λ¦¬ν΄.
μμΈ μ€λͺ :
String a = "hello";
String b = "hello";
λ©λͺ¨λ¦¬ μν:
[Stack]
a (μ°Έμ‘°) βββ
b (μ°Έμ‘°) βββ€
β
[Heap] βΌ
βββ String Constant Pool
β βββ "hello" β a, b λ λ€ μ¬κΈ°λ₯Ό κ°λ¦¬ν΄
β
βββ (Pool μΈλΆ μμ λΉμ΄μμ)
ν΅μ¬:
"hello" κ° .class νμΌμ Constants Pool μ κΈ°λ‘a == b β trueString c = new String("hello");
String d = new String("hello");
λ©λͺ¨λ¦¬ μν:
[Stack]
c (μ°Έμ‘°)
d (μ°Έμ‘°)
[Heap]
βββ String Constant Pool
β βββ "hello" β c, d κ° κ°λ¦¬ν€μ§ μμ!
β
βββ μΌλ° Heap
βββ String κ°μ²΄ #1 ("hello") β c κ° κ°λ¦¬ν΄
βββ String κ°μ²΄ #2 ("hello") β d κ° κ°λ¦¬ν΄
ν΅μ¬:
new λ 무쑰건 Heap μ μ κ°μ²΄ μμ±new λ§λ€ μ κ°μ²΄ β λ©λͺ¨λ¦¬ λλΉc == d β falseString a = "hello"; // Pool
String c = new String("hello"); // Heap
String d = c.intern(); // Pool μ "hello" λ°ν
λ©λͺ¨λ¦¬ μν:
[Stack]
a (μ°Έμ‘°) ββββββ
c (μ°Έμ‘°) ββββββββββ
d (μ°Έμ‘°) ββββββ€ β
β β
[Heap] βΌ β
βββ String Pool β
β βββ "hello" β a, d κ° κ°λ¦¬ν΄
β
βββ μΌλ° Heap β
βββ "hello" β c κ° κ°λ¦¬ν΄
κ²μ¦:
a == c β false (λ€λ₯Έ κ°μ²΄)a == d β true (κ°μ Pool κ°μ²΄)c == d β false (c λ Heap, d λ Pool)a.equals(c) β true (κ° λΉκ΅)Pool = λμκ΄:
new String() μ¬μ©μ = κ°μ μ±
μλ‘ μΈμ (λ³λ 보κ΄)intern() = μκΈ° μ±
λ€κ³ λμκ΄ κ°μ κ°μ μ±
μΉ΄λλ‘ κ΅νλ©λͺ¨λ¦¬:
new String 1λ§ λ² = 1λ§ κ° κ°μ²΄λΉκ΅:
== λ μμΉμ λ°λΌ κ²°κ³Ό λ€λ¦equals λ νμ μμ κΆμ₯:
new String("...") μ κ±°μ μ¬μ© μ ν¨String.valueOf() μ¬μ©Q2: μ String μ λΆλ³ (Immutable) μΈκ°?
ν μ€ λ΅: Thread-Safe / HashMap ν€ μμ μ± / 보μ / μΊμ± 4κ°μ§ μ΄μ .
μμΈ μ€λͺ :
κ°λ³ String μ λ¬Έμ :
// κ°μ μ½λ (μ€μ μλ° X)
String userId = "alice";
// Thread 1
userId += "_admin";
// Thread 2
log.info("User: " + userId); // "alice" λλ "alice_admin"?
λΆλ³ String μ μμ :
String userId = "alice";
// Thread 1
String adminId = userId + "_admin"; // μ String
// Thread 2
log.info("User: " + userId); // νμ "alice"
β λκΈ°ν μμ΄ μμ .
κ°λ³ String μ λ¬Έμ :
Map<String, Customer> cache = new HashMap<>();
String key = "alice";
cache.put(key, alice);
// κ°μ: key κ° κ°λ³μ΄λΌ λ³κ²½λ¨
// key.append("_modified"); // hashCode λ³κ²½!
cache.get(key); // null λ°ν! (μλͺ»λ λ²ν· κ²μ)
λΆλ³ String μ μμ :
String key = "alice"; // hashCode μμν κ°μ
cache.put(key, alice);
cache.get("alice"); // μ μ λμ
β HashMap, HashSet μ μ νμ± λ³΄μ₯.
κ°λ³ String μ μν:
public void connectDB(String url) {
// 1. κ²μ¦
if (isValidUrl(url)) {
// 2. μ¬μ© (κ²μ¦κ³Ό μ¬μ© μ¬μ΄μ λ€λ₯Έ μ€λ λκ° url λ³κ²½ κ°λ₯)
connect(url); // μ
μμ URL!
}
}
λΆλ³ String μ μμ :
λΆλ³μ΄λΌμ κ°λ₯ν κ²:
String a = "hello"; // Pool
String b = "hello"; // Pool μ κ°μ κ°μ²΄
// a λλ b λ₯Ό ν΅ν΄ "hello" λ₯Ό μμ νλ€λ©΄?
// β λ€λ₯Έ λͺ¨λ κ³³μ μν₯ β μ¬μ!
β λΆλ³μ΄κΈ° λλ¬Έμ Pool κ°λ₯.
public final class String {
private int hash; // μΊμ
@Override
public int hashCode() {
int h = hash;
if (h == 0 && value.length > 0) {
// ν λ²λ§ κ³μ°
h = computeHashCode();
hash = h; // μΊμ±
}
return h;
}
}
| μΈ‘λ©΄ | κ°λ³ String | λΆλ³ String (Java) |
|---|---|---|
| Thread μμ μ± | λκΈ°ν νμ | μλ μμ |
| HashMap μ¬μ© | μν | μμ |
| 보μ | TOCTOU μν | μμ |
| Pool κ°λ₯ | X | β |
| hashCode μΊμ± | X | β |
| λ©λͺ¨λ¦¬ | μ μ½ κ°λ₯ | Pool λ‘ μ μ½ |
| μ±λ₯ | μμ λΉ λ¦ | λ³ν μ μ κ°μ²΄ (λλ¦Ό) |
β μλ°λ μμ + Pool μ μ ν. κ°λ³μ΄ νμνλ©΄ StringBuilder μ¬μ©.