|
JavaFAQ Home » Java Lessons by Jon Huhtala

Interfaces, instanceof, and object conversion and casting
Overview
In the previous lesson, you
learned that an object reference can point to an object of a descendant class.
For example, if vehiclePtr
is a Vehicle object
reference and Car, Rowboat, Airplane, and MotorCycle are subclasses of Vehicle, it is not obvious what kind of object vehiclePtr is referencing.
In this lesson, you will learn
how to determine the type of an object. You will also learn how to convert and
cast an object so that it can be referenced and used in a variety of ways. The
concept is similar to the conversion and casting of primitives, but an object's
type never changes.
Finally, you will learn how to
define and use an interface
to further categorize a class and force certain behavior upon it.
The instanceof operator
object-reference instanceof class-name
For example, using the class
hierarchy mentioned above, if vPointer is a Vehicle object reference that is currently pointing to a Rowboat object, the following
expression will evaluate to true
vPointer instanceof Rowboat
public class App { public static void main(String[] args)
{
// Declare an array of superclass object
references.
Mammal[] pets = new
Mammal[5];
// Load the array with objects that
descend from the superclass.
pets[0] = new
Cat("Fluffy", 10); pets[1] = new Mouse("Speedy",
1); pets[2] = new Mouse("Mickey",
2); pets[3] = new Cat("Tiger",
3); pets[4] = new Cat("Garfield",
15);
// Display information about cats. Other
objects will be skipped.
Utility.skip(); for (int i = 0; i < pets.length;
i++) { if (pets[i] instanceof
Cat) System.out.println(" " +
pets[i].toString()); } } }
abstract class Mammal
{ private String name; public Mammal(String iName)
{ setName(iName); } public boolean
setName(String nName) { if (nName.length() > 0)
{ name =
nName; return true;
} else { name =
"unknown"; return
false; } } public String getName()
{ return name; } public abstract
boolean setAge(int nAge); public abstract int getAge();
public abstract String toString(); }
class Cat extends Mammal
{ private int age; public static final int MAX_LIFE =
25; public Cat(String iName, int iAge) {
super(iName); setAge(iAge); }
public boolean setAge(int nAge) { if (nAge >= 0
&& nAge <= MAX_LIFE) { age =
nAge; return true;
} else return
false; } public int getAge() {
return age; } public String toString()
{ return getName() + " (cat), " + age;
} }
class Mouse extends Mammal { private int
age; public static final int MAX_LIFE = 5; public
Mouse(String iName, int iAge) {
super(iName); setAge(iAge); }
public boolean setAge(int nAge) { if (nAge >= 0
&& nAge <= MAX_LIFE) { age =
nAge; return true;
} else return
false; } public int getAge() {
return age; } public String toString()
{ return this.getName() + " (mouse), " +
age; } }
Object conversion and
casting
-
Make it possible to reference an object in a
different way by assigning it to an object reference of a different type.
-
"Object conversion" is handled automatically by
the compiler. You have already seen an example in the above program where
Cat and Mouse objects are assigned to
the elements within an array of Mammal object references. The compiler will let you assign an
object to an object reference of an ancestor type (a class from which it
descends in the class hierarchy).
"Object conversion" is really a
misnomer as the object itself is NOT converted or changed in any way. Once
instantiated, an object NEVER changes its type (a Cat will always be a Cat). The concept, however, is similar to the
"widening conversions" performed on primitive data types.
For example, based upon the
following class hierarchy
The compiler would NOT permit
the following:
Cat x = new Cat(); Manx y = x;
The second statement will result in an
"incompatible types" error message. The statement attempts to assign a Cat object to a Manx object reference. Because
Manx is below Cat in the hierarchy, this is
not automatically allowed.
The following, however will
compile successfully:
Cat x = new Cat(); Manx y = (Manx) x;
It is ultimately the programmer's
responsibility to ensure that such a cast is appropriate.
The most common use of "object casting" is when
calling certain packaged Java methods that return a reference to a generic
Object. The narrowing
conversion requires an explicit cast as shown by the following diagram:
|
Application |
|
Some packaged Java method |
|
Uses a reference to an object of a
particular type
| |
automatic conversion |
|
Uses a reference to a generic Object
| |
|
-------> |
| |
|
explicit cast |
|
<------- |
-
Example: The following program is another
variation on the sample used in the previous lesson. It constructs a Vector object (a growable array
of generic Object
references) and loads it with a mix of Cat and Mouse objects. It then loops to retrieve the generic Object references, casts them
as Mammal object
references, and displays information about the Cat or Mouse object.
import java.util.*; public class App { public static void main(String[]
args) {
// Declare a Vector (a growable
array).
Vector pets = new Vector();
// Load the
Vector with various Cat and Mouse objects.
pets.add(new
Cat("Fluffy", 10)); pets.add(new Mouse("Speedy",
1)); pets.add(new Mouse("Mickey",
2)); pets.add(new Cat("Tiger",
3)); pets.add(new Cat("Garfield",
15));
// Display information extracted from the
Vector.
Utility.skip(); for (int i = 0; i
< pets.size(); i++) { Mammal critter =
(Mammal) pets.get(i);
System.out.println(" " + critter.getName() + " is "
+
critter.getAge()); } } }
abstract class Mammal { private String
name; public Mammal(String iName) {
setName(iName); } public boolean setName(String nName)
{ if (nName.length() > 0)
{ name =
nName; return true;
} else { name =
"unknown"; return
false; } } public String getName()
{ return name; } public abstract
boolean setAge(int nAge); public abstract int getAge();
public abstract String toString(); }
class Cat extends Mammal
{ private int age; public static final int MAX_LIFE =
25; public Cat(String iName, int iAge) {
super(iName); setAge(iAge); }
public boolean setAge(int nAge) { if (nAge >= 0
&& nAge <= MAX_LIFE) { age =
nAge; return true;
} else return
false; } public int getAge() {
return age; } public String toString()
{ return getName() + " (cat), " + age;
} }
class Mouse extends Mammal { private int
age; public static final int MAX_LIFE = 5; public
Mouse(String iName, int iAge) {
super(iName); setAge(iAge); }
public boolean setAge(int nAge) { if (nAge >= 0
&& nAge <= MAX_LIFE) { age =
nAge; return true;
} else return
false; } public int getAge() {
return age; } public String toString()
{ return this.getName() + " (mouse), " +
age; } }
Notes:
-
To use the Vector class, the java.util package must be imported
-
The add() method of the Vector class is used to add a generic Object reference to the
Vector object. A
widening conversion will automatically be performed.
-
The size() method of the Vector class is used to determine how many Object references the Vector object contains.
-
The get() method of the Vector class is used to retrieve a generic Object reference stored in
the specified index location of the Vector object. An explicit cast is needed in order to assign
the object to a reference of the appropriate type (in this case a Mammal). Without the cast,
this program will not compile.
Interfaces
-
Are similar to classes that contain ONLY
abstract methods. They can have no instance variables, they can have no
constructor, and they can have no instance methods that have a body.
-
Are defined with the interface keyword. The general syntax is:
interface interface-name { abstract methods go here... }
All methods of an interface MUST be abstract so
the abstract keyword can
be omitted (it is implied). For example, in advanced Java you will learn about
the WindowListener
interface in the java.awt.event package. The code for this interface is as
follows:
interface WindowListener { public void
windowActivated(WindowEvent e); public void
windowClosed(WindowEvent e); public void windowClosing(WindowEvent
e); public void windowDeactivated(WindowEvent e); public
void windowDeiconified(WindowEvent e); public void
windowIconified(WindowEvent e); public void
windowOpened(WindowEvent e); }
-
Are implemented by a class via the implements keyword to represent
an "is a" relationship. Technically, they do
NOT violate Java's restriction of single-inheritance because interfaces don't
encapsulate anything (so there is nothing to inherit). For example, the class
header for a Windows program might be coded as follows:
public class App extends Frame implements
WindowListener
Which indicates that the App class "is a" Frame and "is a" WindowListener. If more than
one interface is implemented by a class, the interface names are simply coded
as a list as shown by the following example:
public class App extends Frame implements WindowListener,
ActionListener
Which is the header of a Windows program
that implements the WindowListener and the ActionListener interfaces. You will learn what these (and other)
packaged interfaces do in advanced Java.
-
Force implementing classes to define their
abstract methods. Failure to do so will result in a compile error. For
example, a class that implements the WindowListener interface must define all seven of its required
methods.
-
Are detected by the instanceof operator. For example, if myApp is an App class object whose class
header was coded as shown above, the following expression would evaluate as
true:
myApp instanceof WindowListener
-
Can be used as a "type" for an object
reference. When used this way, the object being referenced must be of a class
that either implements the interface or descend from one that does. Such
references can be converted and cast in much the same way as when a class name
is used as the "type". For example, based upon the most recent of the App class headers shown above,
the following code is valid:
WindowListener a = new App(); Object b =
a; ActionListener c = (ActionListener) b; App d = (App)
c;
The key to understanding
this code is that ONLY ONE App OBJECT EXISTS. The result is to have four references of
different types for that single object. The construction and "processing" of
the object are as follows:
-
It was instantiated by the
first statement as an App object and assigned to WindowListener reference a. The compiler allows this
because it is a widening conversion (an App "is a" WindowListener).
-
It was then assigned to
Object reference b. This is allowed because it
is a widening conversion (an App "is an" Object).
-
It was then assigned to
ActionListener
reference c. The
compiler requires a cast for this because it sees it as a narrowing
conversion (an Object
"is NOT" an ActionListener). The cast will work at runtime, however,
because the JVM will see that an App object that "is an" ActionListener.
-
It was finally assigned to
App reference d. The compiler requires a
cast for this because it sees it as a narrowing conversion (an ActionListener "is NOT" an
App). The cast will
work at runtime, however, because the JVM will see that an App object "is an" App.
-
Example: The following
program is yet another variation on the previous samples. It contains the
definition of a Cat class
and a Porcupine class
that are both subclasses of Mammal. It also defines an interface named Petable that is implemented by
Cat, but not by Porcupine. The application
class constructs an array of Mammal object references and loads it with a mix of Cat and Porcupine objects. It then uses
instanceof to display
information about only the Petable objects.
public class App { public static void
main(String[] args) {
// Declare an array of
superclass object references.
Mammal[] pets = new
Mammal[5];
// Load the array with objects that
descend from the superclass.
pets[0] = new
Cat("Fluffy", 10); pets[1] = new Porcupine("Spike",
4); pets[2] = new Porcupine("Needles",
7); pets[3] = new Cat("Tiger",
3); pets[4] = new Cat("Garfield",
15);
// Display information about Petable objects.
Others will be skipped.
Utility.skip(); for (int i = 0; i < pets.length;
i++) { if (pets[i] instanceof
Petable)
System.out.println(" " + pets[i].getName() + ": "
+
((Petable) pets[i]).likes()); }
} }
interface Petable { public String
likes(); }
abstract class Mammal { private String
name; public Mammal(String iName) {
setName(iName); } public boolean setName(String nName)
{ if (nName.length() > 0)
{ name =
nName; return true;
} else { name =
"unknown"; return
false; } } public String getName()
{ return name; } public abstract
boolean setAge(int nAge); public abstract int getAge();
public abstract String toString(); }
class Cat extends Mammal implements
Petable {
private int age; public static final int MAX_LIFE = 25;
public Cat(String iName, int iAge) {
super(iName); setAge(iAge); }
public boolean setAge(int nAge) { if (nAge >= 0
&& nAge <= MAX_LIFE) { age =
nAge; return true;
} else return
false; } public int getAge() {
return age; } public String toString()
{ return getName() + " (cat), " + age;
} public
String likes() { return "Scratch behind
ears"; } }
class Porcupine extends Mammal { private int
age; public static final int MAX_LIFE = 10; public
Porcupine(String iName, int iAge) {
super(iName); setAge(iAge); }
public boolean setAge(int nAge) { if (nAge >= 0
&& nAge <= MAX_LIFE) { age =
nAge; return true;
} else return
false; } public int getAge() {
return age; } public String toString()
{ return this.getName() + " (porcupine), " +
age; } }
Notes:
-
The Petable interface has only
one abstract method named likes() which must be defined by any class that implements
the interface.
-
The Cat class implements the
Petable interface and
defines the required likes() method.
-
The application class
loads the array of Mammal object references and then loops to find objects that
implement the Petable
interface. When one is found, information about the object is displayed.
Notice how the object must be cast in order to call its likes() method.
Looking ahead
For convenience, Java allows a
class to be defined within another class and even inside a method. Such classes
are known as "inner classes" and are the subject of the next lesson.
Lab exercise for Ferris
students
E-mail your answers to this
assignment no later than
the due date listed in the class schedule.
Review questions
-
Based upon the following
class headers, if x is an object of the
Appliance class and y is an object of the Washer class which of the expressions below
will evaluate to true? (choose
three)
public abstract class Item public class
Appliance extends Item public class Washer
extends Appliance implements Electricity, Plumbing
-
x instanceof Object
-
y instanceof Plumbing
-
x instanceof Electricity
-
y instanceof Item
-
x instanceof Washer
-
Based upon these class
headers where each instantiable class has a no-argument constructor, if x is an object of the Appliance class and y is an object of the Washer class which of the statements below will compile?
(choose three)
public abstract class Item public class
Appliance extends Item public class Washer
extends Appliance implements Electricity, Plumbing
-
Item z = new Item();
-
Electricity e = y;
-
Appliance a = y;
-
Electricity e = new Washer();
-
Washer w = x;
-
Java's Runnable interface contains the following
code:
interface Runnable { public void run(); }
Based upon this information,
which of the following class definitions will compile successfully?
(choose two)
-
public class App extends Runnable { }
-
public class App implements Runnable { public void
run(); }
-
public abstract class App implements Runnable { }
-
public class App implements Runnable { public void run()
{} }
-
True or False: If Frame is a subclass of Window and x is the reference of a Window object, the following statement changes the Window object into a Frame object.
Frame y = (Frame) x;
-
True
-
False
Printer Friendly Page
Send to a Friend
..
Search here again if you need more info!
|