copying: deep vs shallow copy module (clone() traps, copy constructors, serialization copy, records, defensive copies, collection copies), 17 contract tests
Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_017ic4b4X2xx7RrveVQU9R1N
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
== Identity before any change ==
|
||||
alias == original : true
|
||||
shallow == original : false
|
||||
shallow.address == original.address : true
|
||||
shallow.tags == original.tags : true
|
||||
deep.address == original.address : false
|
||||
deep.tags == original.tags : false
|
||||
|
||||
== Now mutate the ORIGINAL (name, city, tags) ==
|
||||
original : Asha K | Pune | [java, spring, jpa]
|
||||
alias : Asha K | Pune | [java, spring, jpa]
|
||||
shallow : Asha | Pune | [java, spring, jpa]
|
||||
deep : Asha | Mumbai | [java, spring]
|
||||
@@ -0,0 +1,5 @@
|
||||
$ javac src/broken/FinalFieldClone.java
|
||||
src/broken/FinalFieldClone.java:17: error: cannot assign a value to final variable items
|
||||
copy.items = new ArrayList<>(items); // error: cannot assign a value to final variable
|
||||
^
|
||||
1 error
|
||||
@@ -0,0 +1,24 @@
|
||||
== 1. clone() does not call a constructor, and it is shallow ==
|
||||
Team constructor ran for 'platform'
|
||||
b == a : false
|
||||
b.members == a.members : true
|
||||
after a.members.add("meera"), b.members = [asha, ravi, meera]
|
||||
|
||||
== 2. The fix: copy the mutable field inside clone() ==
|
||||
c.members = [asha, ravi, meera]
|
||||
d.members = [asha, ravi]
|
||||
|
||||
== 3. Calling super.clone() without implementing Cloneable ==
|
||||
CloneNotSupportedException: com.ankurm.copying.CloneDemo$ForgotCloneable
|
||||
|
||||
== 4. Array clone() is shallow for nested arrays ==
|
||||
gridCopy[0] == grid[0] : true
|
||||
grid = [[99, 2], [3, 4]]
|
||||
gridCopy = [[99, 2], [3, 4]]
|
||||
|
||||
== 5. ArrayList.clone() is shallow too ==
|
||||
original.get(0) = abc-changed
|
||||
same element object: true
|
||||
|
||||
== 6. A parent clone() that uses 'new' breaks every subclass ==
|
||||
ClassCastException: class com.ankurm.copying.CloneDemo$Animal cannot be cast to class com.ankurm.copying.CloneDemo$Dog (com.ankurm.copying.CloneDemo$Animal and com.ankurm.copying.CloneDemo$Dog are in unnamed module of loader 'app')
|
||||
@@ -0,0 +1,10 @@
|
||||
== 1. Copy constructor: new object, own list, final fields intact ==
|
||||
rex = Dog(Rex, [sit, roll]) bark=7
|
||||
rex2 = Dog(Rex, [sit, roll]) bark=7
|
||||
rex2.tricks == rex.tricks : false
|
||||
|
||||
== 2. The static type picks the constructor, so a subtype is sliced off ==
|
||||
new Animal(pet) = Animal(Rex, [sit, roll])
|
||||
|
||||
== 3. A virtual copy() keeps the runtime type ==
|
||||
pet.copy() = Dog(Rex, [sit, roll]) bark=7
|
||||
@@ -0,0 +1,21 @@
|
||||
== 1. A full deep copy: nothing is shared ==
|
||||
copy.dept == asha.dept : false
|
||||
copy.skills == asha.skills : false
|
||||
asha.skills = [java, spring], asha.dept = Core
|
||||
copy.skills = [java], copy.dept = Platform
|
||||
|
||||
== 2. transient fields are lost ==
|
||||
asha.sessionToken = secret-token
|
||||
copy.sessionToken = null
|
||||
|
||||
== 3. Sharing INSIDE the graph is preserved ==
|
||||
original: same Dept object : true
|
||||
copy : same Dept object : true
|
||||
copy dept is a new object : true
|
||||
|
||||
== 4. Cycles are handled ==
|
||||
cyc.mentor.mentor == cyc : true
|
||||
cyc != asha : true
|
||||
|
||||
== 5. A non-serializable field fails at run time ==
|
||||
NotSerializableException: java.lang.Object
|
||||
@@ -0,0 +1,15 @@
|
||||
== 1. A record does not copy the list you give it ==
|
||||
leaky.items() = [pen, book, laptop]
|
||||
after leaky.items().add("phone"), basket = [pen, book, laptop, phone]
|
||||
|
||||
== 2. List.copyOf in the compact constructor ==
|
||||
safe.items() = [pen, book]
|
||||
safe.items().add(...) -> UnsupportedOperationException
|
||||
|
||||
== 3. 'Changing' a record means building a new one ==
|
||||
safe = SafeOrder[id=A-2, items=[pen, book]]
|
||||
bigger = SafeOrder[id=A-2, items=[pen, book, charger]]
|
||||
|
||||
== 4. A mutable Date component ==
|
||||
LeakyEvent.when().getTime() after caller mutates d1 = 86400000
|
||||
SafeEvent.when().getTime() after both mutations = 0
|
||||
@@ -0,0 +1,17 @@
|
||||
== 1. Leak through the constructor ==
|
||||
leaky.size() after the caller clears its own list : 0
|
||||
|
||||
== 2. Leak through the getter ==
|
||||
leaky2.getNames() = [asha, ravi, intruder]
|
||||
|
||||
== 3. The defensive version ==
|
||||
safe.size() after the caller clears its list : 2
|
||||
safe.getNames().add(...) -> UnsupportedOperationException
|
||||
|
||||
== 4. unmodifiableList is a VIEW; List.copyOf is a SNAPSHOT ==
|
||||
source = [a, b, c]
|
||||
view = [a, b, c]
|
||||
snapshot = [a, b]
|
||||
|
||||
== 5. copyOf is shallow: the ELEMENTS are still shared ==
|
||||
frozen.get(0) = x-mutated
|
||||
@@ -0,0 +1,23 @@
|
||||
== 1. new ArrayList<>(list) and List.copyOf share the elements ==
|
||||
boxes = [Box(100), Box(2)]
|
||||
viaCtor = [Box(100), Box(2)]
|
||||
viaCopyOf = [Box(100), Box(2)]
|
||||
after boxes.add(Box(3)): viaCtor.size() = 2
|
||||
|
||||
== 2. The deep version needs the element copy, so a stream or a loop ==
|
||||
boxes = [Box(-1), Box(2), Box(3)]
|
||||
deep = [Box(100), Box(2), Box(3)]
|
||||
|
||||
== 3. HashMap copy constructor: new map, same values ==
|
||||
mCopy.get("k") = Box(55)
|
||||
|
||||
== 4. 1-D primitive arrays: clone / copyOf are true copies ==
|
||||
a = [99, 2, 3], clone = [1, 2, 3], copyOf = [1, 2, 3]
|
||||
|
||||
== 5. 2-D arrays: the rows are shared ==
|
||||
grid = [[99, 2], [3, 4]]
|
||||
shallow = [[99, 2], [3, 4]]
|
||||
deepGrid = [[1, 2], [3, 4]]
|
||||
|
||||
== 6. Object arrays: copyOf copies references, not objects ==
|
||||
arrCopy[0] = Box(77), same object: true
|
||||
@@ -0,0 +1,3 @@
|
||||
[INFO] Tests run: 17, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.145 s -- in com.ankurm.copying.CopyingTest
|
||||
[INFO] Tests run: 17, Failures: 0, Errors: 0, Skipped: 0
|
||||
[INFO] BUILD SUCCESS
|
||||
@@ -0,0 +1,7 @@
|
||||
Indicative only: plain nanoTime loop, best of 7 rounds, ns per deep copy.
|
||||
Absolute numbers drift per machine and run; read the ratios.
|
||||
|
||||
copy constructor : 14 ns
|
||||
deep clone() : 13 ns
|
||||
serialization : 9765 ns
|
||||
serialization is about 698x the copy constructor
|
||||
@@ -0,0 +1,3 @@
|
||||
copyOf(immutable list) is the same instance : true
|
||||
copyOf(unmodifiableList view) is the same instance : false
|
||||
copyOf with a null element throws : NullPointerException
|
||||
Reference in New Issue
Block a user