Why use defensive copies
In ADT, there are often Observer methods that allow users to view some of the properties associated with ADT. Users may make some modifications to these attributes after they get them. If we return the rep in ADT directly to the user, the user's modifications may have a significant impact on ADT, resulting in some unexpected errors in the program. Defensive copies are used to avoid similar situations.
In fact, in a broader sense, it is not only possible to show leaks when return values appear. In fact, if there is no valid copy of the user's input object in the constructor, the user's subsequent modifications may actually have an impact on your design of ADT, which also requires a defensive copy.
Give an example
The following is an example.
class Poem { public String title; public String author; private List<String> lines = new ArrayList<>(); private Date date; // AF: Represents a poem with four attributes: // Title is the title of the poem, // Author is the author of the poem, // lines is the text line of the poem, // Date is the date the poem was published public Poem(String t, String a, List<String> l, Date d) { title = t; author = a; lines = l; date = d; } public void addOneLine(String newLine) { lines.add(newLine); } //.. public List<String> getAllLines() { return lines; } }
There is actually some risk of leakage in this ADT.
- In the constructor, lines = l is used directly; In such a statement, I intend to copy the input List <String> l to the lines property in the rep. However, since List is an object data type, L and line are actually pointing to the same object at this time, and once the client changes the value in l, line changes with it, which is dangerous.
- In the constructor, date = d also appears; Sentence. Date is an object data type, and once a client changes the value in d, the value in date changes with it, which is dangerous.
- In the getAllLines() method, the lines in rep are placed back directly to the user, so that the user can modify the returned List at will, which may change the rep in ADT, which also violates the representation independence.
Solution: Defensive Copy
With regard to defensive copy, the main idea is to create an object that is identical to the original object, and the new object will not be affected by the original object, that is, they are completely independent. Here are two typical mutable types, List and Date.
List
For a List, what we actually want to copy is all the objects in the entire List, not a pointer to that List.
We can iterate through the original List and copy all the elements in the List into the new List, code below.
class Test { public static void main(String[] args) { List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c")); List<String> copyList = new ArrayList<>(); for(int i=0; i<list.size(); i++) { copyList.add(list.get(i)); } list.remove(0); System.out.println("list:"+list.toString()); System.out.println("copylist:"+copyList.toString()); } }
The final result is
list : [b, c]
copylist : [a, b, c]
You can see here that a copy of the element has been successfully implemented, and changes to the original List will not affect the new List.
However, this kind of copy is a bit cumbersome, so the following method is simpler.
class Test { public static void main(String[] args) { List<String> list = new ArrayList<>(Arrays.asList("a", "b", "c")); List<String> copyList = new ArrayList<>(); copyList.addAll(list); list.remove(0); System.out.println("list:"+list.toString()); System.out.println("copylist:"+copyList.toString()); } }
The addAll() method of the List object is used to make copies of the elements in the List (commonly referred to as "deep copies").
Some other collection classes in Java, such as Set, Map, and so on, can also implement defensive copies in a similar way to avoid the impact of external changes on ADT.
In the Observer method, a defensive copy is actually required, either by returning a copy of the rep to the user or by using Collections.unmodifiableLis() modifies the List object, which is immutable.
Date
Date is simpler than List, and we just need to create a new Date object that will have the same value as the original one.
The following is a specific code implementation.
class Test { public static void main(String[] args) { Date date = new Date(); Date copyDate = new Date(date.getTime()); date.setTime(2000000000); System.out.println("date:"+date.toString()); System.out.println("copydate:"+copyDate.toString()); } }
The final result is
date : Sat Jan 24 11:33:20 CST 1970
copydate : Tue Jul 06 16:57:15 CST 2021
We have successfully created two completely independent Date objects, which is exactly what we want.
For some other object, either through a constructor or through some set method, we can create an object that is identical to the original object.
epilogue
Taking List and Date as examples, this paper mainly introduces some methods of defensive copy in Java, and illustrates the necessity of defensive copy.
But if the return value itself is an object of immutable type, there is no need for a defensive copy, because such objects are not allowed to be changed. Therefore, in future programming, if you can use objects of type immutable, use them as much as possible to avoid many of the troubles associated with representation leaks.