-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathStringImmutability.java
More file actions
161 lines (148 loc) · 6.27 KB
/
Copy pathStringImmutability.java
File metadata and controls
161 lines (148 loc) · 6.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
package Phase2_MethodsArraysStrings.Strings;
/**
* Why Strings Are Immutable
* -------------------------
* In Java a String object cannot be CHANGED after creation. Methods like
* `toUpperCase()`, `substring(...)`, `concat(...)` always RETURN A NEW STRING
* instead of mutating the receiver. The original object is untouched.
* <p>
*
* String s = "java";
* s.toUpperCase(); // returns "JAVA" but ignored
* System.out.println(s); // still prints "java"
* <p>
*
* s = s.toUpperCase(); // now s points to the NEW "JAVA"
* <p>
*
* Reasons Java's Designers Chose Immutability
* -------------------------------------------
* <p>
*
* 1. STRING POOL / Memory Sharing
* - If a literal could change, two variables pointing to the same pool
* object would surprise each other. Immutability lets the JVM safely
* cache and share literals.
* <p>
*
* 2. THREAD SAFETY
* - Immutable objects are inherently thread-safe; they need no locks.
* Strings can be passed between threads with no risk of mutation.
* <p>
*
* 3. SECURITY
* - Many security-critical APIs accept Strings: class names, file paths,
* URLs, network endpoints, SQL fragments, login credentials. If they
* were mutable, attackers could change the value AFTER the security
* check but BEFORE the use ("time-of-check vs time-of-use" attacks).
* <p>
*
* 4. HASHCODE CACHING
* - String hashCode is computed once and stored. HashMap / HashSet lookups
* stay fast and correct because the hash never changes.
* <p>
*
* 5. CLASS LOADING
* - Class names in Java are Strings. Immutability is the simplest way to
* guarantee they never change between load and use.
* <p>
*
* 6. SAFE TO PASS AROUND
* - No defensive copies needed when accepting or returning a String. The
* receiver cannot break the sender's invariants.
* <p>
*
* What "Immutable" Actually Means
* -------------------------------
* - The byte[] backing field is `private final`.
* - All "mutator" methods return a NEW String; none modify in place.
* - You CAN reassign a String variable - that just rebinds the name to
* another (possibly new) String.
* - Reflection could technically reach in and overwrite the final array,
* but doing so violates the JVM specification and breaks the language
* guarantees - don't do it.
* <p>
*
* Demonstration
* -------------
* The main() below shows:
* - mutator methods do NOT change the original
* - pool sharing is safe because of immutability
* - hash code stays consistent for a given content
* - passing a String to a method cannot mutate the caller's value
*/
public class StringImmutability {
public static void main(String[] args) {
// ============================================================
// 1. "Mutator" methods return new objects
// ============================================================
String s = "java";
s.toUpperCase(); // result thrown away!
System.out.println("s after toUpperCase() ignored = " + s); // "java"
String upper = s.toUpperCase(); // capture the new String
System.out.println("s = " + s); // "java"
System.out.println("upper = " + upper); // "JAVA"
System.out.println("same object ? " + (s == upper)); // false
// ============================================================
// 2. Pool sharing is safe because Strings cannot change
// ============================================================
String a = "Hello";
String b = "Hello";
System.out.println("\na == b ? " + (a == b)); // true - shared
// If `a` could be mutated, this sharing would be unsafe.
// ============================================================
// 3. Hash code is cached; for the same content it never changes
// ============================================================
String key = "compute-once";
int h1 = key.hashCode();
int h2 = key.hashCode();
System.out.println("hashCode stable ? " + (h1 == h2)); // true
System.out.println("hashCode value = " + h1);
// ============================================================
// 4. Methods cannot mutate the caller's String
// ============================================================
String name = "deepak";
tryToMutate(name);
System.out.println("\nname after tryToMutate = " + name); // still "deepak"
// ============================================================
// 5. The "loop concatenation" trap is a consequence of immutability
// ============================================================
// Every iteration creates a NEW String. For long loops this is O(n^2)
// - use StringBuilder instead (see StringConcatenation.java).
String acc = "";
for (int i = 0; i < 5; i++) {
acc += i; // builds "0", "01", "012", ...
}
System.out.println("\nacc after loop = " + acc);
// ============================================================
// 6. final reference vs final contents - same idea applies
// ============================================================
final String fixed = "I cannot change";
// fixed = "..."; // ERROR - cannot reassign
// fixed.setCharAt(0,'X')// no such method exists
System.out.println("fixed = " + fixed);
// OUTPUT
// s after toUpperCase() ignored = java
// s = java
// upper = JAVA
// same object ? false
//
// a == b ? true
// hashCode stable ? true
// hashCode value = 1357883693
//
// name after tryToMutate = deepak
//
// acc after loop = 01234
// fixed = I cannot change
}
/**
* Demonstrates that a method CANNOT alter the caller's String. The
* parameter `s` is a local variable holding a copy of the reference;
* reassigning it has no effect on the caller.
*/
static void tryToMutate(String s) {
s = s.toUpperCase(); // changes only the local reference
s = s + "!!!";
}
}