Lesson 10.4 New Methods
Adding New Methods
In the last section, you learned how to use the "ancestral methods" provided to the MyLabel class by the Component class. In this lesson, the MyLabel class has the first of many children, the MoneyLabel class.
As you've seen, every MyLabel object is also a Label object; a MyLabel object can do everything a Label does, but no more.
In fact, a MyLabel object is actually a little less capable than a Label object, because class constructors are not inherited when you extend a class. That means you can't yet write:
| MyLabel theLabel = new MyLabel("Hi"); |
You'll take care of this constructor problem in the next section.
In this section, however, you'll add new features to the MyLabel class. Rather than modifying MyLabel directly, let's extendMyLabel to create a new class, and then add the new features to that class.
The MoneyLabel Class
Let's extend the MyLabel class to create a MoneyLabel class. The MoneyLabel class will automatically format text as money values, but will, in all other respects, act just like a normal Label.
Step 1: Data
The first step when creating a new, specialized version of an existing class is to decide if any new fields are required.
In the case of the MoneyLabel class, you'll need at least two.
- You'll need a field to store the numeric value of the Label.
- You'll need a formatting object to format the value.
For the numeric value you could use a long, a double, or a BigDecimal. Let's look at the pros and cons of each.
Using long
Using a long is attractive because it gives you "exact change". By storing the value as pennies and displaying dollar and cent values, we can use simple, accurate integer arithmetic. Since a long can hold +/- 9,223,372,036,854,775,807, storing pennies means your Label could hold $ 92,233,720,368,547,758.07 or ninety-two quadrillion dollars. It seems that should be enough for any possible use, but before you go this route, remember these two famous sayings:
"640K is enough memory for anybody" - Bill Gates
"Two digits is fine. By the time the turn of the century rolls around, the application will be long gone anyway." - Anon.
Using double
Using a double is attractive for two reasons. You'll never exhaust its range, and it makes it easy to work with amounts like .0175 which are impossible if we keep our money as a long--at least without making the range of values even smaller. The disadvantage of the double is that calculations must be done very carefully, especially when dealing with dollar amounts. The double is a binary floating-point number, not a decimal, so it especially vulnerable to representational errors.
Using java.math.BigDecimal
The third alternative is to use the java.math.BigDecimal class. BigDecimal is an arbitrary precision math class. That means that there is no limit to the size of the numbers you can store, and that arithmetic done using BigDecimal numbers is precise.
There are two disadvantages to using BigDecimal numbers. First, arithmetic operations are much, much slower than when doing arithmetic with integers or floating-point numbers. Rather than using the built-in hardware support, numbers are added, subtracted, and multiplied the same way you did it in grade school, one digit at a time. The second limitation is that there is no built-in formatting for BigDecimal numbers, so that kind of eliminates BigDecimal for our purposes.
[If you write arithmetic or accounting type applications, IBM has released several replacements for the BigDecimal class that are available through alphaWorks, and are, apparently, free for your use and of higher quality.]
For your puposes, it looks like using a double is the best compromise.
Using NumberFormat
In addition to a numeric value, you'll need a formatting object. Since you want to format all of your MoneyLabels using the same convention, this formatting object can be static, shared between all of the MoneyLabels.
The java.text package contains formatting objects, and the one you'll need is NumberFormat. You don't have to construct a custom version of NumberFormat for the MoneyLabel class; the NumberFormat class comes with a "pre-built" model which you can instantiate by calling the static NumberFormat method getCurrencyInstance(). That's enough planning to get started, so let's create the file MoneyLabel.java and add the following code:
- Import the java.text package.
- Extend the MyLabel class, naming the new class MoneyLabel.
- Add a private double field named value.
- Add a private static NumberFormat field named fmt. Initialize it by calling the NumberFormat.getCurrencyInstance() method.
Your code should look like this. Once you've entered it in, make sure you compile, just to check for syntax errors. It's better to fix them as you go then try and do them all at once at the end.
|
MoneyLabel.java - Version .01
|
| import java.text.*;
public class MoneyLabel extends MyLabel
{
private double value;
private static NumberFormat fmt =
NumberFormat.getCurrencyInstance();
}
|
Step 2: Methods
Once you've decided on the data your new class will add, you have to decide on the interface. What should users be able to tell your object to do?
Well, since you now are storing a numeric value, users might like to read that value; you'll probably want to provide an accessor method, getValue(). The value also has to get set, so you probably want to provide a setValue() method. You don't need to provide access to your fmt field, however. You'll use that internally. When users change the value of your object through setValue(), you'll automatically use your NumberFormat field to format and display the data. You can also define private methods, as well as private fields. For instance, rather than formatting the value field and then calling setText() inside the setValue() method, why not create a private method named format() and do the work inside there? Then, any time you need to format your value you can call this method rather than calling fmt.format() and then setText(). Here's what your MoneyLabel class will look like after adding your new methods.
|
MoneyLabel.java - Version .02
|
| import java.text.*;
public class MoneyLabel extends MyLabel
{
// Public methods ----------------------
public double getValue()
{
return value;
} public void setValue(double newValue)
{
value = newValue;
format();
} // Private methods ----------------------
private void format()
{
setText(fmt.format(value));
}
// Private data -------------------------
private double value;
private static NumberFormat fmt =
NumberFormat.getCurrencyInstance();
}
|
|
Why Move the Fields?
|
| I've moved the fields to the bottom of the class rather than keeping them at the top. Now that you know about private, users looking at your class are going to be more interested in the interface--the public methods--than in the private fields and methods which they can't access anyway. |
Step 3: Try it out
At this point we should be able to create an applet that adds a MoneyLabel object and then send your MoneyLabel the setValue() message.
Call your class ML1, and add the following code:
|
ML1.java - Testing MoneyLabel Version .02
|
| import java.applet.*;
public class ML1 extends Applet
{
public void init()
{
MoneyLabel money = new MoneyLabel();
money.setAlignment(MoneyLabel.RIGHT);
money.setValue(2345673945.34753623);
add(money);
}
}
|
Problems
The first problem is not so much with our class as with Java in general.
Take a look at the ML1 applet running just underneath the source code, and then look at the picture of the applet running here on my machine in applet viewer. Are they displaying the same number? On my machine, if I use Navigator, then I see thirty-four cents [incorrect] while IE and appletviewer display thirty-five cents [correct]. This is a problem with Java [and distributed applications] in general. When you send out your version of MoneyLabel, it uses the version of NumberFormat installed on the user's machine. If two JVM's have different versions of their class libraries, then you'll get different results as you can see, [or perhaps not see], here. You can't really do anything about that problem, but the second problem is something you can tackle. Did you notice that there is no way to initialize a MoneyLabel object? You'd like to be able to write both of these:
MoneyLabel m1 = new MoneyLabel(234.45);
MoneyLabel m2 = new MoneyLabel("5.34"); |
You'd also like the MoneyLabel to automatically set it's alignment to the right, so you don't have to do it manually as in ML1.java. To do this, you need to write a constructor for the MoneyLabel class. Unfortunately, inheritance complicates constructors a little. You really can't write a construtor for MoneyLabel, because its superclass, MyLabel doesn't yet have a constructor. Given that, you might be surprised that MoneyLabel works at all. In the next section, you'll learn how to add constructors to your classes once inheritance gets involved.
Something to Talk About
In the last section, I mentioned the different versioning problems with class libraries installed on the user's machine, and suggested that it was a problem you couldn't solve.
But, that's not entirely correct. Tell me how you think you might go about solving the problem in this particular case, so MoneyLabel objects perform identically on different platforms. (You don't have to actually solve the problem. Just tell us how you'd approach it in a sentence or two.)
Please continue to the next section of this lesson.
|