Skip to content

Latest commit

 

History

History
executable file
·
53 lines (30 loc) · 6.25 KB

336b.md

File metadata and controls

executable file
·
53 lines (30 loc) · 6.25 KB

Back to questions

336b: Evolving the Set interface

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 IntSet interface 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.

  1. Add a default method to the GenericSet interface called addAll. The addAll method have void return type, and should take an array, items, of type E as its single parameter, where E is the generic parameter associated with GenericSet (use Integer or int instead of E if you are working on IntSet rather than GenericSet).

    For each element item in items, the addAll method should use the add method to add items to the set.

  2. In a Demo class, write a main method that creates a MemoryEfficientGenericSet of integers and a SpeedEfficientGenericSet of integers, creates some small arrays of integers, and uses addAll to add the arrays of integers to each of the sets. Run your main method to check that it behaves appropriately.

    Now change your main method so that it creates some large arrays of integers, and uses addAll to add the large arrays of integers to each of the sets.

    (You could look at Demo.java from the sample solution for some example code here; see solutions/code/tutorialquestions/question336b/Demo.java.)

    You will probably find that addAll takes an excessively long time to add a large array of integers to a MemoryEfficientGenericSet. Think about why this is.

  3. The default implementation of addAll works for any set, but as demonstrated in step 2 is not efficient for MemoryEfficientGenericSets. Override addAll in MemoryEfficientGenericSet with an implementation that does not invoke the default addAll implementation, but instead uses a HashSet local variable to keep track of the contents of the set. This hash set should be initialized with the contents of the MemoryEfficientGenericSet. Then, for each element of items (the parameter to addAll), 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 the MemoryEfficientGenericSet (without going through the add method), 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 your main method behaves in an efficient manner. Think hard to make sure you understand why this is the case.

  4. Add one more default method to GenericSet. This method should be called asUnmodifiableSet. It should take no parameters, and should return a GenericSet<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 an UnsupportedOperationException.

    There are two ways you could approach this. The simple, but somewhat verbose way, is to make a new class, UnmodifiableGenericSet that implements the GenericSet<E> interface. This class should have a single field of type GenericSet<E> representing the set for which an unmodifiable version is being created; this field could be called wrapped. The new class should implement all of the required methods of GenericSet<E>. The non-mutating methods should be implemented by delegation to wrapped: invoking the corresponding method, with the same parameters (if any) on wrapped, and returning the result (if any) returned by said method. The mutating methods should be implemented by simply throwing an UnsupportedOperationException.

    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 of asUnmodifiableSet look 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 } following new 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 syntax GenericSet.this. So, for example, in a method inside the anonymous inner class, isEmpty() would call the isEmpty method of the anonymous inner class, while GenericSet.this.isEmpty() would call the isEmpty method of the object implementing the outer GenericSet<E> interface.

    Try both implementation approaches -- using an explicit UnmodifiableGenericSet class 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?

  5. Adapt your main method so that it uses asUnmodifiableSet to 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 of MemoryEfficientGenericSets and SpeedEfficientGenericSets without having had to modify these classes.