Investigate an example of a project which has an escaping reference, and consider whether duplicating the reference might overcome the issue.
- [Instructor] So, how do we get round escaping references? Well, the first point to note is that references can only escape if we have methods that are returning a pointer to the object. In this instance, the get customers method is returning a pointer to the map. So we're looking for ways to avoid returning pointers to existing objects. Let's view this clause in Eclipse, so that we can try out different techniques for avoiding the escaping reference.
There's a project in the starting workspace for this chapter called escaping references, so load this project up in Eclipse. So here's my copy of Eclipse, and there are three classes in this project. We have a customer class, and this is really simple. We have a constructor that sets the name, a get name method, and I've also defined a two string that also prints out the name. As you can see, there's only one field in this class which is the name. The customer records class is the one we've just seen on screen.
And right now, it has this get customers method, which is providing an escaping reference to the records map. That's something we're going to try and fix. And there's a main class, which simply instantiates the customer records, adds to customers, and then loops through printing out those customers to the console. Well, the first approach, although you don't see this too often in Java, is that we could make customer records an iterable class and provide an iterator to the objects that we want to be able to iterate through.
Let's have a go at doing that. So we're going to say that customer records is going to implement the iterable interface, and it will be iterating customers. Now, the squiggly line under customer records will tell us that we need to implement the iterator method. If I just hover over this and add the unimplemented methods, we'll get the methods stumped for us here. And so all I need to do in here is to return. Well, it's going to be the records, the map that's in our class is a private field.
I want to get the customers, which are the values, so we'll call .values. And then I simply need to call the .iterator method on that to return an iteration of the customers. Having done this, having made customer records an iterable class, it means in the main method, rather than calling records.getcustomers.values, I can just say for each customer in records. These should now be iterable, and if I run this, I should get both of the customers printed out to the console.
Now, that looks like a reasonable solution, but it isn't perfect. There's one issue with this, and that is that the iterator has a remove method. I could do here records.iterator.remove. And so this method would allow me still to mutate the underlying collection. So this isn't a foolproof way of ensuring that our reference has not escaped from the encapsulated class.
A better solution might be to return a new instance of the collection. So let's go back to customer records. I'm going to leave in this iterable option, although we won't use it anymore, and instead, going back to get customers, rather than returning this .records, let's return a new hash map. And it's a hash map of strings with a key and customer values, and we'll pass the existing hash map into the constructor of the new hash map.
All collections in Java have a constructor, which sets an existing collection. And this will create a brand new collection seeded with the data that we've passed in. So, by doing this, we're returning a copy of the hash map. This is an important point, so let's just review that. If we have a collection, I've got a list on screen here, which is referenced by a variable, and we set another variable equal to the collection variable, then all that happens, as we know, is that the second variable points to the same object on the heap as the first collection.
However, if we create a new collection, and we pass an existing collection into the constructor of that new collection, then a new object is created on the heap containing the same data as the original collection. The possible downside of this is that the client is still free to mutate the collection that they have been passed. But it's their own personal copy of their collection, so if they do make a change, well, they're not corrupting the original source data.
But this is not a perfect solution, and we can change the underlying objects in the collection. Note that both of these collections have a reference to Customer B. If there were fiends that can be changed in Customer B, then this will impact the original collection. And the other issue is that if the client changes their own copy of the collection, they might think that they're changing the original master copy, which could cause confusion. If we go back to Eclipse, and we've just made now the get customers method return a new instance of our customers, if in the main class we call records.getcustomers.clear, can we predict what will happen after that? I've got some code here, which is going to loop through the customers.
Let's put that back to also using the .getcustomers method. So, the question is, does getcustomers.clear clear out the data in our underlying records? Well, I think we can predict that it won't do, because calling get customers is going to return a copy of the hash map, and we're calling .clear on the copy. And because it's on the copy, the underlying hash map won't have been cleared.
When we call .getcustomers again here, we're getting another copy of that original hash map, and that copy should still contain the values. So if we run this now, I expect we'll still get our two customers printed out to the console. Well, we do. I was right with my prediction. But just looking at this code in isolation, this really looks counterintuitive. We're calling .clear, and then we find there's still values there.
- How memory works in Java
- Passing variables by value
- How objects are passed
- What are escaping references?
- How to avoid escaping references with collections and custom objects
- Garbage collection and generation sizes
- Detecting soft leaks
- Choosing a garbage collector
- Tuning a virtual machine
- Fixing a memory leak