Lesson 10.5 Constructors
Constructors and Inheritance
Now is the time for you to learn how to add constructors to your newly created subclasses. This is important because, unlike the other methods in a class, constructors are not inherited.
You'll also learn about a new type of access specifier: protected. Protected access allows "child" classes direct access to their "parent's" data, while keeping out the general public.
Before you turn to writing the new constructors for the MyLabel and MoneyLabel classes, let's briefly review some constructor basics.
Constructor Review
Constructors:
- are initializers for objects. They are used to provide meaningful values for an object's fields when the object is first created.
- are invoked by using the new operator. [Sometimes this is done indirectly, when calling a static factory method.] When called using the new operator, the constructor "returns" a reference to a newly created object.
- have the same name as the class.
Mechanics
Whenever a new object is constructed, Java performs the following construction tasks in this order:
- First, the object's "superclass" object is constructed and its fields intialized.
- Any fields having intializers are given a value.
- Any fields not having intializers are given a default value: zero, false, or null, depending on the type of the field.
- The statements in the body of the constructor are executed in order.
You'll recall from Lesson 9.2, "Constructors", if your class contains no constructors at all, then Java will automatically "write" a default constructor for you. The automatic constructor simply follows the first three of these steps. Since there is no explicit constructor, it cannot do the fourth.
This automatic constructor is the reason you can write:
| MoneyLabel lbl = new MoneyLabel(); |
But, how does the constructor for the superclass, MyLabel, get called?
Constructors and Inheritance
As you can see from the list of construction tasks, Java has to create the superclass portion of an object before it can construct the "current" portion.
Normally, it does this by implicitly calling the object's superclass constructor. Thus, when we write this:
| MoneyLabel lbl = new MoneyLabel(); |
the "automatic" default constructor first does this:
and the "automatic" default constructor in the MyLabel class does this:
and the default constructor in the Label class calls:
and the default Component constructor calls:
Only after all of the fields in the Object portion of your MoneyLabel are initialized and all the statements in the Object() default constructor executed, will the fields be initialized and constructor statements in the Component, Label, and MyLabel classes be carried out.
MoneyLabel(double)
This scheme means that, to write our MoneyLabel constructor that takes a double, we can just do this:
|
The MoneyLabel(double) Constructor
|
public MoneyLabel(double value)
{
this.value = value;
setAlignment(RIGHT);
format();
} |
This constructor first sets the field value, then sets the default alignment to RIGHT. We don't have to worry about whether we can call setAlignment() because by the time we get to this point, the Label part of the MoneyLabel is completely initialized.
In the same way, we can end the MoneyLabel(double) constructor by setting the text using the private method format(), certain that the setText() method will work. [Note that we don't have to use MoneyLabel.RIGHT when inside the MoneyLabel class. We can refer to the field simply as RIGHT.]
MoneyLabel(String)
We can follow a similar scheme for the MoneyLabel(String) constructor. This constructor takes a String, but, unlike the Label(String) constructor, it first converts the String to a number, stores the number in value, and then uses format() to display the result.
We can use this() to do the last three steps, but remember that this() must be called as the first line inside the constructor where it's used. Thus MoneyLabel(String) must look something like this:
public MoneyLabel(String s)
{
this([turn s into a double]);
} |
Here's a version that works fine, provided the String s is a "well-formed" number:
|
The MoneyLabel(String) Constructor [1]
|
public MoneyLabel(String s)
{
this((new Double(s)).doubleValue());
} |
To make the constructor as robust as possible, however, it would be nice to accept either of the following String arguments as well:
MoneyLabel ml1 = new MoneyLabel("123,45.07");
MoneyLabel ml2 = new MoneyLabel("$ 5.53"); |
To do this, we simply have to "strip out" the comma, dollar sign, and space from the passed argument, before converting it.
You already learned how to do this in Lesson 6, "Iteration". You can't, however, stick multiple lines of code in the argument you're passing to this(), and you can't have any code before this(). The solution is to write a private method in the MoneyLabel class that converts a String to a double, stripping out the comma, dollar sign, and space as mentioned.
Unfortunately when you try to do this, you'll find that you simply can't call the method. You are trying to call it before your MoneyLabel object actually exists, so there is no object to contain the method. The solution, fortunately, is simple. Make the method static, and all is well.
Here's a completed imlementation of the MoneyLabel(String) constructor, along with the private method doubleFromString().
The MoneyLabel(String) Constructor [2]
The private doubleFromString() Method
|
public MoneyLabel(String s)
{
this(doubleFromString(s));
}
static private
double doubleFromString(String s)
{
// 1. Converted String in temp
String temp = "";
double dAns = 0.0;
// 2. Remove space, comma, $ from s
int len = s.length();
for (int i = 0; i < len; i++)
{
char ch = s.charAt(i);
if (ch != ' ' &&
ch != '$' && ch != ',')
{
temp += ch;
}
}
// 3. Convert result to double
try
{
Double d = new Double(temp);
dAns = d.doubleValue();
}
catch (NumberFormatException ne) { }
// 4. Return converted s or 0.0
return dAns;
} |
Using super()
Now that you've finished the MoneyLabel constructors, let's take a quick look at the MyLabel constructors. Suppose you want to write a MyLabel(String) constructor, so you can say:
| MyLabel ml = new MyLabel("Hi There"); |
You can use the same strategy we used for MoneyLabel. Your constructor would look like this:
public MyLabel(String s)
{
setText(s);
} |
The only problem with this is it's not very efficient. Rather than calling the default Label constructor [implicitly] and then calling the setText() method, it would be nice if we could tell the Label constructor to use the Label(String) constructor in the first place, thereby eliminating the unnecessary default construction.
Is that possible? Yep. All you have to do is use the super() method.
The super() method works just like the this() method does. Instead of calling another constructor in the same class, however, super() calls the superclass constructor of your choice.
You select which superclass constructor is called by your choice of arguments when you call super(). Like this(), the super() constructor must appear as the first line of your construtor. You can't use both this() and super() in the same constructor. It's one or the other.
Here are three of the constructors for the MyLabel class, built using super():
|
MoneyLabel Constructors
|
// With constructors
import java.awt.*;
public class MyLabel extends Label
{
public MyLabel(String s)
{
super(s);
}
public MyLabel()
{
super();
}
public MyLabel(String s, int align)
{
super(s, align);
}
} |
Access Control Deja Vu
The access specifiers public and private allow you to control who has access to the "insides" of your class. Methods meant to be used by others should be public; most of the time, your data should be private.
Having children, however, complicates things for your objects a bit, just as it does in real life.
For instance, if you come to my house and decide you want a soda from my refrigerator, you'd just ask me and I'll get it for you. Most likely you wouldn't just go help yourself. And, I certainly wouldn't want anyone walking by my house to walk in the front door and grab a soda out of the refrigerator.
On the other hand, I don't want my children to come ask me to get them a soda everytime they want one. I'd never get anything done. I want to give my children a different level of access to my refrigerator than I want to give to the general public.
That's not to say that I want my children to have unlimited access to everything I own. I don't, for instance, give my children free reign with my credit cards or my car.
Protected
With objects, it's much the same. The public and private access specifiers are too limiting to reflect the actual relationships between your classes and their subclasses. In this case, you want the option to offer protected access.
Specifying protected access allows your derived [sub]classes to access fields defined in their parent class, but doesn't allow general public access.
Resist the impulse to make all of your fields protected, however. If you already provide accessor and mutator methods for a field, there's not much to gain by making it protected as well.
Naming Restrictions
What happens when a child class has a field with the same name as a field in the parent class? In this case, the field in the parent class is said to be "shadowed".
You can access the parent's field by prefixing the field name with super like this:
System.out.println("Child = " + a);
System.out.println("Parent= " + super.a); |
Something to Talk About
Now that the MoneyLabel class has a pair of constructors, you'll find that the automatic default constructor no longer works.
How would you write the default constructor for the MoneyLabel class?
Did you use super()? Why or why not?
Please continue to the next section of this lesson.
|