Avoid Leaking References

 class​ Inventory {
 
 private​ ​final​ List<Supply> supplies;
 
  Inventory(List<Supply> supplies) {
»this​.supplies = supplies;
  }
 
  List<Supply> getSupplies() {
»return​ supplies;
  }
 }

Practically any nontrivial object has some inner state that’s accessible from the outside. You need to be careful how you make this state available. Otherwise, you risk serious bugs.

The Inventory here shows a fairly common example of a class that maintains a data structure. That data structure is initialized externally and inserted into the constructor of the Inventory. Nothing’s obviously wrong with the class itself, but let’s look at the usage:

 List<Supply> externalSupplies = ​new​ ArrayList<>();
 Inventory inventory = ​new​ Inventory(externalSupplies);
 
 inventory.getSupplies().size(); ​// == 0
»externalSupplies.add(​new​ Supply(​"Apple"​));
 inventory.getSupplies().size(); ​// == 1
 
»inventory.getSupplies().add(​new​ Supply(​"Banana"​));
 inventory.getSupplies().size(); ​// == 2

First, we pass the empty externalSupplies to the new Inventory, and getSupplies() returns an empty list. But the inventory doesn’t protect its internal list of supplies. Instead, we can change the state of the inventory by adding a supply to the externalSupplies list or by other change operations on the list returned by getSupplies(). Note that the final keyword for the supplies field doesn’t prevent this behavior! And right now, we could also pass null into the constructor, which can easily trigger exceptions later.

The problem is that there’s only one list in memory—the one created by new ArrayList<>(). The inventory just stores a reference to that list in its supplies field and returns that reference through getSupplies(). Essentially, the Inventory leaks the reference to its inner structure to the outside through the getter. This is bad! But there’s a way to avoid this.

How can we protect the internals of a class from being manipulated after the instantiation?

 class​ Inventory {
 
 private​ ​final​ List<Supply> supplies;
 
  Inventory(List<Supply> supplies) {
»this​.supplies = ​new​ ArrayList<>(supplies);
  }
 
  List<Supply> getSupplies() {
»return​ Collections.unmodifiableList(supplies);
  }
 }

The Inventory does a way better job of protecting its inner structure now. Instead of just using the reference to the list we pass in, it only uses the Supply objects in it to fill an internal ArrayList. It will also trigger an exception immediately if you try to enter null.

What’s more, it doesn’t expose the internal list through getSupplies() directly, but only after wrapping it as a unmodifiableList(). This ensures read-only access. If a call wants to add an element to the list, we would need to write an explicit method for that.

The instance of the internal ArrayList stays private within the Inventory. Its reference never leaves the class. It’s hidden away. Protected. The new usage of the Inventory shows this:

 List<Supply> externalSupplies = ​new​ ArrayList<>();
 Inventory inventory = ​new​ Inventory(externalSupplies);
 
 inventory.getSupplies().size(); ​// == 0
 externalSupplies.add(​new​ Supply(​"Apple"​));
»inventory.getSupplies().size(); ​// == 0
 
 // UnsupportedOperationException
»inventory.getSupplies().add(​new​ Supply(​"Banana"​));

Changing the externalSupplies list or the list returned from getSupplies() has no effect on the internal state of the inventory. Even better, any attempt to modify the list returned by getSupplies() causes an UnsupportedOperationException.

This technique is also called defensive copying. Instead of reusing a passed data structure, you make a copy to avoid losing control.

Always remember: It’s important to protect both the setter and the getter. And your job gets much easier if you don’t allow setters in the first place.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.212.71