JMU JMU - Department of Computer Science
Help Tools
Lab: Experimenting with Specialization and Inheritance


Instructions: Answer the following questions one at a time. After answering each question, check your answer (by clicking on the check-mark icon if it is available) before proceeding to the next question.

Getting Ready: Before going any further, you should:

  1. Setup your development environment.
  2. Depending on your development environment, create either a directory or a project for this lab.
  3. Download the following files:
    to an appropriate directory/folder. (In most browsers/OSs, the easiest way to do this is by right-clicking/control-clicking on each of the links above and then selecting Save as... or Save link as....)
  4. Add the appropriate files you downloaded to the directory/project you created for this lab.

    .java Files: In most IDEs, .java files (i.e., source files) can just be copied into the project.

    .class and .jar Files: In some IDEs it is easier to use .class files and in others it is easier to use a .jar file that contains the .class files. Hence, you have been provided with both. See the course "Help" page on your IDE for more information.

    Resources: In some IDEs, resources (e.g., images, data files) need to be in a special directory whereas in others they need to be in the working directory. See the course "Help" page on your IDE for more information.

  5. Briefly review the following documents:
    Note that a class may be referred to using its fully qualified name. For example, the String class may be referred to as the java.lang.String class and the Scanner class may be referred to as the java.util.Scanner class.

1. Using an Existing Class: In this part of the lab you will get some additional experience using existing classes.
  1. Write a driver (named ClockDriver in the default package) that constructs two clocks, one for Harrisonburg and one for Paris.


    public class ClockDriver
    {
        public static void main(String[] args)
        {
           Clock home;
           Clock paris;
    
    	home  = new Clock();
    	paris = new Clock("Paris");
        }
    }
    
    Expand
  2. Compile and execute your driver to make sure it works. (Note: You may need to move one of the clocks to see the other one.)
2. Extending a Class to Add Functionality: This part of the lab will help you understand how to specialize existing classes in order to add functionality.
  1. Create a file in the default package named AlarmClock.java that contains the following:
    AlarmClock1.java
    /**
     * A GUI window that contains an alarm clock
     * with a digital display
     *
     * @author  
     * @version 1.0
     */
    public class AlarmClock extends Clock
    {
        private boolean    on;
        private int        hour, minute, second;
        private String     ampm;
    
    
    
        /**
         * Default Constructor
         */
        public AlarmClock()
        {
    
        }
    
    
        /**
         * Explicit Value Constructor
         *
         * Constructs a clock for a particular city, setting
         * the time zone appropriately
         *
         * @param city   The city
         */
        public AlarmClock(String city)
        {
    
        }
    
    
        
    }
            
  2. Modify the two constructors in the AlarmClock class so that they call the appropriate constructors in the Clock class.


        /**
         * Default Constructor.
         */
        public AlarmClock()
        {
    	super();
        }
    
    
        /**
         * Explicit Value Constructor.
         *
         * Constructs a clock for a particular city, setting
         * the time zone appropriately
         *
         * @param city   The city
         */
        public AlarmClock(String city)
        {
    	super(city);
        }
    
    Expand
  3. Modify your driver so that the clock for Harrisonburg is both declared to be an AlarmClock object and instantiated as an AlarmClock object.


        /**
         * The entry point of the application.
         *
         * @param args  The command line arguments
         */
        public static void main(String[] args)
        {
            AlarmClock        home;
    	Clock             paris;
    
    	home  = new AlarmClock();
    	paris = new Clock("Paris");
        }
    
    Expand
  4. Compile and execute your driver.
  5. Add the following to your AlarmClock.java class:
    AlarmClock2.java
    
        /**
         * Set the alarm
         *
         * @param hour     The hour of the alarm
         * @param minute   The minute of the alarm
         * @param second   The second of the alarm
         * @param ampm     "AM" for before noon, "PM" for noon and after
         */
        public void setAlarm(int hour, int minute, int second, String ampm)
        {
    
        }
    
    
        /**
         * Turn the alarm off
         */
        public void turnAlarmOff()
        {
    
    
        }
    
    
    
        /**
         * Turn the alarm on
         */
        public void turnAlarmOn()
        {
    
    
        }
    
            

    and complete the setAlarm() method (i.e., set the attributes of the AlarmClock object to the values in the corresponding parameters).


        /**
         * Set the alarm.
         *
         * @param hour     The hour of the alarm
         * @param minute   The minute of the alarm
         * @param second   The second of the alarm
         * @param ampm     "AM" for before noon, "PM" for noon and after
         */
        public void setAlarm(int hour, int minute, int second, String ampm)
        {
    	this.hour   = hour;
    	this.minute = minute;
    	this.second = second;
    	this.ampm   = ampm;
        }
    
    Expand
  6. Now complete the turnAlarmOn() method. It must set the on attribute to true, create an appropriately formatted String containing the time of the alarm, and pass the String to the setText() method in the Clock class. The String can be formatted using the formatTime() method in the Clock class.

    If you want to be fancy, you can add an alarm icon to the String using the Unicode character '\u23F0'. (Note: Not all fonts support this character. So, you may need to use the "clock-like" '\u00A4' instead.)


        public void turnAlarmOn()
        {
    	on = true;
            setText("\u23F0"+Clock.formatTime(hour, minute, second, ampm));
        }
    
    Expand
  7. The formatTime() method in the Clock class is static. So, it can certainly be called as Clock.formatTime(). Can it also be called as formatTime()? If so, why? If not, why not?


    It can be called as formatTime() in the Clock class because static methods belong to the class which means they belong to every element in the class. So, this.formatTime() is OK and, since there is no ambiguity, formatTime() is also OK.

    It can also be called as formatTime() in the AlarmClock class since it is inherited from the Clock class (and the super. is not necessary to disambiguate).

    Expand
  8. Now, complete the turnAlarmOff() method. It must set the on attribute to false, and pass the empty String to the setText() method.


        public void turnAlarmOff()
        {
    	on = false;
            setText("");
        }
    
    Expand
  9. What functionality does an AlarmClock have that a Clock doesn't? How useful is this functionality at this point?


    It is possible to set an alarm (i.e., the time that an alarm should sound) and the state of the alarm (i.e., to either on or off). At this point, though, this only changes attributes of the AlarmClock object and the display, it doesn't really do anything "useful" (i.e., an alarm won't sound at the desired time).
    Expand
3. Extending a Class to Modify Existing Functionality: This part of the lab will help you understand how to specialize existing classes in order to modify existing functionality.
  1. If you look at the documentation for the Clock class you'll see that it has a getAMPM() method that returns a String. Obviously, this method can be used to retrieve information about the time period if you need it. It turns out, however, that it is also used by the updateTime() method in the Clock class when it displays the time (which happens every second).

    Though you don't have the source code for the Clock class you do for the AlarmClock class since you constructed it. That means that you can add a getAMPM() method to the AlarmClock class that will override the version in the clock class.

    Wouldn't it be cool to have your own time period? To that end, add a getAMPM() method to the AlarmClock class that returns your initials.


        /**
         * Get the time period
         *
         * @return   My initials!
         */
        public String getAMPM()
        {
            return "DB";
        }
    
    Expand
  2. Compile and execute your driver.
  3. The result is quite cool but a little impractical. Modify the getAMPM() method so that it returns your initials, followed by an apostrophe, an "s" and a space, followed by the actual time period.

    Hints:
    (1) The getAMPM() method in the Clock class still returns the actual timer period.
    (2) The apostrophe character is used for char literals so you will need to escape it.


        /**
         * Get the time period
         *
         * @return   My initials!
         */
        public String getAMPM()
        {
            return "DB\'s " + super.getAMPM();
        }
    
    Expand
  4. Did the behavior of the Clock, the AlarmClock, or both change?


    Just the AlarmClock, which isn't surprising since that's the class that has the "new and improved" getAMPM() method.
    Expand
  5. Remove the getAMPM() method from the AlarmClock class. (Though cool to create your own time period, it's more than a little confusing.)
  6. Why did the updateTime() method in the Clock class call the getAMPM() method in the AlarmClock class rather than the getAMPM() method in the Clock class?


    We'll talk about that when we talk about polymorphism.
    Expand
  7. Now, let's override the updateTime() method so that AlarmClock objects do everything they need to do. Specifically, add the following to your AlarmClock.java class:
    AlarmClock3.java
        /**
         * Update the time displayed on the clock
         */
        public void updateTime()
        {
            int         hourNow, minuteNow, secondNow;
            String      ampmNow;
    
            // Call the parent's version of updateTime()
    
    
            // If the alarm is on, get the current hour, minute
            // second, and ampm; check to see if the alarm
            // should sound now; if it should, sound the alarm
        }
        
            

    and complete the updateTime() method. As alluded to earlier, the updateTime() method will be called every second (in a manner that is out of your control). Your implementation of the updateTime() method must do everything that needs to be done every second. (Hint: Some things that need to be done are already being done in the Clock class. Take advantage of that and only do the things that are different for AlarmClock objects.)


        /**
         * Update the time displayed on the clock
         */
        public void updateTime()
        {
    	int         hourNow, minuteNow, secondNow;
    	String      ampmNow;
    
    	// Call the parent's version
    	super.updateTime();
    
    
            // Check if the alarm is on and if the alarm should sound
    	if (on) 
            {
    	    hourNow = getHour();
    	    minuteNow = getMinute();
    	    secondNow = getSecond();
    	    ampmNow = getAMPM();
    
    	    if ((hourNow == hour) && (minuteNow == minute) && 
    		(secondNow == second) && ampmNow.equals(ampm)) 
                {
    		beep();
    	    }
    	}
        }
    
    Expand
  8. Modify your driver so that it sets the alarm (for a time about a minute in the future) and turns the alarm on.


    	home.setAlarm(1, 39, 45, "PM");
    	home.turnAlarmOn();
    
    Expand
  9. Compile and execute your driver. (Note: Depending on your hardware, you may or may not hear a sound when the alarm goes off. You should, however, see the word "Beep" in the title bar.)
  10. What happens (or doesn't happen) if you comment out the call to the parent's updateTime() method?


    The time does not change every second the way it should because that behavior is provided by the updateTime() method in the Clock class.
    Expand
  11. What happens if you remove super. from the call to the updateTime()? In other words, what happens if you call updateTime() instead of super.updateTime()? Why?


    In this case, the updateTime() method in the AlarmClock class is calling itself, rather than the updateTime() method in the Clock class. This is an infinite recursion and, ultimately, results in a stack overflow.
    Expand
  12. Undo the fault that you introduced in the previous question.
  13. Overload the setAlarm() method with a version that is not passed a parameter for second (and uses a default of 0). Do not duplicate any existing code!


        /**
         * Set the alarm.
         *
         * @param hour     The hour of the alarm
         * @param minute   The minute of the alarm
         * @param ampm     "AM" for before noon, "PM" for noon and after
         */
        public void setAlarm(int hour, int minute, String ampm)
        {
    	setAlarm(hour, minute, 0, ampm);
        }
    
    Expand
  14. What is the difference between overloading a method and overriding a method?


    Overloaded methods are two or more methods, "in the same class", with the same name but different parameters. Note that the phrase "in the same class" is in quotes because the methods can either be physically in the same class or inherited. In other words, a derived class can overload a method in a base class.

    A method in a derived class overrides a method in a base class if the two have the same signature (i.e., the same name and parameters). Recall that, in Java, the two methods must have the same return type.

    Expand
4. Common Problems: In this part of the lab you will think about the extension mechanism in Java and the kinds of errors you might encounter when you develop classes that use this mechanism.
  1. Create a file named Tester1.java that contains the following:
    Tester1.java
    /**
     * A Driver for testing the Clock and AlarmClock classes
     */
    public class Tester1
    {
        /**
         * The enty point of the application
         *
         * @param args  The command line arguments
         */
        public static void main(String[] args)
        {
            AlarmClock        home;
            Clock             paris;
    
            home = new AlarmClock();
            paris = new Clock("Paris");
    
            setup(home);
            setup(paris);
        }
    
    
        /**
         * Setup a Clock
         *
         * @param clock   The clock to setup
         */
        private static void setup(Clock clock)
        {
            clock.reverseColors();
        }
    
    }
            
  2. Compile Tester1.java.
  3. The setup() method has a Clock as a formal parameter but is actually passed an AlarmClock. Why did it compile properly anyway?


    It compiles because the AlarmClock class extends the Clock class. Hence, an AlarmClock object "is a" Clock.
    Expand
  4. Create a file named Tester2.java that contains the following:
    Tester2.java
    /**
     * A Driver for testing the Clock and AlarmClock classes
     */
    public class Tester2
    {
        /**
         * The enty point of the application
         *
         * @param args  The command line arguments
         */
        public static void main(String[] args)
        {
            AlarmClock        home;
            Clock             paris;
    
            home = new AlarmClock();
            paris = new Clock("Paris");
    
            setup(home);
            setup(paris);
        }
    
    
        /**
         * Setup a clock
         *
         * @param clock   The clock to setup
         */
        private static void setup(Clock clock)
        {
            clock.reverseColors();
            clock.turnAlarmOn();
        }
    
    }
            
  5. Compile Tester2.java.
  6. Why didn't this class compile?


    It did not compile because the Clock class does not have a turnAlarmOn() method. Note that the parameter named clock in the setup() method is declared to be a Clock object (not an AlarmClock object). So, the compiler looked in the Clock class for a turnAlarmOn() method and did not find one.
    Expand
  7. Create a file named Tester3.java that contains the following:
    Tester3.java
    /**
     * A Driver for testing the Clock and AlarmClock classes
     */
    public class Tester3
    {
        /**
         * The enty point of the application
         *
         * @param args  The command line arguments
         */
        public static void main(String[] args)
        {
            AlarmClock        home;
            Clock             paris;
    
            home = new AlarmClock();
            paris = new Clock("Paris");
    
            setup(home);
            setup(paris);
        }
    
    
        /**
         * Setup a clock
         *
         * @param clock   The clock to setup
         */
        private static void setup(AlarmClock clock)
        {
            clock.reverseColors();
            clock.turnAlarmOn();
        }
    
    }
            
  8. Compile Tester3.java.
  9. Why didn't this class compile?


    It did not compile because an attempt is made to pass a Clock object (named paris) to a method that is expecting an AlarmClock object. Though an AlarmClock "is a" Clock, a Clock is not an AlarmClock.
    Expand
  10. Create a file named Tester4.java that contains the following:
    Tester4.java
    /**
     * A Driver for testing the Clock and AlarmClock classes
     */
    public class Tester4
    {
        /**
         * The enty point of the application
         *
         * @param args  The command line arguments
         */
        public static void main(String[] args)
        {
            Clock        home, paris;
    
            home  = createClock("Harrisonburg");
            paris = createClock("Paris");
        }
    
    
        /**
         * Create and setup a Clock
         *
         * @param clock   The clock to setup
         */
        private static AlarmClock createClock(String city)
        {
            AlarmClock      temp;
    
            temp = new AlarmClock(city);
            temp.reverseColors();
    
            return temp;
        }
    
    }
            
  11. Compile Tester4.java.
  12. The createClock() method creates and returns AlarmClock objects that main assigns to variables declared to be Clock objects. Why did this class compile anyway?


    The createClock method returns an AlarmClock object which is then assigned to a variable that is supposed to refer to a Clock object. Since an AlarmClock object "is a" Clock this assignment will work.
    Expand
  13. We know that AlarmClock objects require more memory than Clock objects because they have all of the attributes of Clock objects and some additional attributes. What do we know about the size of references to AlarmClock and Clock objects?


    All references in Java are the same size. So, references to AlarmClock and Clock objects are the same size.
    Expand
  14. Create a file named Tester5.java that contains the following:
    Tester5.java
    /**
     * A Driver for testing the Clock and AlarmClock classes
     */
    public class Tester5
    {
        /**
         * The enty point of the application
         *
         * @param args  The command line arguments
         */
        public static void main(String[] args)
        {
            Clock        home, paris;
    
            home  = createClock("Harrisonburg");
            paris = createClock("Paris");
    
            home.setAlarm(1, 39, 45, "PM");
            home.turnAlarmOn();
        }
    
    
    
        /**
         * Create and setup a Clock
         *
         * @param clock   The clock to setup
         */
        private static AlarmClock createClock(String city)
        {
            AlarmClock      temp;
    
            temp = new AlarmClock(city);
            temp.reverseColors();
    
            return temp;
        }
    
    }
            
  15. Compile Tester5.java.
  16. Why didn't this class compile?


    In the main() method an attempt is made to call the setAlarm() method that belongs to the home object. However, home is declared to be a Clock object. So, the compiler looked in the Clock class for a setAlarm() method and did not find one.

    You might argue that the home object is actually an AlarmClock since that is how it is created in the createClock() method. While that's true, the compiler doesn't know that -- it uses the declaration of the variable to determine where to look.

    Expand
  17. Create a file named Tester6.java that contains the following:
    Tester6.java
    /**
     * A Driver for testing the Clock and AlarmClock classes
     */
    public class Tester6
    {
        /**
         * The enty point of the application
         *
         * @param args  The command line arguments
         */
        public static void main(String[] args)
        {
            AlarmClock        home;
            Clock             paris;
    
            home  = (AlarmClock)createClock("Harrisonburg");
            paris = createClock("Paris");
    
            home.setAlarm(1, 39, 45, "PM");
            home.turnAlarmOn();
        }
    
    
    
        /**
         * Create and setup a Clock
         *
         * @param clock   The clock to setup
         */
        private static Clock createClock(String city)
        {
            Clock      temp;
    
            temp = new Clock(city);
            temp.reverseColors();
    
            return temp;
        }
    
    }
            
  18. Compile Tester6.java.
  19. createClock() returns a Clock that is being typecast as an AlarmClock. Why did this class compile?


    The compiler has no way to determine whether a typecast is correct or not. Hence, if you explicitly typecast something the compiler will assume that you know what you're doing and allow it.

    You might argue that the compiler should know that a Clock is being returned because of the return type of the createClock() method. However, the object that is returned could be an AlarmClock (since an AlaramClock "is a" Clock), in which case the typecast would be appropriate.

    Expand
  20. When is typecasting a good idea?


    Typecasting is almost never a good idea. The code will compile because the compiler will believe you. However, if you make a mistake, the code will fail at runtime (when you least want code to fail).

    The compiler will find a lot of faults if you let it.

    Expand
  21. Execute Tester6.
  22. What happened?


    The following exception was thrown:
    Exception in thread "main" java.lang.ClassCastException: Clock cannot be cast to
     AlarmClock
            at Tester6.main(Tester6.java:16)
    

    At run-time, Java noticed that an attempt was made to typecast a Clock as an AlarmClock.

    Expand
  23. Create a file named Tester7.java that contains the following:
    Tester7.java
    /**
     * A Driver for testing the Clock and AlarmClock classes
     */
    public class Tester7
    {
        /**
         * The enty point of the application
         *
         * @param args  The command line arguments
         */
        public static void main(String[] args)
        {
            AlarmClock        home;
            Clock             paris;
    
            home  = (AlarmClock)createClock("Harrisonburg");
            paris = createClock("Paris");
    
            home.setAlarm(1, 39, 45, "PM");
            home.turnAlarmOn();
        }
    
    
    
        /**
         * Create and setup a Clock
         *
         * @param clock   The clock to setup
         */
        private static Clock createClock(String city)
        {
            AlarmClock      temp;
    
            temp = new AlarmClock(city);
            temp.reverseColors();
    
            return temp;
        }
    
    }
            
  24. Compile Tester7.java.
  25. createClock() claims to return a Clock but it actually returns an AlarmClock. Why did this class compile?


    Because an AlarmClock "is a" Clock.
    Expand
  26. Execute Tester7.
  27. What happened?


    It executed properly. Since the object that was returned by createClock() is, actually, an AlarmClock, the typecast did not cause any problems at run-time. It was dangerous, but didn't cause any problems this time.
    Expand
Note: This part of the lab allows you to "go further" on this material. It is neither required nor for extra credit. It will, however, help you gain a deeper understanding of the material.
5. Additional Functionality:
  1. Modify the AlarmClock class so that it supports four alarms. Your existing driver must continue to work (without any changes), hence, you will need to overload the existing methods.
  2. What would you need to do to display the alarm times in order of when they will sound? Why might it be a good idea to create a SimpleTime class that contains the hour, minute, second, and period of the day?

Copyright 2021