Now that we’ve covered fundamental Java syntax, we are ready to begin object-oriented programming in Java. All Java programs use objects, and the type of an object is defined by its class or interface. Every Java program is defined as a class, and nontrivial programs include a number of classes and interface definitions. This chapter explains how to define new classes and how to do object-oriented programming with them. We also introduce the concept of an interface, but a full discussion of interfaces and Java’s type system is deferred until Chapter 4.
This is a fairly lengthy chapter, so let’s begin with an overview and some definitions.
Classes are the most fundamental structural element of all Java programs. You cannot write Java code without defining a class. All Java statements appear within classes, and all methods are implemented within classes.
Here are a couple important definitions:
A class is a collection of data fields that hold values and methods that operate on those values. A class defines a new reference type, such as the Point
type defined in Chapter 2.
The Point
class defines a type that is the set of all possible two-dimensional points.
A Point
object is a value of that type: it represents a single two-dimensional point.
Objects are often created by instantiating a class with the new
keyword and a constructor invocation, as shown here:
Point
p
=
new
Point
(
1.0
,
2.0
);
Constructors are covered later in this chapter in “Creating and Initializing Objects”.
A class definition consists of a signature and a body. The class signature defines the name of the class and may also specify other important information. The body of a class is a set of members enclosed in curly braces. The members of a class usually include fields and methods, and may also include constructors, initializers, and nested types.
Members can be static or nonstatic. A static member belongs to the class itself while a nonstatic member is associated with the instances of a class (see “Fields and Methods”).
The signature of a class may declare that the class extends another class. The extended class is known as the superclass and the extension is known as the subclass. A subclass inherits the members of its superclass and may declare new members or override inherited methods with new implementations.
The members of a class may have access modifiers public
, protected
, or private
.5 These modifiers specify their visibility and accessibility to clients and to subclasses. This allows classes to control access to members that are not part of their public API. This ability to hide members enables an object-oriented design technique known as data encapsulation, which we discuss in “Data Hiding and Encapsulation”.
The signature of a class may also declare that the class implements one or more interfaces. An interface is a reference type similar to a class that defines method signatures but does not usually include method bodies to implement the methods.
However, from Java 8 onward, interfaces may use the keyword default
to indicate that a method specified in the interface is optional. If a method is optional, the interface file must include a default implementation (hence the choice of keyword) which will be used by all implementing classes that do not provide an implementation of the optional method.
A class that implements an interface is required to provide bodies for the interface’s nondefault methods. Instances of a class that implement an interface are also instances of the interface type.
Classes and interfaces are the most important of the five fundamental reference types defined by Java. Arrays, enumerated types (or “enums”), and annotation types (usually just called “annotations”) are the other three. Arrays are covered in Chapter 2. Enums are a specialized kind of class and annotations are a specialized kind of interface—both are discussed later in Chapter 4, along with a full discussion of interfaces.
At its simplest level, a class definition consists of the keyword class
followed by the name of the class and a set of class members within curly braces. The class
keyword may be preceded by modifier keywords and annotations. If the class extends another class, the class name is followed by the extends
keyword and the name of the class being extended. If the class implements one or more interfaces, then the class name or the extends
clause is followed by the implements
keyword and a comma-separated list of interface names. For example:
public
class
Integer
extends
Number
implements
Serializable
,
Comparable
{
// class members go here
}
A generic class may also have type parameters and wildcards as part of its definition (see Chapter 4).
Class declarations may include modifier keywords. In addition to the access control modifiers (public
, protected
, etc.), these include:
abstract
An abstract
class is one whose implementation is incomplete and cannot be instantiated. Any class with one or more abstract
methods must be declared abstract
. Abstract classes are discussed in “Abstract Classes and Methods”.
final
The final
modifier specifies that the class may not be extended. A class cannot be declared to be both abstract
and final
.
strictfp
If a class is declared strictfp
, all its methods behave as if they were declared strictfp
. This modifier is extremely rarely used.
A class can be viewed as a collection of data (also referred to as state
) and code to operate on that state. The data is stored in fields, and the code is organized into methods.
This section covers fields and methods, the two most important kinds of class members. Fields and methods come in two distinct types: class members (also known as static members) are associated with the class itself, while instance members are associated with individual instances of the class (i.e., with objects). This gives us four kinds of members:
Class fields
Class methods
Instance fields
Instance methods
The simple class definition for the class Circle
, shown in Example 3-1, contains all four types of members.
public
class
Circle
{
// A class field
public
static
final
double
PI
=
3.14159
;
// A useful constant
// A class method: just compute a value based on the arguments
public
static
double
radiansToDegrees
(
double
radians
)
{
return
radians
*
180
/
PI
;
}
// An instance field
public
double
r
;
// The radius of the circle
// Two instance methods: they operate on the instance fields of an object
public
double
area
()
{
// Compute the area of the circle
return
PI
*
r
*
r
;
}
public
double
circumference
()
{
// Compute the circumference
// of the circle
return
2
*
PI
*
r
;
}
}
It is not normally good practice to have a public field r
—instead, it would be much more usual to have a private field r
and a method radius()
to provide access to it. The reason for this will be explained later, in “Data Hiding and Encapsulation”. For now, we use a public field simply to give examples of how to work with instance fields.
The following sections explain all four common kinds of members. First, we cover the declaration syntax for fields. (The syntax for declaring methods is covered later in this chapter in “Data Hiding and Encapsulation”.)
Field declaration syntax is much like the syntax for declaring local variables (see Chapter 2) except that field definitions may also include modifiers. The simplest field declaration consists of the field type followed by the field name. The type may be preceded by zero or more modifier keywords or annotations, and the name may be followed by an equals sign and initializer expression that provides the initial value of the field. If two or more fields share the same type and modifiers, the type may be followed by a comma-separated list of field names and initializers. Here are some valid field declarations:
int
x
=
1
;
private
String
name
;
public
static
final
int
DAYS_PER_WEEK
=
7
;
String
[]
daynames
=
new
String
[
DAYS_PER_WEEK
];
private
int
a
=
17
,
b
=
37
,
c
=
53
;
Field modifiers are comprised of zero or more of the following keywords:
public
, protected
, private
These access modifiers specify whether and where a field can be used outside of the class that defines it.
static
If present, this modifier specifies that the field is associated with the defining class itself rather than with each instance of the class.
final
This modifier specifies that once the field has been initialized, its value may never be changed. Fields that are both static
and final
are compile-time constants that javac may inline. final
fields can also be used to create classes whose instances are immutable.
transient
This modifier specifies that a field is not part of the persistent state of an object and that it need not be serialized along with the rest of the object.
volatile
This modifier indicates that the field has extra semantics for concurrent use by two or more threads. The volatile
modifier says that the value of a field must always be read from and flushed to main memory, and that it may not be cached by a thread (in a register or CPU cache). See Chapter 6 for more details.
A class field is associated with the class in which it is defined rather than with an instance of the class. The following line declares a class field:
public
static
final
double
PI
=
3.14159
;
This line declares a field of type double
named PI
and assigns it a value of 3.14159.
The static
modifier says that the field is a class field. Class fields are sometimes called static fields because of this static
modifier. The final
modifier says that the value of the field does not change. Because the field PI
represents a constant, we declare it final
so that it cannot be changed. It is a convention in Java (and many other languages) that constants are named with capital letters, which is why our field is named PI
, not pi
. Defining constants like this is a common use for class fields, meaning that the static
and final
modifiers are often used together. Not all class fields are constants, however. In other words, a field can be declared static
without being declared final
.
final
is almost never a good practice—as multiple threads could update the field and cause behavior that is extremely hard to debug.A public static field is essentially a global variable. The names of class fields are qualified by the unique names of the classes that contain them, however. Thus, Java does not suffer from the name collisions that can affect other languages when different modules of code define global variables with the same name.
The key point to understand about a static field is that there is only a single copy of it. This field is associated with the class itself, not with instances of the class. If you look at the various methods of the Circle
class, you’ll see that they use this field. From inside the Circle
class, the field can be referred to simply as PI
. Outside the class, however, both class and field names are required to uniquely specify the field. Methods that are not part of Circle
access this field as Circle.PI
.
As with class fields, class methods are declared with the static
modifier:
public
static
double
radiansToDegrees
(
double
rads
)
{
return
rads
*
180
/
PI
;
}
This line declares a class method named radiansToDegrees()
. It has a single parameter of type double
and returns a double
value.
Like class fields, class methods are associated with a class, rather than with an object. When invoking a class method from code that exists outside the class, you must specify both the name of the class and the method. For example:
// How many degrees is 2.0 radians?
double
d
=
Circle
.
radiansToDegrees
(
2.0
);
If you want to invoke a class method from inside the class in which it is defined, you don’t have to specify the class name. You can also shorten the amount of typing required via the use of a static import (as discussed in Chapter 2).
Note that the body of our Circle.radiansToDegrees( )
method uses the class field PI
. A class method can use any class fields and class methods of its own class (or of any other class).
A class method cannot use any instance fields or instance methods because class methods are not associated with an instance of the class. In other words, although the radiansToDegrees()
method is defined in the Circle
class, it cannot use the instance part of any Circle
objects.
this
reference to the current object. But class methods are not associated with a specific instance, so have no this
reference, and no access to instance fields.As we discussed earlier, a class field is essentially a global variable. In a similar way, a class method is a global method, or global function. Although radiansToDegrees()
does not operate on Circle
objects, it is defined within the Circle
class because it is a utility method that is sometimes useful when working with circles, and so it makes sense to package it along with the other functionality of the Circle
class.
Any field declared without the static
modifier is an instance field:
public
double
r
;
// The radius of the circle
Instance fields are associated with instances of the class, so every Circle
object we create has its own copy of the double
field r
. In our example, r
represents the radius of a specific circle. Each Circle
object can have a radius independent of all other Circle
objects.
Inside a class definition, instance fields are referred to by name alone. You can see an example of this if you look at the method body of the circumference()
instance method. In code outside the class, the name of an instance method must be prefixed with a reference to the object that contains it. For example, if the variable c
holds a reference to a Circle
object, we use the expression c.r
to refer to the radius of that circle:
Circle
c
=
new
Circle
();
// Create a Circle object; store a ref in c
c
.
r
=
2.0
;
// Assign a value to its instance field r
Circle
d
=
new
Circle
();
// Create a different Circle object
d
.
r
=
c
.
r
*
2
;
// Make this one twice as big
Instance fields are key to object-oriented programming. Instance fields hold the state of an object; the values of those fields make one object distinct from another.
An instance method operates on a specific instance of a class (an object), and any method not declared with the static
keyword is automatically an instance method.
Instance methods are the feature that makes object-oriented programming start to get interesting. The Circle
class defined in Example 3-1 contains two instance methods, area()
and circumference()
, that compute and return the area and circumference of the circle represented by a given Circle
object.
To use an instance method from outside the class in which it is defined, we must prefix it with a reference to the instance that is to be operated on. For example:
// Create a Circle object; store in variable c
Circle
c
=
new
Circle
();
c
.
r
=
2.0
;
// Set an instance field of the object
double
a
=
c
.
area
();
// Invoke an instance method of the object
From within an instance method, we naturally have access to all the instance fields that belong to the object the method was called on. Recall that an object is often best considered to be a bundle containing state (represented as the fields of the object), and behavior (the methods to act on that state).
All instance methods are implemented using an implicit parameter not shown in the method signature. The implicit argument is named this
; it holds a reference to the object through which the method is invoked. In our example, that object is a Circle
.
area()
and circumference()
methods both use the class field PI
. We saw earlier that class methods can use only class fields and class methods, not instance fields or methods. Instance methods are not restricted in this way: they can use any member of a class, whether it is declared static
or not.The implicit this
parameter is not shown in method signatures because it is usually not needed; whenever a Java method accesses the instance fields in its class, it is implicit that it is accessing fields in the object referred to by the this
parameter. The same is true when an instance method invokes another instance method in the same class—it’s taken that this means “call the instance method on the current object.”
However, you can use the this
keyword explicitly when you want to make it clear that a method is accessing its own fields and/or methods. For example, we can rewrite the area()
method to use this
explicitly to refer to instance fields:
public
double
area
()
{
return
Circle
.
PI
*
this
.
r
*
this
.
r
;
}
This code also uses the class name explicitly to refer to class field PI
. In a method this simple, it is not normally necessary to be quite so explicit. In more complicated cases, however, you may sometimes find that it increases the clarity of your code to use an explicit this
where it is not strictly required.
In some cases, the this
keyword is required, however. For example, when a method parameter or local variable in a method has the same name as one of the fields of the class, you must use this
to refer to the field, because the field name used alone refers to the method parameter or local variable.
For example, we can add the following method to the Circle
class:
public
void
setRadius
(
double
r
)
{
this
.
r
=
r
;
// Assign the argument (r) to the field (this.r)
// Note that we cannot just say r = r
}
Some developers will deliberately choose the names of their method arguments in such a way that they don’t clash with field names, so the use of this
can largely be avoided.
Finally, note that while instance methods can use the this
keyword, class methods cannot. This is because class methods are not associated with individual objects.
Now that we’ve covered fields and methods, let’s move on to other important members of a class. In particular, we’ll look at constructors—these are class members whose job is to initialize the fields of a class as new instances of the class are created.
Take another look at how we’ve been creating Circle
objects:
Circle
c
=
new
Circle
();
This can easily be read as the creation of a new instance of Circle
, by calling something that looks a bit like a method. In fact, Circle()
is an example of a constructor. This is a member of a class that has the same name as the class, and has a body, like a method.
Here’s how a constructor works. The new
operator indicates that we need to create a new instance of the class. First of all, memory is allocated to hold the new object instance. Then, the constructor body is called, with any arguments that have been specified. The constructor uses these arguments to do whatever initialization of the new object is necessary.
Every class in Java has at least one constructor, and their purpose is to perform any necessary initialization for a new object. Because we didn’t explicitly define a constructor for our Circle
class in Example 3-1, the javac compiler automatically gave us a constructor (called the default constructor) that takes no arguments and performs no special initialization.
There is some obvious initialization we could do for our circle objects, so let’s define a constructor. Example 3-2 shows a new definition for Circle
that contains a constructor that lets us specify the radius of a new Circle
object. We’ve also taken the opportunity to make the field r
protected (to prevent access to it from arbitary objects).
public
class
Circle
{
public
static
final
double
PI
=
3.14159
;
// A constant
// An instance field that holds the radius of the circle
protected
double
r
;
// The constructor: initialize the radius field
public
Circle
(
double
r
)
{
this
.
r
=
r
;
}
// The instance methods: compute values based on the radius
public
double
circumference
()
{
return
2
*
PI
*
r
;
}
public
double
area
()
{
return
PI
*
r
*
r
;
}
public
double
radius
()
{
return
r
;
}
}
When we relied on the default constructor supplied by the compiler, we had to write code like this to initialize the radius explicitly:
Circle
c
=
new
Circle
();
c
.
r
=
0.25
;
With the new constructor, the initialization becomes part of the object creation step:
Circle
c
=
new
Circle
(
0.25
);
Here are some basic facts regarding naming, declaring, and writing constructors:
The constructor name is always the same as the class name.
A constructor is declared without a return type, not even void
.
The body of a constructor is initializing the object. You can think of this as setting up the contents of the this
reference
A constructor may not return this
or any other value.
Sometimes you want to initialize an object in a number of different ways, depending on what is most convenient in a particular circumstance. For example, we might want to initialize the radius of a circle to a specified value or a reasonable default value. Here’s how we can define two constructors for Circle
:
public
Circle
()
{
r
=
1.0
;
}
public
Circle
(
double
r
)
{
this
.
r
=
r
;
}
Because our Circle
class has only a single instance field, we can’t initialize it too many ways, of course. But in more complex classes, it is often convenient to define a variety of constructors.
It is perfectly legal to define multiple constructors for a class, as long as each constructor has a different parameter list. The compiler determines which constructor you wish to use based on the number and type of arguments you supply. This ability to define multiple constructors is analogous to method overloading.
A specialized use of the this
keyword arises when a class has multiple constructors; it can be used from a constructor to invoke one of the other constructors of the same class. In other words, we can rewrite the two previous Circle
constructors as follows:
// This is the basic constructor: initialize the radius
public
Circle
(
double
r
)
{
this
.
r
=
r
;
}
// This constructor uses this() to invoke the constructor above
public
Circle
()
{
this
(
1.0
);
}
This is a useful technique when a number of constructors share a significant amount of initialization code, as it avoids repetition of that code. In more complex cases, where the constructors do a lot more initialization, this can be a very useful technique.
There is an important restriction on using this()
: it can appear only as the first statement in a constructor—but the call may be followed by any additional initialization a particular constructor needs to perform. The reason for this restriction involves the automatic invocation of superclass constructors, which we’ll explore later in this chapter.
The fields of a class do not necessarily require initialization. If their initial values are not specified, the fields are automatically initialized to the default value false
, u0000
, 0
, 0.0
, or null
, depending on their type (see Table 2-1 for more details). These default values are specified by the Java language specification and apply to both instance fields and class fields.
If the default field value is not appropriate for your field, you can instead explicitly provide a different initial value. For example:
public
static
final
double
PI
=
3.14159
;
public
double
r
=
1.0
;
Consider the following code excerpt, which shows a constructor and two instance fields of a hypothetical class:
public
class
SampleClass
{
public
int
len
=
10
;
public
int
[]
table
=
new
int
[
len
];
public
SampleClass
()
{
for
(
int
i
=
0
;
i
<
len
;
i
++)
table
[
i
]
=
i
;
}
// The rest of the class is omitted...
}
In this case, the code generated by javac
for the constructor is actually equivalent to the following:
public
SampleClass
()
{
len
=
10
;
table
=
new
int
[
len
];
for
(
int
i
=
0
;
i
<
len
;
i
++)
table
[
i
]
=
i
;
}
If a constructor begins with a this()
call to another constructor, the field initialization code does not appear in the first constructor. Instead, the initialization is handled in the constructor invoked by the this()
call.
So, if instance fields are initialized in constructor, where are class fields initialized? These fields are associated with the class, even if no instances of the class are ever created. This means they need to be initialized even before a constructor is called.
To support this, javac
generates a class initialization method automatically for every class. Class fields are initialized in the body of this method, which is invoked exactly once before the class is first used (often when the class is first loaded by the Java VM.)
As with instance field initialization, class field initialization expressions are inserted into the class initialization method in the order in which they appear in the source code. This means that the initialization expression for a class field can use the class fields declared before it. The class initialization method is an internal method that is hidden from Java programmers. In the class file, it bears the name <clinit>
(and this method could be seen by, for example, examining the class file with javap
—see Chapter 13 for more details on how to use javap
to do this).
So far, we’ve seen that objects can be initialized through the initialization expressions for their fields and by arbitrary code in their constructors. A class has a class initialization method, which is like a constructor, but we cannot explicitly define the body of this method as we can for a constructor. Java does allow us to write arbitrary code for the initialization of class fields, however, with a construct known as a static initializer. A static initializer is simply the keyword static
followed by a block of code in curly braces. A static initializer can appear in a class definition anywhere a field or method definition can appear. For example, consider the following code that performs some nontrivial initialization for two class fields:
// We can draw the outline of a circle using trigonometric functions
// Trigonometry is slow, though, so we precompute a bunch of values
public
class
TrigCircle
{
// Here are our static lookup tables and their own initializers
private
static
final
int
NUMPTS
=
500
;
private
static
double
sines
[]
=
new
double
[
NUMPTS
];
private
static
double
cosines
[]
=
new
double
[
NUMPTS
];
// Here's a static initializer that fills in the arrays
static
{
double
x
=
0.0
;
double
delta_x
=
(
Circle
.
PI
/
2
)/(
NUMPTS
-
1
);
for
(
int
i
=
0
,
x
=
0.0
;
i
<
NUMPTS
;
i
++,
x
+=
delta_x
)
{
sines
[
i
]
=
Math
.
sin
(
x
);
cosines
[
i
]
=
Math
.
cos
(
x
);
}
}
// The rest of the class is omitted...
}
A class can have any number of static initializers. The body of each initializer block is incorporated into the class initialization method, along with any static field initialization expressions. A static initializer is like a class method in that it cannot use the this
keyword or any instance fields or instance methods of the class.
Classes are also allowed to have instance initializers. An instance initializer is like a static initializer, except that it initializes an object, not a class. A class can have any number of instance initializers, and they can appear anywhere a field or method definition can appear. The body of each instance initializer is inserted at the beginning of every constructor for the class, along with any field initialization expressions. An instance initializer looks just like a static initializer, except that it doesn’t use the static
keyword. In other words, an instance initializer is just a block of arbitrary Java code that appears within curly braces.
Instance initializers can initialize arrays or other fields that require complex initialization. They are sometimes useful because they locate the initialization code right next to the field, instead of separating into a constructor. For example:
private
static
final
int
NUMPTS
=
100
;
private
int
[]
data
=
new
int
[
NUMPTS
];
{
for
(
int
i
=
0
;
i
<
NUMPTS
;
i
++)
data
[
i
]
=
i
;
}
In practice, however, this use of instance initializers is fairly rare.
The Circle
defined earlier is a simple class that distinguishes circle objects only by their radii. Suppose, instead, that we want to represent circles that have both a size and a position. For example, a circle of radius 1.0 centered at point 0,0 in the Cartesian plane is different from the circle of radius 1.0 centered at point 1,2. To do this, we need a new class, which we’ll call PlaneCircle
.
We’d like to add the ability to represent the position of a circle without losing any of the existing functionality of the Circle
class. This is done by defining PlaneCircle
as a subclass of Circle
so that PlaneCircle
inherits the fields and methods of its superclass, Circle
. The ability to add functionality to a class by subclassing, or extending, is central to the object-oriented programming paradigm.
In Example 3-3, we show how we can implement PlaneCircle
as a subclass of the Circle
class.
public
class
PlaneCircle
extends
Circle
{
// We automatically inherit the fields and methods of Circle,
// so we only have to put the new stuff here.
// New instance fields that store the center point of the circle
private
final
double
cx
,
cy
;
// A new constructor to initialize the new fields
// It uses a special syntax to invoke the Circle() constructor
public
PlaneCircle
(
double
r
,
double
x
,
double
y
)
{
super
(
r
);
// Invoke the constructor of the superclass, Circle()
this
.
cx
=
x
;
// Initialize the instance field cx
this
.
cy
=
y
;
// Initialize the instance field cy
}
public
double
getCentreX
()
{
return
cx
;
}
public
double
getCentreY
()
{
return
cy
;
}
// The area() and circumference() methods are inherited from Circle
// A new instance method that checks whether a point is inside the circle
// Note that it uses the inherited instance field r
public
boolean
isInside
(
double
x
,
double
y
)
{
double
dx
=
x
-
cx
,
dy
=
y
-
cy
;
// Distance from center
double
distance
=
Math
.
sqrt
(
dx
*
dx
+
dy
*
dy
);
// Pythagorean theorem
return
(
distance
<
r
);
// Returns true or false
}
}
Note the use of the keyword extends
in the first line of Example 3-3. This keyword tells Java that PlaneCircle
extends, or subclasses, Circle
, meaning that it inherits the fields and methods of that class.
Circle
as well as having a position. This is probably the simplest, but is not always the most suitable, especially in larger systems.The definition of the isInside()
method shows field inheritance; this method uses the field r
(defined by the Circle
class) as if it were defined right in PlaneCircle
itself. PlaneCircle
also inherits the methods of Circle
. Therefore, if we have a
object referenced by variable pc
, we can say:
double
ratio
=
pc
.
circumference
()
/
pc
.
area
();
This works just as if the area()
and circumference()
methods were defined in
itself.
Another feature of subclassing is that every PlaneCircle
object is also a perfectly legal Circle
object. If pc
refers to a PlaneCircle
object, we can assign it to a Circle
variable and forget all about its extra positioning capabilities:
// Unit circle at the origin
PlaneCircle
pc
=
new
PlaneCircle
(
1.0
,
0.0
,
0.0
);
Circle
c
=
pc
;
// Assigned to a Circle variable without casting
This assignment of a PlaneCircle
object to a Circle
variable can be done without a cast. As we discussed in Chapter 2 a conversion like this is always legal. The value held in the Circle
variable c
is still a valid PlaneCircle
object, but the compiler cannot know this for sure, so it doesn’t allow us to do the opposite (narrowing) conversion without a cast:
// Narrowing conversions require a cast (and a runtime check by the VM)
PlaneCircle
pc2
=
(
PlaneCircle
)
c
;
boolean
origininside
=
((
PlaneCircle
)
c
).
isInside
(
0.0
,
0.0
);
This distinction is covered in more detail in “Lambda Expressions”, where we talk about the distinction between the compile and runtime type of an object.
When a class is declared with the final
modifier, it means that it cannot be extended or subclassed. java.lang.String
is an example of a final
class. Declaring a class final
prevents unwanted extensions to the class: if you invoke a method on a String
object, you know that the method is the one defined by the String
class itself, even if the String
is passed to you from some unknown outside source.
In our example, PlaneCircle
is a subclass of Circle
. We can also say that Circle
is the superclass of PlaneCircle
. The superclass of a class is specified in its extends
clause:
public
class
PlaneCircle
extends
Circle
{
...
}
Every class you define has a superclass. If you do not specify the superclass with an extends
clause, the superclass is the class java.lang.Object
. The Object
class is special for a couple of reasons:
It is the only class in Java that does not have a superclass.
All Java classes inherit the methods of Object
.
Because every class (except Object
) has a superclass, classes in Java form a class hierarchy, which can be represented as a tree with Object
at its root.
Object
has no superclass, but every other class has exactly one superclass. A subclass cannot extend more than one superclass. See Chapter 4 for more information on how to achieve a similar result.Figure 3-1 shows a partial class hierarchy diagram that includes our Circle
and
classes, as well as some of the standard classes from the Java API.
Look again at the PlaneCircle()
constructor from Example 3-3:
public
PlaneCircle
(
double
r
,
double
x
,
double
y
)
{
super
(
r
);
// Invoke the constructor of the superclass, Circle()
this
.
cx
=
x
;
// Initialize the instance field cx
this
.
cy
=
y
;
// Initialize the instance field cy
}
Although this constructor explicitly initializes the cx
and cy
fields newly defined by PlaneCircle
, it relies on the superclass Circle()
constructor to initialize the inherited fields of the class. To invoke the superclass constructor, our constructor calls super()
.
super
is a reserved word in Java. One of its uses is to invoke the constructor of a superclass from within a subclass constructor. This use is analogous to the use of this()
to invoke one constructor of a class from within another constructor of the same class. Invoking a constructor using super()
is subject to the same restrictions as is using this()
:
super()
can be used in this way only within a constructor.
The call to the superclass constructor must appear as the first statement within the constructor, even before local variable declarations.
The arguments passed to super()
must match the parameters of the superclass constructor. If the superclass defines more than one constructor, super()
can be used to invoke any one of them, depending on the arguments passed.
Java guarantees that the constructor of a class is called whenever an instance of that class is created. It also guarantees that the constructor is called whenever an instance of any subclass is created. In order to guarantee this second point, Java must ensure that every constructor calls its superclass constructor.
Thus, if the first statement in a constructor does not explicitly invoke another constructor with this()
or super()
, the javac
compiler inserts the call super()
(i.e., it calls the superclass constructor with no arguments). If the superclass does not have a visible constructor that takes no arguments, this implicit invocation causes a compilation error.
Consider what happens when we create a new instance of the PlaneCircle
class.
First, the PlaneCircle
constructor is invoked.
This constructor explicitly calls super(r)
to invoke a Circle
constructor.
That Circle()
constructor implicitly calls super()
to invoke the constructor of its superclass, Object
(Object
only has one constructor).
At this point, we’ve reached the top of the hierarchy and constructors start to run.
The body of the Object
constructor runs first.
When it returns, the body of the Circle()
constructor runs.
Finally, when the call to super(r)
returns, the remaining statements of the
constructor are executed.
What all this means is that constructor calls are chained; any time an object is created, a sequence of constructors is invoked, from subclass to superclass on up to Object
at the root of the class hierarchy. Because a superclass constructor is always invoked as the first statement of its subclass constructor, the body of the Object
constructor always runs first, followed by the constructor of its subclass and on down the class hierarchy to the class that is being instantiated.
There is one missing piece in the previous description of constructor chaining. If a constructor does not invoke a superclass constructor, Java does so implicitly. But what if a class is declared without a constructor? In this case, Java implicitly adds a constructor to the class. This default constructor does nothing but invoke the superclass constructor.
For example, if we don’t declare a constructor for the PlaneCircle
class, Java implicitly inserts this constructor:
public
PlaneCircle
()
{
super
();
}
If the superclass, Circle
, doesn’t declare a no-argument constructor, the super()
call in this automatically inserted default constructor for PlaneCircle()
causes a compilation error. In general, if a class does not define a no-argument constructor, all its subclasses must define constructors that explicitly invoke the superclass constructor with the necessary arguments.
If a class does not declare any constructors, it is given a no-argument constructor by default. Classes declared public
are given public
constructors. All other classes are given a default constructor that is declared without any visibility modifier: such a constructor has default visibility. (The notion of visibility is explained later in this chapter.)
If you are creating a public
class that should not be publicly instantiated, you should declare at least one non-public
constructor to prevent the insertion of a default public
constructor. Classes that should never be instantiated (such as java.lang.Math
or java.lang.System
) should define a private
constructor. Such a constructor can never be invoked from outside of the class, but it prevents the automatic insertion of the default constructor.
For the sake of example, imagine that our PlaneCircle
class needs to know the distance between the center of the circle and the origin (0,0). We can add another instance field to hold this value:
public
double
r
;
Adding the following line to the constructor computes the value of the field:
this
.
r
=
Math
.
sqrt
(
cx
*
cx
+
cy
*
cy
);
// Pythagorean theorem
But wait; this new field r
has the same name as the radius field r
in the Circle
superclass. When this happens, we say that the field r
of PlaneCircle
hides the field r
of Circle
. (This is a contrived example, of course: the new field should really be called distanceFromOrigin
).
With this new definition of PlaneCircle
, the expressions r
and this.r
both refer to the field of PlaneCircle
. How, then, can we refer to the field r
of Circle
that holds the radius of the circle? A special syntax for this uses the super
keyword:
r
// Refers to the PlaneCircle field
this
.
r
// Refers to the PlaneCircle field
super
.
r
// Refers to the Circle field
Another way to refer to a hidden field is to cast this
(or any instance of the class) to the appropriate superclass and then access the field:
((
Circle
)
this
).
r
// Refers to field r of the Circle class
This casting technique is particularly useful when you need to refer to a hidden field defined in a class that is not the immediate superclass. Suppose, for example, that classes A
, B
, and C
all define a field named x
and that C
is a subclass of B
, which is a subclass of A
. Then, in the methods of class C
, you can refer to these different fields as follows:
x
// Field x in class C
this
.
x
// Field x in class C
super
.
x
// Field x in class B
((
B
)
this
).
x
// Field x in class B
((
A
)
this
).
x
// Field x in class A
super
.
super
.
x
// Illegal; does not refer to x in class A
x
in the superclass of a superclass with super.super.x
. This is not legal syntax.Similarly, if you have an instance c
of class C
, you can refer to the three fields named x
like this:
c
.
x
// Field x of class C
((
B
)
c
).
x
// Field x of class B
((
A
)
c
).
x
// Field x of class A
So far, we’ve been discussing instance fields. Class fields can also be hidden. You can use the same super
syntax to refer to the hidden value of the field, but this is never necessary, as you can always refer to a class field by prepending the name of the desired class. Suppose, for example, that the implementer of PlaneCircle
decides that the Circle.PI
field does not express to enough decimal places. She can define her own class field PI
:
public
static
final
double
PI
=
3.14159265358979323846
;
Now code in PlaneCircle
can use this more accurate value with the expressions PI
or PlaneCircle.PI
. It can also refer to the old, less accurate value with the expressions super.PI
and Circle.PI
. However, the area()
and circumference()
methods inherited by PlaneCircle
are defined in the Circle
class, so they use the value Circle.PI
, even though that value is hidden now by PlaneCircle.PI
.
When a class defines an instance method using the same name, return type, and parameters as a method in its superclass, that method overrides the method of the superclass. When the method is invoked for an object of the class, it is the new definition of the method that is called, not the old definition from the superclass.
Method overriding is an important and useful technique in object-oriented programming. PlaneCircle
does not override either of the methods defined by Circle
, but suppose we define another subclass of Circle
, named Ellipse
.
It is important for Ellipse
to override the area()
and circumference()
methods of Circle
in this case, because the formulas used to compute the area and circumference of a circle do not work for ellipses.
The upcoming discussion of method overriding considers only instance methods. Class methods behave quite differently, and they cannot be overridden. Just like fields, class methods can be hidden by a subclass but not overridden. As noted earlier in this chapter, it is good programming style to always prefix a class method invocation with the name of the class in which it is defined. If you consider the class name part of the class method name, the two methods have different names, so nothing is actually hidden at all.
Before we go any further with the discussion of method overriding, you should understand the difference between method overriding and method overloading. As we discussed in Chapter 2, method overloading refers to the practice of defining multiple methods (in the same class) that have the same name but different parameter lists. This is very different from method overriding, so don’t get them confused.
Although Java treats the fields and methods of a class analogously in many ways, method overriding is not like field hiding at all. You can refer to hidden fields simply by casting an object to an instance of the appropriate superclass, but you cannot invoke overridden instance methods with this technique. The following code illustrates this crucial difference:
class
A
{
// Define a class named A
int
i
=
1
;
// An instance field
int
f
()
{
return
i
;
}
// An instance method
static
char
g
()
{
return
'A'
;
}
// A class method
}
class
B
extends
A
{
// Define a subclass of A
int
i
=
2
;
// Hides field i in class A
int
f
()
{
return
-
i
;
}
// Overrides method f in class A
static
char
g
()
{
return
'B'
;
}
// Hides class method g() in class A
}
public
class
OverrideTest
{
public
static
void
main
(
String
args
[])
{
B
b
=
new
B
();
// Creates a new object of type B
System
.
out
.
println
(
b
.
i
);
// Refers to B.i; prints 2
System
.
out
.
println
(
b
.
f
());
// Refers to B.f(); prints -2
System
.
out
.
println
(
b
.
g
());
// Refers to B.g(); prints B
System
.
out
.
println
(
B
.
g
());
// A better way to invoke B.g()
A
a
=
(
A
)
b
;
// Casts b to an instance of class A
System
.
out
.
println
(
a
.
i
);
// Now refers to A.i; prints 1
System
.
out
.
println
(
a
.
f
());
// Still refers to B.f(); prints -2
System
.
out
.
println
(
a
.
g
());
// Refers to A.g(); prints A
System
.
out
.
println
(
A
.
g
());
// A better way to invoke A.g()
}
}
While this difference between method overriding and field hiding may seem surprising at first, a little thought makes the purpose clear.
Suppose we are manipulating a bunch of Circle
and Ellipse
objects. To keep track of the circles and ellipses, we store them in an array of type Circle[]
. We can do this because Ellipse
is a subclass of Circle
, so all Ellipse
objects are legal Circle
objects.
When we loop through the elements of this array, we don’t have to know or care whether the element is actually a Circle
or an Ellipse
. What we do care about very much, however, is that the correct value is computed when we invoke the area()
method of any element of the array. In other words, we don’t want to use the formula for the area of a circle when the object is actually an ellipse!
All we really want is for the objects we’re computing the areas of to “do the right thing”—the Circle
objects to use their definition of how to compute their own area, and the Ellipse
objects to use the definition that is correct for them.
Seen in this context, it is not surprising at all that method overriding is handled differently by Java than is field hiding.
If we have a Circle[]
array that holds Circle
and Ellipse
objects, how does the compiler know whether to call the area()
method of the Circle
class or the Ellipse
class for any given item in the array? In fact, the source code compiler cannot know this at compilation time.
Instead, javac
creates bytecode that uses virtual method lookup at runtime. When the interpreter runs the code, it looks up the appropriate area()
method to call for each of the objects in the array. That is, when the interpreter interprets the expression o.area()
, it checks the actual runtime type of the object referred to by the variable o
and then finds the area()
method that is appropriate for that type.
C#
or C++) do not do virtual lookup by default and instead have a virtual
keyword that programmers must explicitly use if they want to allow subclasses to be able to override a method.The JVM does not simply use the area()
method that is associated with the static type of the variable o
, as that would not allow method overriding to work in the way detailed earlier. Virtual method lookup is the default for Java instance methods. See Chapter 4 for more details about compile-time and runtime type and how this affects virtual method lookup.
We’ve seen the important differences between method overriding and field hiding. Nevertheless, the Java syntax for invoking an overridden method is quite similar to the syntax for accessing a hidden field: both use the super
keyword. The following code illustrates:
class
A
{
int
i
=
1
;
// An instance field hidden by subclass B
int
f
()
{
return
i
;
}
// An instance method overridden by subclass B
}
class
B
extends
A
{
int
i
;
// This field hides i in A
int
f
()
{
// This method overrides f() in A
i
=
super
.
i
+
1
;
// It can retrieve A.i like this
return
super
.
f
()
+
i
;
// It can invoke A.f() like this
}
}
Recall that when you use super
to refer to a hidden field, it is the same as casting this
to the superclass type and accessing the field through that. Using super
to invoke an overridden method, however, is not the same as casting the this
reference. In other words, in the previous code, the expression super.f()
is not the same as ((A)this).f()
.
When the interpreter invokes an instance method with the super
syntax, a modified form of virtual method lookup is performed. The first step, as in regular virtual method lookup, is to determine the actual class of the object through which the method is invoked. Normally, the runtime search for an appropriate method definition would begin with this class. When a method is invoked with the super
syntax, however, the search begins at the superclass of the class. If the superclass implements the method directly, that version of the method is invoked. If the superclass inherits the method, the inherited version of the method is invoked.
Note that the super
keyword invokes the most immediately overridden version of a method. Suppose class A
has a subclass B
that has a subclass C
and that all three classes define the same method f()
. The method C.f()
can invoke the method B.f()
, which it overrides directly, with super.f()
. But there is no way for C.f()
to invoke A.f()
directly: super.super.f()
is not legal Java syntax. Of course, if C.f()
invokes B.f()
, it is reasonable to suppose that B.f()
might also invoke A.f()
.
This kind of chaining is relatively common when working with overridden methods: it is a way of augmenting the behavior of a method without replacing the method entirely.
super
to invoke an overridden method with the super()
method call used in a constructor to invoke a superclass constructor. Although they both use the same keyword, these are two entirely different syntaxes. In particular, you can use super
to invoke an overridden method anywhere in the overriding class while you can use super()
only to invoke a superclass constructor as the very first statement of a constructor.It is also important to remember that super
can be used only to invoke an overridden method from within the class that overrides it. Given a reference to an Ellipse
object e
, there is no way for a program that uses e
to invoke the area()
method defined by the Circle
class on e
.
We started this chapter by describing a class as a collection of data and methods. One of the most important object-oriented techniques we haven’t discussed so far is hiding the data within the class and making it available only through the methods. This technique is known as encapsulation because it seals the data (and internal methods) safely inside the “capsule” of the class, where it can be accessed only by trusted users (i.e., the methods of the class).
Why would you want to do this? The most important reason is to hide the internal implementation details of your class. If you prevent programmers from relying on those details, you can safely modify the implementation without worrying that you will break existing code that uses the class.
Another reason for encapsulation is to protect your class against accidental or willful stupidity. A class often contains a number of interdependent fields that must be in a consistent state. If you allow a programmer (including yourself) to manipulate those fields directly, he may change one field without changing important related fields, leaving the class in an inconsistent state. If instead he has to call a method to change the field, that method can be sure to do everything necessary to keep the state consistent. Similarly, if a class defines certain methods for internal use only, hiding these methods prevents users of the class from calling them.
Here’s another way to think about encapsulation: when all the data for a class is hidden, the methods define the only possible operations that can be performed on objects of that class.
Once you have carefully tested and debugged your methods, you can be confident that the class will work as expected. On the other hand, if all the fields of the class can be directly manipulated, the number of possibilities you have to test becomes unmanageable.
Other, secondary, reasons to hide fields and methods of a class include:
Internal fields and methods that are visible outside the class just clutter up the API. Keeping visible fields to a minimum keeps your class tidy and therefore easier to use and understand.
If a method is visible to the users of your class, you have to document it. Save yourself time and effort by hiding it instead.
Java defines access control rules that can restrict members of a class from being used outside the class. In a number of examples in this chapter, you’ve seen the public
modifier used in field and method declarations. This public
keyword, along with protected
and private
(and one other, special one) are access control modifiers; they specify the access rules for the field or method.
Access control on a per-package basis is not directly part of the Java language. Instead, access control is usually done at the level of classes and members of classes.
By default, top-level classes are accessible within the package in which they are defined. However, if a top-level class is declared public
, it is accessible everywhere.
The members of a class are always accessible within the body of the class. By default, members are also accessible throughout the package in which the class is defined. This default level of access is often called package access. It is only one of four possible levels of access. The other three levels are defined by the public
, protected
, and private
modifiers. Here is some example code that uses these modifiers:
public
class
Laundromat
{
// People can use this class.
private
Laundry
[]
dirty
;
// They cannot use this internal field,
public
void
wash
()
{
...
}
// but they can use these public methods
public
void
dry
()
{
...
}
// to manipulate the internal field.
// A subclass might want to tweak this field
protected
int
temperature
;
}
These access rules apply to members of a class:
All the fields and methods of a class can always be used within the body of the class itself.
If a member of a class is declared with the public
modifier, it means that the member is accessible anywhere the containing class is accessible. This is the least restrictive type of access control.
If a member of a class is declared private
, the member is never accessible, except within the class itself. This is the most restrictive type of access control.
If a member of a class is declared protected
, it is accessible to all classes within the package (the same as the default package accessibility) and also accessible within the body of any subclass of the class, regardless of the package in which that subclass is defined.
If a member of a class is not declared with any of these modifiers, it has default access (sometimes called package access) and it is accessible to code within all classes that are defined in the same package but inaccessible outside of the package.
protected
—as default access does not allow access by subclasses outside the package.protected
access requires more elaboration. Suppose class A
declares a protected
field x
and is extended by a class B
, which is defined in a different package (this last point is important). Class B
inherits the protected
field x
, and its code can access that field in the current instance of B
or in any other instances of B
that the code can refer to. This does not mean, however, that the code of class B
can start reading the protected fields of arbitrary instances of A
.
Let’s look at this language detail in code. Here’s the definition for A
:
package
javanut6
.
ch03
;
public
class
A
{
protected
final
String
name
;
public
A
(
String
named
)
{
name
=
named
;
}
public
String
getName
()
{
return
name
;
}
}
Here’s the definition for B
:
package
javanut6
.
ch03
.
different
;
import
javanut6.ch03.A
;
public
class
B
extends
A
{
public
B
(
String
named
)
{
super
(
named
);
}
@Override
public
String
getName
()
{
return
"B: "
+
name
;
}
}
javanut6.ch03.different
is just a different package than javanut6.ch03
; it is not contained inside it or related to it in any way.However, if we try to add this new method to B
, we will get a compilation error, because instances of B
do not have access to arbitary instances of A
:
public
String
examine
(
A
a
)
{
return
"B sees: "
+
a
.
name
;
}
If we change the method to this:
public
String
examine
(
B
b
)
{
return
"B sees another B: "
+
b
.
name
;
}
then the compiler is happy, because instances of the same exact type can always see each other’s protected
fields. Of course, if B
was in the same package as A
then any instance of B
could read any protected field of any instance of A
because protected
fields are visible to every class in the same package.
The Java specification states that:
A subclass inherits all the instance fields and instance methods of its superclass accessible to it.
If the subclass is defined in the same package as the superclass, it inherits all non-private
instance fields and methods.
If the subclass is defined in a different package, it inherits all protected
and public
instance fields and methods.
private
fields and methods are never inherited; neither are class fields or class methods.
Constructors are not inherited (instead, they are chained, as described earlier in this chapter).
However, some programmers are confused by the statement that a subclass does not inherit the inaccessible fields and methods of its superclass. It could be taken to imply that when you create an instance of a subclass, no memory is allocated for any private
fields defined by the superclass. This is not the intent of the statement, however.
This existence of potentially inaccessible members seems to be in conflict with the statement that the members of a class are always accessible within the body of the class. To clear up this confusion, we define “inherited members” to mean those superclass members that are accessible.
Then the correct statement about member accessibility is: “All inherited members and all members defined in this class are accessible.” An alternative way of saying this is:
A class inherits all instance fields and instance methods (but not constructors) of its superclass.
The body of a class can always access all the fields and methods it declares itself. It can also access the accessible fields and members it inherits from its superclass.
We summarize the member access rules in Table 3-1.
Member visibility | ||||
---|---|---|---|---|
Accessible to | Public | Protected | Default | Private |
Defining class |
Yes |
Yes |
Yes |
Yes |
Class in same package |
Yes |
Yes |
Yes |
No |
Subclass in different package |
Yes |
Yes |
No |
No |
Nonsubclass different package |
Yes | No | No | No |
Here are some simple rules of thumb for using visibility modifiers:
Use public
only for methods and constants that form part of the public API of the class. The only acceptable usage of public
fields is for constants or immutable objects, and they must be also declared final
.
Use protected
for fields and methods that aren’t required by most programmers using the class but that may be of interest to anyone creating a subclass as part of a different package.
protected
members are technically part of the exported API of a class. They must be documented and cannot be changed without potentially breaking code that relies on them.Use the default package visibility for fields and methods that are internal implementation details but are used by cooperating classes in the same package.
Use private
for fields and methods that are used only inside the class and should be hidden everywhere else.
If you are not sure whether to use protected
, package, or private
accessibility, start with private
. If this is overly restrictive, you can always relax the access restrictions slightly (or provide accessor methods, in the case of fields).
This is especially important when designing APIs because increasing access restrictions is not a backward-compatible change and can break code that relies on access to those members.
In the Circle
example, we declared the public
field. The Circle
class is one in which it may well be reasonable to keep that field publicly accessible; it is a simple enough class, with no dependencies between its fields. On the other hand, our current implementation of the class allows a Circle
object to have a negative radius, and circles with negative radii should simply not exist. As long as the radius is stored in a public
field, however, any programmer can set the field to any value she wants, no matter how unreasonable. The only solution is to restrict the programmer’s direct access to the field and define public
methods that provide indirect access to the field. Providing public
methods to read and write a field is not the same as making the field itself public
. The crucial difference is that methods can perform error checking.
We might, for example, want to prevent Circle
objects with negative radii—these are obviously not sensible, but our current implementation does not prohibit this. In Example 3-4, we show how we might change the definition of Circle
to prevent this.
This version of Circle
declares the r
field to be protected
and defines accessor methods named getRadius()
and setRadius()
to read and write the field value while enforcing the restriction on negative radius values. Because the r
field is protected
, it is directly (and more efficiently) accessible to subclasses.
package
shapes
;
// Specify a package for the class
public
class
Circle
{
// The class is still public
// This is a generally useful constant, so we keep it public
public
static
final
double
PI
=
3.14159
;
protected
double
r
;
// Radius is hidden but visible to subclasses
// A method to enforce the restriction on the radius
// This is an implementation detail that may be of interest to subclasses
protected
void
checkRadius
(
double
radius
)
{
if
(
radius
<
0.0
)
throw
new
IllegalArgumentException
(
"radius may not be negative."
);
}
// The non-default constructor
public
Circle
(
double
r
)
{
checkRadius
(
r
);
this
.
r
=
r
;
}
// Public data accessor methods
public
double
getRadius
()
{
return
r
;
}
public
void
setRadius
(
double
r
)
{
checkRadius
(
r
);
this
.
r
=
r
;
}
// Methods to operate on the instance field
public
double
area
()
{
return
PI
*
r
*
r
;
}
public
double
circumference
()
{
return
2
*
PI
*
r
;
}
}
We have defined the Circle
class within a package named shapes
. Because r
is protected
, any other classes in the shapes
package have direct access to that field and can set it however they like. The assumption here is that all classes within the shapes
package were written by the same author or a closely cooperating group of authors and that the classes all trust each other not to abuse their privileged level of access to each other’s implementation details.
Finally, the code that enforces the restriction against negative radius values is itself placed within a protected
method, checkRadius()
. Although users of the Circle
class cannot call this method, subclasses of the class can call it and even override it if they want to change the restrictions on the radius.
boolean
, the get()
method may be replaced with an equivalent method that begins with “is.” For example, the accessor method for a boolean
field named readable
is typically called isReadable()
instead of getReadable()
.In Example 3-4, we declared our Circle
class to be part of a package named shapes
. Suppose we plan to implement a number of shape classes: Rectangle
, Square
, Ellipse
, Triangle
, and so on. We can give these shape classes our two basic area()
and circumference()
methods. Now, to make it easy to work with an array of shapes, it would be helpful if all our shape classes had a common superclass, Shape
. If we structure our class hierarchy this way, every shape object, regardless of the actual type of shape it represents, can be assigned to variables, fields, or array elements of type Shape
. We want the Shape
class to encapsulate whatever features all our shapes have in common (e.g., the area()
and circumference()
methods). But our generic Shape
class doesn’t represent any real kind of shape, so it cannot define useful implementations of the methods. Java handles this situation with abstract methods.
Java lets us define a method without implementing it by declaring the method with the abstract
modifier. An abstract
method has no body; it simply has a signature definition followed by a semicolon.6 Here are the rules about abstract
methods and the abstract
classes that contain them:
Any class with an abstract
method is automatically abstract
itself and must be declared as such. To fail to do so is a compilation error.
An abstract
class cannot be instantiated.
A subclass of an abstract
class can be instantiated only if it overrides each of the abstract
methods of its superclass and provides an implementation (i.e., a method body) for all of them. Such a class is often called a concrete subclass, to emphasize the fact that it is not abstract
.
If a subclass of an abstract
class does not implement all the abstract
methods it inherits, that subclass is itself abstract
and must be declared as such.
static
, private
, and final
methods cannot be abstract
, because these types of methods cannot be overridden by a subclass. Similarly, a final
class cannot contain any abstract
methods.
A class can be declared abstract
even if it does not actually have any abstract
methods. Declaring such a class abstract
indicates that the implementation is somehow incomplete and is meant to serve as a superclass for one or more subclasses that complete the implementation. Such a class cannot be instantiated.
Classloader
class that we will meet in Chapter 11 is a good example of an abstract class that does not have any abstract methods.Let’s look at an example of how these rules work. If we define the Shape
class to have abstract
area()
and circumference()
methods, any subclass of Shape
is required to provide implementations of these methods so that it can be instantiated. In other words, every Shape
object is guaranteed to have implementations of these methods defined. Example 3-5 shows how this might work. It defines an abstract
Shape
class and two concrete subclasses of it.
public
abstract
class
Shape
{
public
abstract
double
area
();
// Abstract methods: note
public
abstract
double
circumference
();
// semicolon instead of body.
}
class
Circle
extends
Shape
{
public
static
final
double
PI
=
3.14159265358979323846
;
protected
double
r
;
// Instance data
public
Circle
(
double
r
)
{
this
.
r
=
r
;
}
// Constructor
public
double
getRadius
()
{
return
r
;
}
// Accessor
public
double
area
()
{
return
PI
*
r
*
r
;
}
// Implementations of
public
double
circumference
()
{
return
2
*
PI
*
r
;
}
// abstract methods.
}
class
Rectangle
extends
Shape
{
protected
double
w
,
h
;
// Instance data
public
Rectangle
(
double
w
,
double
h
)
{
// Constructor
this
.
w
=
w
;
this
.
h
=
h
;
}
public
double
getWidth
()
{
return
w
;
}
// Accessor method
public
double
getHeight
()
{
return
h
;
}
// Another accessor
public
double
area
()
{
return
w
*
h
;
}
// Implementation of
public
double
circumference
()
{
return
2
*(
w
+
h
);
}
// abstract methods
}
Each abstract
method in Shape
has a semicolon right after its parentheses. They have no curly braces, and no method body is defined. Using the classes defined in Example 3-5, we can now write code such as:
Shape
[]
shapes
=
new
Shape
[
3
];
// Create an array to hold shapes
shapes
[
0
]
=
new
Circle
(
2.0
);
// Fill in the array
shapes
[
1
]
=
new
Rectangle
(
1.0
,
3.0
);
shapes
[
2
]
=
new
Rectangle
(
4.0
,
2.0
);
double
totalArea
=
0
;
for
(
int
i
=
0
;
i
<
shapes
.
length
;
i
++)
totalArea
+=
shapes
[
i
].
area
();
// Compute the area of the shapes
Notice two important points here:
Subclasses of Shape
can be assigned to elements of an array of Shape
. No cast is necessary. This is another example of a widening reference type conversion (discussed in Chapter 2).
You can invoke the area()
and circumference()
methods for any Shape
object, even though the Shape
class does not define a body for these methods. When you do this, the method to be invoked is found using virtual method lookup, which means that the area of a circle is computed using the method defined by Circle
, and the area of a rectangle is computed using the method defined by Rectangle
.
Objects can be converted between different reference types. As with primitive types, reference type conversions can be widening conversions (allowed automatically by the compiler) or narrowing conversions that require a cast (and possibly a runtime check). In order to understand reference type conversions, you need to understand that reference types form a hierarchy, usually called the class hierarchy.
Every Java reference type extends some other type, known as its superclass. A type inherits the fields and methods of its superclass and then defines its own additional fields and methods. A special class named Object
serves as the root of the class hierarchy in Java. All Java classes extend Object
directly or indirectly. The Object
class defines a number of special methods that are inherited (or overridden) by all objects.
The predefined String
class and the Point
class we discussed earlier in this chapter both extend Object
. Thus, we can say that all String
objects are also Object
objects. We can also say that all Point
objects are Object
objects. The opposite is not true, however. We cannot say that every Object
is a String
because, as we’ve just seen, some Object
objects are Point
objects.
With this simple understanding of the class hierarchy, we can define the rules of reference type conversion:
An object cannot be converted to an unrelated type. The Java compiler does not allow you to convert a String
to a Point
, for example, even if you use a cast operator.
An object can be converted to the type of its superclass or of any ancestor class. This is a widening conversion, so no cast is required. For example, a String
value can be assigned to a variable of type Object
or passed to a method where an Object
parameter is expected.
An object can be converted to the type of a subclass, but this is a narrowing conversion and requires a cast. The Java compiler provisionally allows this kind of conversion, but the Java interpreter checks at runtime to make sure it is valid. Only cast an object to the type of a subclass if you are sure, based on the logic of your program, that the object is actually an instance of the subclass. If it is not, the interpreter throws a ClassCastException
. For example, if we assign a String
object to a variable of type Object
, we can later cast the value of that variable back to type String
:
Object
o
=
"string"
;
// Widening conversion from String
// to Object Later in the program...
String
s
=
(
String
)
o
;
// Narrowing conversion from Object
// to String
Arrays are objects and follow some conversion rules of their own. First, any array can be converted to an Object
value through a widening conversion. A narrowing conversion with a cast can convert such an object value back to an array. Here’s an example:
// Widening conversion from array to Object
Object
o
=
new
int
[]
{
1
,
2
,
3
};
// Later in the program...
int
[]
a
=
(
int
[])
o
;
// Narrowing conversion back to array type
In addition to converting an array to an object, an array can be converted to another type of array if the “base types” of the two arrays are reference types that can themselves be converted. For example:
// Here is an array of strings.
String
[]
strings
=
new
String
[]
{
"hi"
,
"there"
};
// A widening conversion to CharSequence[] is allowed because String
// can be widened to CharSequence
CharSequence
[]
sequences
=
strings
;
// The narrowing conversion back to String[] requires a cast.
strings
=
(
String
[])
sequences
;
// This is an array of arrays of strings
String
[][]
s
=
new
String
[][]
{
strings
};
// It cannot be converted to CharSequence[] because String[] cannot be
// converted to CharSequence: the number of dimensions don't match
sequences
=
s
;
// This line will not compile
// s can be converted to Object or Object[], because all array types
// (including String[] and String[][]) can be converted to Object.
Object
[]
objects
=
s
;
Note that these array conversion rules apply only to arrays of objects and arrays of arrays. An array of primitive type cannot be converted to any other array type, even if the primitive base types can be converted:
// Can't convert int[] to double[] even though
// int can be widened to double
// This line causes a compilation error
double
[]
data
=
new
int
[]
{
1
,
2
,
3
};
// This line is legal, however, because int[] can be converted to Object
Object
[]
objects
=
new
int
[][]
{{
1
,
2
},{
3
,
4
}};
As we’ve seen, classes, interfaces, and their members can be declared with one or more modifiers—keywords such as public
, static
, and final
. Let’s conclude this chapter by listing the Java modifiers, explaining what types of Java constructs they can modify, and explaining what they do. Table 3-2 has the details; you can also refer back to “Overview of Classes” and “Field Declaration Syntax” as well as “Method Modifiers”.
Modifier | Used on | Meaning |
---|---|---|
|
Class |
The class cannot be instantiated and may contain unimplemented methods. |
Interface |
All interfaces are |
|
Method |
No body is provided for the method; it is provided by a subclass. The signature is followed by a semicolon. The enclosing class must also be |
|
|
Method |
Implementation of this interface method is optional. The interface provides a default implementation for classes that elect not to implement it. See Chapter 4 for more details. |
|
Class |
The class cannot be subclassed. |
Method |
The method cannot be overridden. |
|
Field |
The field cannot have its value changed. |
|
Variable |
A local variable, method parameter, or exception parameter cannot have its value changed. |
|
|
Method |
The method is implemented in some platform-dependent way (often in C). No body is provided; the signature is followed by a semicolon. |
<None> (package) |
Class |
A non- |
Interface |
A non- |
|
Member |
A member that is not |
|
|
Member |
The member is accessible only within the class that defines it. |
|
Member |
The member is accessible only within the package in which it is defined and within subclasses. |
|
Class |
|
Interface |
The interface is accessible anywhere its package is. |
|
Member |
The member is accessible anywhere its class is. |
|
|
Class |
|
Method |
All floating-point computation done by the method must be performed in a way that strictly conforms to the IEEE 754 standard. In particular, all values, including intermediate results, must be expressed as IEEE |
|
|
Class |
An inner class declared |
Method |
A |
|
Field |
A |
|
Initializer |
The initializer is run when the class is loaded rather than when an instance is created. |
|
|
Method |
The method makes nonatomic modifications to the class or instance, so care must be taken to ensure that two threads cannot modify the class or instance at the same time. For a |
|
Field |
The field is not part of the persistent state of the object and should not be serialized with the object. Used with object serialization; see |
|
Field |
The field can be accessed by unsynchronized threads, so certain optimizations must not be performed on it. This modifier can sometimes be used as an alternative to |
5 There is also the default, aka package, visibility that we will meet later.
6 An abstract
method in Java is something like a pure virtual function in C++ (i.e., a virtual function that is declared = 0
). In C++, a class that contains a pure virtual function is called an abstract class and cannot be instantiated. The same is true of Java classes that contain abstract
methods.