Discussion 6: Inheritance

Solutions

Exercise 1: Reverse Engineering a Superclass
(a)
The first line of the Playlist constructor must be a call to the superclass constructor. Why does Java require this?
We must initialize a superclass before a subclass, since Java needs to set everything up from the top down before proceeding with a subclass definition. Consider a simple case of trying to access a superclass-only variable: we must initialize it with a call to super() before trying to use it.
(b)
Which fields and methods appear to be used in Playlist without initialization?
For this code to compile, where must they be initialized?
The title and songs fields, and the calculateRuntime() method are used without initialization in the Playlist class. For the code to compile, they must be initialized in a superclass.
(c)
What visibility modifier must these methods and fields have? Specifically for fields, is this a potential concern? Explain why or why not.
Regardless of your conclusion, keep the implementation as is.
These methods and fields should have either a protected or public visibility modifier, so that they can be accessed in subclasses. For fields, this is a concern, since it is the base class’s responsibility to manage its class invariant, and this may not be respected if subclasses have direct access to fields. We should encapsulate the fields with getters and setters.
(d)
Would your answer to the above change if the fields were final?
If we made title final, then this would prevent any undesired updates in subclasses (preserving the invariant) even if they had access to the variable. If we made songs final, while subclasses wouldn’t be able to reassign the value of songs, they could still change the contents of the song array, possibly violating an invariant. This boils down to immutability—since title is an immutable String, once it’s declared as final, it cannot be changed at all, since any changes would need to be reassigns which are disallowed (by final). However, songs is an array, which is not immutable; we can change its state without needing to reassign it.
(e)
Take a look at the method headers: some have an @Override tag while others do not. What does this mean for methods in the superclass?
The methods with @Override are guaranteed to be declared in the superclass, while the other methods should not exist in the superclass. Note the difference between “should be” and “guaranteed”: having @Override isn’t a requirement for existing superclass methods that we want to override (the code will still compile without them), but as good programmers, we include them. On the other hand, you cannot include an @Override tag for a non-overridden method (the code will not compile).
(f)
Recall that one of the methods in SongCollection was abstract. Since Playlist is not abstract, you know that one of the overridden methods must be abstract in the superclass. Which method would this be?
Hint: think about which method should have "default" behavior for all types of SongCollections.
The method with “default” behavior is play()—the default being to play the songs in order. This order behavior is being overridden in the Playlist subclass by shuffling. There is no “default” behavior for the edit() method, since editing actions may vary drastically (or not be allowed) for certain SongCollection subclasses. Therefore edit() is the abstract method in the SongCollection class.
Exercise 2: Implementing the SongCollection Superclass
Using your answers to the previous exercises, re-code the implementation of SongCollection. You may ignore Javadoc comments. Some Song methods that you may find helpful are listed at the end of this handout.

SongCollection.java

Note that the visibility modifiers of all non-constructor methods and fields may be public or protected, since both give access to subclasses.
Exercise 3: Adding toString() and equals() Methods
Having recovered the superclass, you can now continue coding in the subclass.
(a)
Implement a toString() method in the Playlist class. This method should return a string in the following template: "Playlist {title}: Featuring {song title} by {artist name}", where the title and artist are for the first song in the playlist, and can be accessed with the Song methods getTitle() and getArtist().
(b)
Why does the above method include an @Override tag, when there is no toString() method defined in the SongCollection superclass?
@Override is necessary because we are overriding the Object class’s toString() method, since Object is at the top of Java’s type hierarchy (the “super-est of all classes”).
(c)
Implement an equals() method in the Playlist class, as specified.
(d)
Why do we need to cast the obj variable?
We need this cast to change the static type of the variable referencing obj. This way, we are allowed to use Playlist methods and fields without compilation errors (the compile-time reference rule). When the method runs, however, we use dynamic dispatch to get the appropriate methods and fields.
(e)
Explain why we should not use == when comparing if two Playlists contain the same songs and length.
The == operator compares the values of the expressions on both sides of it. For the case of comparing two Playlist variables, the values are references to a Playlist object. Hence, == would compare if the reference values are the same (i.e., the Playlists are the same object), but it says nothing about the contents of that object (the songs and playlist length).
Exercise 4: Dealing with Exceptions - Time Permitting
You now decide to add a "clean playlist" feature, which finds clean replacements for every explicit song in a playlist. Since this is a feature specific to playlists, you implement it in the Playlist class, under the cleanPlaylist() method.
(a)
To extract the maximal surplus value from the consumer, you decide to make this a paid feature. Specifically, if this method is called from a user who does not have a premium subscription, it must throw a PremiumFeatureException (which extends Exception). Assume that you have already implemented a method getPremiumStatus() in the superclass SongCollection that returns true if the user has the appropriate access tier, otherwise false. You may find the Song method clean() helpful.
(b)
You'd like to make a main() method demo of the cleanPlaylist() feature, and you write the following code:
However, this code will not compile. What are two ways of changing the above code so that it compiles successfully? Explain why both changes work.
(1) You could add a throws clause to the method signature. Specifically, you should add throws PremiumFeatureException, which lets Java know that this method may propagate an exception. (2) Another solution is to swap the RuntimeException for the correct PremiumFeatureException (or a superclass of this exception), which will catch the error if it is thrown from the try block. Since RuntimeException is not a superclass of PremiumFeatureException, the current catch block isn’t catching anything (that we’re aware of). Since the correct catch condition will corral the exception, it does not need to be added to the method signature, as it will never propagate from the method.

The Song Interface

Song.java