This question involves adding default methods to the GenericSet interface that you designed in question b401.
Note: if you had trouble completing question b401, you have two choices:
-
You could attempt this question by starting with the question b401 sample solution;
-
You could add default methods to the
IntSetinterface of question 8a61, rather than to its generic version.
Let's suppose that our GenericSet interface has become popular, and that we now regret not having included some additional methods when we designed the interface. We'll go through how to use default methods to fill these omissions.
-
Add a
defaultmethod to theGenericSetinterface calledaddAll. TheaddAllmethod havevoidreturn type, and should take an array,items, of typeEas its single parameter, whereEis the generic parameter associated withGenericSet(useIntegerorintinstead ofEif you are working onIntSetrather thanGenericSet).For each element
iteminitems, theaddAllmethod should use theaddmethod to additemsto the set. -
In a
Democlass, write a main method that creates aMemoryEfficientGenericSetof integers and aSpeedEfficientGenericSetof integers, creates some small arrays of integers, and usesaddAllto add the arrays of integers to each of the sets. Run yourmainmethod to check that it behaves appropriately.Now change your
mainmethod so that it creates some large arrays of integers, and usesaddAllto add the large arrays of integers to each of the sets.(You could look at
Demo.javafrom the sample solution for some example code here; seesolutions/code/tutorialquestions/question336b/Demo.java.)You will probably find that
addAlltakes an excessively long time to add a large array of integers to aMemoryEfficientGenericSet. Think about why this is. -
The default implementation of
addAllworks for any set, but as demonstrated in step 2 is not efficient forMemoryEfficientGenericSets. OverrideaddAllinMemoryEfficientGenericSetwith an implementation that does not invoke the defaultaddAllimplementation, but instead uses aHashSetlocal variable to keep track of the contents of the set. This hash set should be initialized with the contents of theMemoryEfficientGenericSet. Then, for each element ofitems(the parameter toaddAll), you should first check whether the element is in the hash set. If it is, you should move on to the next element; otherwise you should add it directly to the elements of theMemoryEfficientGenericSet(without going through theaddmethod), and also add it to the hash set of elements that are known to be in the set.You should find that, with this specific implementation of
addAll, the code in yourmainmethod behaves in an efficient manner. Think hard to make sure you understand why this is the case. -
Add one more default method to
GenericSet. This method should be calledasUnmodifiableSet. It should take no parameters, and should return aGenericSet<E>. The generic set that is returned should be a version of the original set where any methods that could cause the set to be modified are replaced with methods that throw anUnsupportedOperationException.There are two ways you could approach this. The simple, but somewhat verbose way, is to make a new class,
UnmodifiableGenericSetthat implements theGenericSet<E>interface. This class should have a single field of typeGenericSet<E>representing the set for which an unmodifiable version is being created; this field could be calledwrapped. The new class should implement all of the required methods ofGenericSet<E>. The non-mutating methods should be implemented by delegation towrapped: invoking the corresponding method, with the same parameters (if any) onwrapped, and returning the result (if any) returned by said method. The mutating methods should be implemented by simply throwing anUnsupportedOperationException.Recall from question 85bb) that we avoided the need for writing a fully separate iterator class by using an inner class. It is not possible for an interface to have an inner class (it can have a nested class, but not an inner class; read online about nested classes to understand the difference).
However, we can use an anonymous inner class in the implementation of
asUnmodifiableSet, by having the body ofasUnmodifiableSetlook like this:return new GenericSet<E>() { @Override public void add(E item) { throw new UnsupportedOperationException("Attempt to add to an unmodifiable set."); } ... /* Implementations of other methods */ ... };This returns an instance of a new class that implements the
GenericSet<E>interface, implementing its methods according to the method implementations given between the{and}followingnew GenericSet<E>.If you follow this approach, your anonymous inner class will need to call methods of the
GenericSet<E>that it is defined inside. This can be achieved by using the syntaxGenericSet.this. So, for example, in a method inside the anonymous inner class,isEmpty()would call theisEmptymethod of the anonymous inner class, whileGenericSet.this.isEmpty()would call theisEmptymethod of the object implementing the outerGenericSet<E>interface.Try both implementation approaches -- using an explicit
UnmodifiableGenericSetclass and an anonymous inner class, and see which you prefer. Beyond the amount of code you have to write, can you see any pros and cons of the two approaches? -
Adapt your
mainmethod so that it usesasUnmodifiableSetto create unmodifiable versions of some sets, and check that the unmodifiable sets behave just like the sets that they wrap if non-mutator methods are called, and that appropriate exceptions are thrown if mutator methods are called. Notice that, due to the use of a default method, you can work with unmodifiable versions ofMemoryEfficientGenericSets andSpeedEfficientGenericSets without having had to modify these classes.