Lab 7
Polymorphic Classes
Today we are going to implement some related classes that use polymorphism.
You may have noticed that people use multiple units of measurement for different things. For example, most Americans use miles per hour to measure the speed of their cars, while most Europeans use kilometers per hour. Both groups of people use knots for boat speed. Vertical speed is usually measured in meters per second or feet per second.
We do a similar thing with temperature - most Americans describe the outdoors temperature in Fahrenheit, while most Europeans use Celsius. Most scientists and engineers (even the Americans) use Celsius and Kelvin. It is not unusual for a scientific software application to use more than one temperature scale. This can get confusing if temperatures are simply stored in primitive variables. If you have dozens of floats named 'temp', do you think you are likely to remember which units you are using in each instance? It is very easy to forget the units of a data element and use the wrong number in an equation. Errors like that happen frequently, even for professionals. Take a moment to read about a few here.
One way to solve this problem is to use a different class for each unit of measurement. So Fahrenheit and Celsius can be stored as different classes. If the variable 'temp' is of type 'Fahrenheit' then it is a lot more difficult to forget its units than if it is simply a 'float'. Also, the Fahrenheit class can contain methods for easily converting to Celsius or Kelvin if needed. This is what we are going to build today.
Requirements
Everything in this section must be implemented exactly as described. Do not take shortcuts or leave a method out if you think you can solve the problem a different way.
You are going to build four data classes: Temperature, Fahrenheit, Celsius, and Kelvin.
The diagram to the right explains their relationship. Note the italics in the classname
'Temperature'. In UML, a classname in italics indicates an abstract class.
The three 'concrete' classes each inherit from the abstract class Temperature.
The three concrete classes should each be final.
Temperature has a private double data member named 'degrees'. This value holds the recorded temperature, regardless of the units being used. So for "32 degrees Fahrenheit", degrees==32. For "0 degrees Celsius", degrees==0.
Temperature has two accessor methods for manipulating 'degrees'. These set the numeric value in degrees directly, without converting from one scale to another:
- public void set(double n) sets 'degrees'
- public double get() returns 'degrees'
Temperature has two constructors:
- A protected constructor that accepts a double as its only argument. The constructor sets the initial value of degrees equal to the argument.
- A protected constructor that takes no arguments. It automatically sets degrees to the freezing point of water in the child's unit of measurement. This setting must be done in Temperature's constructor, not the child's.
Temperature has several abstract methods that must be overridden in the child classes:
- public abstract double freezingPoint(); returns the freezing point of water at sea level in the appropriate units. Note - use two decimal places of accuracy (e.g. 273.15)
- public abstract double boilingPoint(); returns the boiling point of water at sea level in the appropriate units. Note - use two decimal places of accuracy (e.g. 373.15)
- public abstract String toString(); returns a string in the following format: "45.2 C", "102.3 F", or "0.0 K". The degrees value should be printed with a single digit after the decimal place. The letter indicates the appropriate unit. Use the static String.format() method to format your return value. This snippet should get you started: String s = String.format("%.1f K", degrees);
Temperature has three public methods for converting each unit to its sibling units. These must be overridden in the child classes. Note that it is not possible to prevent all three from being required of each child, so class Celsius will have to have a method named toCelsius(). They should each return an appropriate value:
- public abstract double toFahrenheit();
- public abstract double toCelsius();
- public abstract double toKelvin();
The three child classes must each implement all of the abstract methods so they can be instantiated.
Each child class should have two public constructors that perform the same as the Temperature constructors. The version with no arguments should always default to the freezing temperature of water. (Hint - let the parent's constructor do the work for you here.)
You should create main() methods in each of the child classes for your own testing. Create tests for each separate method to make sure they are running correctly.
You must use javadoc notation to document all four of these classes. You will be scored on the usefulness and correctness of the documentation. The goal is to create documentation that would allow a new developer to use your temperature libraries without having to look through the code to figure out how it works.
Error-Checking
NOTE - there is no error-checking of user input required in this lab. Normally, a data class like this would want to ensure that values are within a legal range. In the case of temperature, none of them should be allowed below absolute zero (0.0 K). We will revisit that topic in one week, after we have covered Exception handling. For today's lab, we will accept temps below absolute zero.
What to hand in
Four files:
- Temperature.java
- Fahrenheit.java
- Celsius.java
- Kelvin.java
All of the above must be in a directory named 'Lab07'
Delete any *.class or other unneeded files prior to submitting
Delete all of your javadoc files. We will recreate them ourselves.
When you run the submit script, the directory and its contents get zipped and sent to your instructor. Ask for help early if you are having problems running the submit script.
Make sure that you run the correct submit script for your instructor:
- CDR Blenkhorn: /courses/blenk/submit Lab07
- Dr. Taylor: /courses/taylor/submit Lab07
Extra Credit
For some extra points, we are going to create a method to test whether any two Temperature objects are equal to each other. We have previously seen that you can do this with String.equals(). In a more general sense, you can do this comparison with Object.equals(Object obj) for any class. But to do this, you need to override Object.equals() for yourself in any custom class you write. Try googling for 'object.equals java example'.
Override the equals(Object obj) method in Temperature. The method must work for any two Temperature objects, even if they are different subclasses. For example:
Celsius c = new Celsius(); Fahrenheit f = new Fahrenheit(); boolean b = c.equals(f); // <-- True, since both are set to the freezing point Celsius c = new Celsius(100); Fahrenheit f = new Fahrenheit(100); boolean b = c.equals(f); // <-- False, since 100 degrees C != 100 degrees F
Sample test program
We will grade your labs using a test program we write. Our program will import your classes and run their various methods. The test code will look something like the program below.
public class Lab07Test {
public static void main(String[] args) {
Fahrenheit f = new Fahrenheit(32);
System.out.println(f);
Celsius c = new Celsius(100);
System.out.println(c);
System.out.println(c.toKelvin());
System.out.println(c.get() == c.boilingPoint());
Kelvin k = new Kelvin(273.15);
System.out.println(k);
System.out.println(k.toFahrenheit());
System.out.println(k.get());
k.set(0);
System.out.println(k);
System.out.println(k.toCelsius());
Temperature[] tarray = new Temperature[3];
tarray[0] = new Fahrenheit();
tarray[1] = new Celsius();
tarray[2] = new Kelvin();
for(int i = 0; i < tarray.length; i++){
System.out.println(tarray[i].toCelsius());
}
// Extra Credit
Celsius cc = new Celsius();
Fahrenheit ff = new Fahrenheit();
Kelvin kk = new Kelvin();
Kelvin kk2 = new Kelvin(100);
System.out.println(cc.equals(ff)); // True
System.out.println(cc.equals(kk)); // True
System.out.println(ff.equals(kk)); // True
System.out.println(kk.equals(kk2)); // False
}
}