|
|
Review and Tutorial: Extending Classes
(In which Dilbert makes a return. As can be seen on the right these notes are picking some momentum. You should read what follows even if you know it all, just to verify your understanding is for real.
But you can also skip this section the first time through it).
Let's make a quick summary first (please refer to the first Dilbert Review).
Here are the things that should be already known:
this argument that identifies the object being operated on
(the receiving object, the one that owns that method, and through which
the method gets invoked.)
this argument and
therefore do not have a current instance of the class that can be used
to implicitly refer to instance variables or invoke instance methods.
new keyword, which invokes a
class constructor method with a list of arguments.
this call, or a superclass constructor
with a super() call, Java automatically inserts a call to
the superclass constructor that takes no arguments. This enforces
"constructor chaining".
private methods and variables
of another class by "subclassing" -- i.e., by declaring that class in
its extends clause.
java.lang.Object is the default superclass for any class.
It is the root of the Java class hierarchy and has no superclass itself.
All Java classes inherit the methods defined by Object.
static, private, and final
methods cannot be overriden and are not subject to dynamic method
lookup. (This allows compiler optimizations such as inlining).
super keyword.
super keyword.
private or protected
visibility modifiers. Members declared public are
visible everywhere. Members with no visibility modifiers are visible
only within the package.
static final variables). Declaring an
interface creates a new data type.
implements clause and by providing a method body for
each of the abstract methods in the interface.
A class is a collection of data and methods that operate on that data.
The data and methods, taken together, usually serve to define the contents
and capabilities of some kind of object. For example, a circle can be
described by the x, y position of its center and by its
radius (r). There are
a number of things we can do with circles: compute their circumference,
compute their area, check whether points are inside them, and so on. Each
circle is different (i.e., has a different center or radius), but as a
class, circles have certain intrinsic properties that we can capture in
a definition:
public class Circle {
public double x, y; // The coordinates of the center
public double r; // The radius
// Methods that return the circumference and area of the circle
public double circumference () {
return 2 * 3.141592 * r;
}
public double area () {
return 3.141592 * r * r;
}
}
Object Creation Here's how we create a circle object:
The way it works is this: TheCircle c = new Circle();
new keyword creates a
new dynamic instance of the class -- i.e., it allocates the new object.
The constructor method is then called, passing the new object implicitly
(a this reference) and passing the arguments specified
between parentheses (if any) explicitly. The constructor can then start working on the object for initialization purposes.
Defining a Constructor
There is some obvious initialization we could do for our circle objects, so let's
define a constructor. The code below shows a constructor that lets us specify the
initial values for the center and radius of our new Circle object:
public class Circle {
public double x, y, r; // The center and the radius of the circle
// The constructor method (or minitialization procedure)
public Circle(double x, double y, double r) {
this.x = x;
this.y = y;
this.r = r;
}
public double circumference () {
return 2 * 3.141592 * r;
}
public double area () {
return 3.141592 * r * r;
}
}
The part that we have not seen before (that is, the constructor) is in blue. There are two important notes about naming and declaring constructors:
void keyword used.
The this object is implicitly returned; a constructor should not use a
return statement to return a value.
Sometimes you'll want to be able 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 be able to initialize the radius of a circle without initializing the center, or we might want to initialize a circle to have the same center and radius as another circle, or we might want to initialize all the fields to default values.
Doing this is no problem: A class can have any number of constructor methods.
The example below shows how:
public Class Circle {
public double x, y, r;
public Circle(double x, double y, double r) {
this.x = x; this.y = y; this.r = r;
}
public Circle(double r) { x = 0.0; y = 0.0; this.r = r; }
public Circle(Circle c) { x = c.x; y = c.y; r = c.r; }
public Circle() { x = 0.0; y = 0.0; r = 1.0; }
public double circumference () { return 2 * 3.141592 * r; }
public double area () { return 3.141592 * r * r; }
}
Again, the new code is in blue. Method Overloading
The surprising thing in this example could be that all the constructor methods have the same name! So how can the compiler tell them apart? The way that you and I tell them apart is that the four methods take different arguments and are useful in different circumstances. The compiler tells them apart in the same way. In Java a method is distinguished by its name, and by the number, type, and position of its arguments. This is not limited to constructor methods -- any two methods are not the same unless they have the same name, and the same number of arguments of the same type passed at the same position in the argument list. When you call a method and there's more than one method with the same name, the compiler automatically picks the one that matches the data types of the arguments you are passing.
Defining methods with the same name and different argument types is called method overloading. It can be a convenient technique, as long as you only give methods the same name when they perform similar functions on slightly different forms of input data. Overloaded methods may have different return types, but only if they have different arguments. Don't confuse method overloading with method overriding, which we'll discuss later.
this Again
There's a specialized use of the this keyword that 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. So we could rewrite the additional constructors from the previous example in terms of the first
one like this:
public Circle(double x, double y, double r) {
this.x = x; this.y = y; this.r = r;
}
public Circle(double r) { this(0.0, 0.0, r); }
public Circle(Circle c) { this(c.x, c.y, c.r); }
public Circle() { this(0.0, 0.0, 1.0); }
Here the this() call refers to whatever constructor of the class
takes the specified type of arguments. This would be a more impressive example, of course,
if the first constructor that we were invoking did a more significant amount of initialization,
as it might, for example, if we were writing a more complicated class.
There is a very important restriction on this this syntax that is, as an invocation:
it may only appear as the first statement in a constructor. It may, of course, be followed
by any additional initialization that a particular version of the constructor needs to
do. The reason for this restriction involves the automatic invocation of superclass
constructor methods, to which we turn now.
Subclasses and Inheritance
This Circle class is good for abstract mathematical
manipulation. For some applications this is exactly what we need.
For other applications, we might want to be able to manipulate circles
and draw them on the screen. This means we need a new
class, GraphicCircle, that has all the functionality
of Circle, but also has the ability to be drawn.
We want to implement GraphicCircle so that it can make
use of the code we've already written for Circle. One way
to do that is the following:
public class GraphicCircle {
public double x, y;
public double r;
public Color outline, fill;
public double circumference () {
return 2 * 3.141592 * r;
}
public double area () {
return 3.141592 * r * r;
}
public void draw(DrawWindow dw) {
/* code omitted */
}
}
The part that we have taken from the previous class is in
blue. This approach would work but it is not particularly elegant. The problem is that we have to literally carry the code with us, and rewrite it. It would be nice if we didn't have to do that.
Extending a Class
Well, we really don't have to do it that way. We can define
GraphicCircle as an extension, or subclass
of class Circle.
public class GraphicCircle extends Circle {
/* We automatically inherit the variables and methods of
class Circle, so we only have to put the new stuff here.
We omit the constructor for GraphicCircle, for now. */
Color outline, fill;
public void draw(DrawWindow dw) {
dw.drawCircle(x, y, r, outline, fill);
}
}
The part that we take from Circle is also represented in blue,
the mechanism is provided by the object-oriented nature of Java, and we have
come up with something reasonable as the code
for the instance method of GraphicCircle, that is draw().
The extends keyword tells Java that GraphicCircle is a subclass of
Circle, and that it inherits the fields and methods of that class (except for private
fields and methods). The definition of the draw() method shows variable inheritance - this method uses the
Circle variables x, y, and r as if they were defined right in
GraphicCircle itself.
GraphicCircle also inherits the methods of Circle. Thus, if we have a
GraphicCircle object referred to by variable gc, we can say:
This works just as if thedouble area = gc.area();
area() method were defined in GraphicCircle itself.
So this is a convenient way of reusing the Circle class's code.
Another feature of subclassing is that every GraphicCircle object is also a perfectly legal
Circle object. Thus, if gc refers to a GraphicCircle object, we can
assign it to a Circle variable, and we can forget all about its extra graphic capabilities:
That is, it's up to us if we want to ignore its graphic capabilities added byCircle c = gc;
GraphicCircle. Final Classes
When a class is declared with the final modifier, it means that it cannot be extended or
subclassed. java.lang.System is an example of a final class. Declaring a class
final prevents unwanted extensions to the class. (But it also allows the compiler to make
some optimizations when invoking the methods of the class.)
Superclasses, Object, and the Class Hierarcy
In our example GraphicCircle is a subclass of Circle. We can also say that
Circle is the superclass of GraphicCircle. The superclass of a class
is specified in its extends clause:
public class GraphicCircle 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 Object. Object is a special class for a couple
of reasons: Object can be called by any Java object
Object
here.
Because every class has a superclass, classes in Java form a class hierarchy, which can be
represented as a tree with object at its root. The diagram below shows a class hierarchy
which includes our Circle and GraphicCircle classes, as well as some of the
standard classes from the Java API.
Object -+- Circle --- GraphicCircle
|
+- Math
|
+- System
|
+- Component -+- Container --- Panel --- Applet
|
+- Button
|
+- List
The complete class hierarchy for the Java 1.1 API is diagrammed
here. Here's the latest.
Subclass Constructors
In our first example of GraphicCircle we left out the constructor method for
our new GraphicCircle class. Let's implement it now. Here's one way:
public GraphicCircle (double x, double y,
double r,
Color outline,
Color fill) {
this.x = x;
this.y = y;
this.r = r;
this.outline = outline;
this.fill = fill;
}
The constructor relies on the fact that GraphicCircle inherits all of the
variables of Circle and simply initializes those variables itself. But this
duplicates the code of the Circle constructor, and if Circle did
more elaborate initialization, it could become quite wasteful. (Same problem with when we
tried to define a GraphicCircle for the first time). Furthermore, if the
Circle class had internal private fields (discussed later) we
wouldn't be able to initialize them like this. What we need is a way of calling a
Circle constructor from within our GraphicCircle constructor,
and that is provided with the extends mechanism, by Java. Here's how we invoke a superclass's constructor:
public GraphicCircle (double x, double y,
double r,
Color outline,
Color fill) {
super(x, y, r);
this.outline = outline;
this.fill = fill;
}
super is a reserved word in Java. One of its uses is that shown in
the example -- to invoke the constructor method of a superclass. Its use is analogous to the use of
the this keyword to invoke one constructor method of a class from within another. Using
super to invoke a constructor is subject to the same restrictions as using this
to invoke a constructor: super may only be used in this way (with the syntax for a method call, or invocation) within
a constructor method. You can't invoke super(...) in a method, for example, as it wouldn't mean
anything to use it that way.
Constructor Chaining
When you define a class, Java guarantees that the class's constructor method is called whenever an
instance of that class is created. It also guarantees that the constructor is called when an instance
of any subclass is created. In order to guarantee this second point, Java must ensure that every
constructor method calls its superclass constructor method. If the first statement in a constructor
is not an explicit call to a constructor of the superclass with the super keyword, then
Java implicitly inserts the call super() -- that is, it calls the superclass constructor
with no arguments. If the superclass does not have a constructor that takes no arguments, this causes
a compilation error.
There is one exception to the rule that Java invokes super() implicitly if you do not do
so explicitly. If the first line of a constructor, C1, uses the this() syntax
to invoke another constructor, C2, of the class, Java relies on C2 to invoke
the superclass constructor, and does not insert a call to super() into C1. Of
course, if C2 itself uses this() to invoke a third constructor, C2
does not call super() either, but somewhere along the chain, a constructor either explicitly
or implicitly invokes the superclass constructor, which is what is required.
Consider what happens when we create a new instance of the GraphicCircle class. First, the
GraphicCircle constructor shown in our previous example is invoked. This constructor explicitly
invokes a Circle constructor and that Circle constructor implicitly calls
super() to invoke the constructor of its superclass, Object. The body of the
Object constructor runs first, followed by the body of the Circle constructor,
and finally followed by the body of the GraphicCircle constructor.
What this all means is that constructor calls are "chained" -- any time an object is created, a sequence
of constructor methods are 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
body of its subclass, and on down the class hierarchy to the class that is being instantiated.
The Default Constructor
There is one missing piece in the description of constructor chaining above. If a constructor does not invoke a superclass constructor, Java does so implicitly. But what if a class is declared without any constructor at all? 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 did not declare a constructor for the GraphicCircle class,
Java would have implicitly inserted this constructor:
public GraphicCircle() { super(); }
Note that if the superclass, Circle() did not declare a no-argument constructor,
then this automatically inserted default constructor would cause a compilation error. If a class
does not define a no-argument constructor, then all of its subclasses must define constructors
that explicitly invoke the superclass constructor with the necessary arguments. It can be confusing when Java implicitly calls a constructor or inserts a constructor definition into a class -- something is happening that does not appear in your code! Therefore, it is good coding style, whenever you rely on an implicit superclass constructor call or on a default constructor, to insert a comment noting this fact. Your comments might look like those in the following example:
class A {
int i;
public A() {
// Implicit call to super(); here
i = 3;
}
}
class B extends A {
// Default constructor: public B() { super(); }
}
If a class does not declare any constructor, it is given a public constructor by default.
Classes that do not want to be publically instantiated, should declare a protected constructor
to prevent the insertion of this public constructor. Classes that never want to be instantiated
at all (in a specific way,) should define that constructor private. Shadowed Variables
Suppose that our GraphicCircle class has a new variable that specifies the
resolution, in dots per inch, of the DrawWindow object in which it is going
to be drawn. And further, suppose that it names that new variable r:
public class GraphicCircle extends Circle {
Color outline, fill;
float r; // New variable. Resolution in dots-per-inch.
public GraphicCircle(double x, double y,
double rad,
Color o, Color f) {
super(x, y, rad); outline = o; fill = f;
}
public void setResolution(float resolution) {
r = resolution;
}
public void draw(DrawWindow dw) {
dw.drawCircle(x, y, r, outline, fill);
}
}
Now, with this resolution variable declared, when we use the variable r in the
GraphicCircle class, we are no longer referring to the radius of the circle. The
resolution variable r in GraphicCircle shadows the radius
variable r in Circle. (This is a contrived example, of course -- we
could simply rename the variable and avoid the issue. Typically we would rename the variable:
variable shadowing is a necessary part of Java syntax, but is not a useful programming technique.
Your code will be easier to understand if you avoid shadowed variables).
So, how can we refer to the radius variable defined in the Circle class when we
need it? Recall that using a variable, such as r, in the class in which it is
defined is shorthand for:
As you might guess, you can refer to a variablethis.r // Refers to the GraphicCircle resolution variable.
r defined in the superclass like this:
Another way you can do this is to castsuper.r // Refers to the Circle radius variable.
this to the appropriate class and then access the
variable:
This cast is exactly what the((Circle)this).r
super does when used like this. You can use
this casting technique when you need to refer to a shadowed variable defined in a class
that is not the immediate superclass. For example, if C is a subclass of
B, which is a subclass of A, and class C shadows
a variable x that is also defined in classes A and B,
A(x) -+
|
+- B(x) -+
|
+- C(x)
then you can refer to these different variables from class C as follows:
But note this:x // Variable x in class C this.x // Variable x in class C super.x // Variable x in class B ((B)this).x // Variable x in class B ((A)this).x // Variable x in class A
You cannot refer to a shadowed variablesuper.super.x // Illegal; does not refer to x in class A
x in the superclass of a superclass
with super.super.x. Java does not recognize this syntax. Shadowed Methods?
Just as a variable defined in one class can shadow a variable with the same name in a superclass, you might expect that a method in one class could shadow a method with the same name (and same arguments) in a superclass. In a sense, they do: "shadowed" methods are called overridden methods. But method overriding is significantly different than variable shadowing; it is discussed below.
Overriding Methods
When a class defines a method using the same name, return type, and arguments as a method in its superclass, the method in the class overrides the method in 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 superclass's old definition.
Method overriding is an important and useful technique in object-oriented programming. Suppose we define
a class Ellipse of our Circle class. (This is admittedly a strange thing to do,
since, mathematically, a circle is a kind of ellipse, and it is customary to derive a more specific class
from a more general one. Nevertheless it is a useful example here). Then it would be important for
Ellipse to override the area() and circumference() methods of
Circle. Ellipse would have to implement new versions of these functions because
the formulas that apply to circles don't work for ellipses.
Before we go any further with the discussion of method overridding, be sure that you understand the difference between method overriding and method overloading, which we discussed earlier. As you probably recall, method overloading refers to the practice of defining multiple methods (in the same class) with the same name but with differing argument lists. This is very different from method overriding, and it is important not to get them confused!
Overriding Is Not Shadowing
Although Java treats the variables and methods of a class analogously in many ways, method overriding is not like variable shadowing at all: You can refer to shadowed variables simply by casting an object to the appropriate type. You cannot invoke overridden methods with this technique, however.
The next example illustrates this crucial difference:
class A {
int i = 1;
int f() { return i; }
}
class B extends A {
int i = 2; // Shadows variable i in class A.
int f() { return -i; } // Overrides method f in class A.
}
public class override_test {
public static void main(String[] args) {
B b = new B();
System.out.println(b.i); // Refers to B.i; prints 2.
System.out.println(b.f()); // Refers to B.f(); prints -2.
A a = (A) b; // Cast 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.
}
}
While this difference between method overriding and variable shadowing may seem surprising at first,
a little thought makes the purpose clear. Suppose we have a bunch of Circle and
Ellipse (a subclass of Circle here) objects that we are manipulating.
To keep track of the circles and ellipses, we store them in an array of type Circle[],
casting all the Ellipse objects to Circle objects before we store them.
Then, 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 very much about,
however, is that the correct value is computed when we invoke the area() method of any element
of the array. That is, we don't want to use the formula for the area of a circle when the object is actually
an ellipse. Seen in this context, it is not surprising at all that method overriding is handled differently by Java than variable shadowing.
final Methods
If a method is declared final, it means that the method declaration is the
"final" one -- that it cannot be overridden. static methods and private
methods cannot be overridden either, nor can the methods of a final class. (If a method
cannot be overridden, the compiler may perform certain optimizations on it, too).
Dynamic Method Lookup
If we have an array of Circle and Ellipse objects, how does the compiler know
to call the Circle area() method or the Ellipse area() method for any
given item in the array? The compiler does not know this; it can't. The compiler knows that it does not know,
however, and produces code that uses "dynamic method lookup" at run-time. When the interpreter runs the code,
it looks up the appropriate area() method to call for each of the objects. That is, when the
interpreter interprets the expression s.area(), it dynamically looks for an area()
method associated with the particular object
referred to by the variable s. It does not simply
use the area() method that is statically associated with the type of the
variable s.
So the actual type of the object is used not the type of the object reference.
class Point {
int x, y;
void clear() {
// code for clearing a Point object
}
}
// Pixel is a Point with a Color
class Pixel extends Point {
Color color;
void clear() {
// code for clearing a Pixel object
}
}
// ... somewhere in a method we have:
Point point = new Pixel();
point.clear(); // uses Pixel's clear()
Dynamic method lookup is fast, but it is not as fast as invoking a method directly. Fortunately, there are a
number of cases in which Java does not need to use dynamic method lookup. static methods cannot be
overridden, so they are always invoked directly. private methods are not inherited by subclasses
and so cannot be overridden by subclasses; this means the Java compiler can safely invoke them without dynamic
method lookup as well. final methods are invoked directly for the same reason: they cannot be
overridden. Finally, when a method of a final class is invoked through an instance of the class
or a subclass of it, then it, too, can be invoked without the overhead of dynamic lookup. Invoking an Overridden Method
We've seen the important differences between method overriding
and variable shadowing. Nevertheless, the Java syntax for invoking
an overridden method is very similar to the syntax for accessing
a shadowed variable: both use the super keyword. The
following example illustrates this:
class A {
int i = 1;
int f() { return i; } // A very simple method.
}
class B extends A {
int i; // This variable shadows i in A.
int f() { // This method overrides f() in A.
i = super.i + 1; // It retrieves A.i this way.
return super.f() + i; // And it invokes A.f() this way.
}
}
Recall that when you use super to refer to a shadowed variable, it is the same
as casting this to the superclass type and accessing the variable through that.
On the other hand, using super to invoke an overridden method is not the same
as casting this. In this case, super has the special purpose of
turning off dynamic method lookup and invoking the specific method that the superclass defines
or inherits. (One needs to be aware of the limitations of the analogy with casting this
in the case of referencing methods with super).
In the example above we use super to invoke an overridden method that is
actually defined in the immediate superclass. super also works perfectly well
to invoke overridden methods that are defined further up the class hierarchy. This is because
the overridden method is inherited by the immediate superclass, and so the super
syntax does in fact refer to the correct method.
To make this more concrete, suppose class A defines method f, and
that B is a subclass of A, and that C is a subclass of
B that overrides method f.
Then you can still use:A(f) --- B --- C(f)
to invoke the overridden nethod from within classsuper.f()
C. This is so because class B
inherits method f from class A. If classes A, B and
C all define method f, however, then calling super.f() in class
C invokes class B's definition of the method. In this case, there is no way to
invoke A.f() from within class C because super.super.f() is not legal
Java syntax.
It is important to note that super can only be used to invoke overridden methods
from within the class that does the overriding. With our Circle and Ellipse
classes, for example, there is no way to write a program (with or without super) that
invokes the Circle area(); method on an object of type Ellipse.
The only way to do this is to use super in a method within the Ellipse class.
Finally, note that this form of super does not have to occur in the first statement
in a method, as it does when used to invoke a superclass constructor method.
Data Hiding and Encapsulation
We started these review notes by describing a class as "a collection of data and methods". One of the important object-oriented techniques that we haven't discussed so far is hiding the data within the class, and making it available only through the methods. This technique is often known as encapsulation, because it seals the classes's data (and internal methods) safely inside the "capsule" of the class, where it can be accessed only by trusted users, i.e., by 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, then 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 variables that are interdependent and must be in a consistent state. If you allow a programmer (this may be you yourself) to manipulate those variables directly, (s)he may change one variable without changing important related variables, thus leaving the class in an inconsistent state. If, instead, (s)he had to call a method to change the variable, the method can be sure to do everything necessary to keep the state consistent.
Here's another way to think about encapsulation When all of a class's variables are hidden, the class's 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 variables can be directly manipulated, the number of possibilities you have to test becomes unmanageable.
There are other reasons to hide data, too:
In most of our examples so far, you've probably noticed the public keyword being used
When applied to a class, it means that the class is visible everywhere. When applied to a method or
a variable, it means that the method or variable is visible everywhere the class is.
To hide variables (or methods for that matter) you just have to declare them private:
public class Laundromat { // People can use this class.
private Laundry[] dirty; // They can't see this internal variable,
public void wash() { ... } // but they can use these public methods
public void dry() { ... } // to manipulate the internal variable.
}
A private field of a class is visible only in methods defined within that class.
(We do not discuss
inner classes in A201, at least not that much).
Similarly, a private method may only be invoked by methods within the
class. Private members are not visible within subclasses, and are not inherited by subclasses as other
members are. Of course, non-private methods that invoke private methods internally are
inherited and may be invoked by subclasses.
Besides public and private, Java has two other visibility levels: protected
and the default visibility level, "package visibility", which applies if none of the public,
private and protected keywords are used.
A protected member of a class is visible within the class where it is defined,
of course, and within all subclasses of the class, and also within all classes that are in
the same package as that class. You should use protected visibility when you
want to hide fields and methods from code that uses your class, but want those fields and
methods to be fully accessible to code that extends your class.
The default package visibility is more restrictive than protected, but less
restrictive than private. If a class member is not declared with any of the
public, private or protected keywords, then it is
visible only within the class that defines it and within classes that are part of the same
package. It is not visible to subclasses unless those subclasses are part of the same
package.
A note about packages: A package is a group of related and possibly cooperating classes.
All non-private members of all classes in the package are visible to all other
classes in the package. This is OK because the classes are assumed to know about, and trust
each other. The only time difficulty arises is when you write programs without a package
statement. These classes are thrown into a default package with other package-less classes,
and all their non-private members are visible throughout the package (The default package
usually consists of all classes in the current working directory).
(The rest of this Visibility Modifiers section for your reference only).
There is an important point to make about subclass access to protected members. A subclass
inherits the protected members of its superclass, but it can only access those members through
instances of itself, not directly in instances of the superclass. Suppose, for example, that A,
B, and C are public classes, each defined in a different package, and
that a, b, and c are instances of those classes. Let B
be a subclass of A, and C be a subclass of B. Now, if A
has a protected field x, then the class B inherits that field, and
its method can use this.x, b.x, and c.x. But it cannot access
a.x. Similarly, if A has a protected method f(), then
the methods of class B can invoke this.f(), b.f(), and
c.f(), but they cannot invoke a.f().
The following table shows the circumstances under which class members of the various visibility types are accessible to other classes:
| Accessible to | Member visibility | |||
|---|---|---|---|---|
| public | protected | package | private | |
| Same class | yes | yes | yes | yes |
| Class in same package | yes | no | yes | no |
| Subclass (perhaps in different package) | yes | yes | no | no |
| Non-subclass, different package | yes | no | no | no |
The details of member visibility in Java can become quite confusing. Here are some simple rules of thumb for using visibility modifiers:
public only for methods and constants that form part of the public API of the class.
Certain very important or very frequently used fields may also be public, but it is common practice
to make fields non-public and encapsulate them with public accessor methods.
protected for fields and methods that aren't necessary to use the class, but that may be
of interest to anyone creating a subclass as part of a different package.
private for fields and methods that are only used inside the class and should be
hidden everywhere else.
package statement
to group your related classes into packages. Data Access Methods
In the Circle example we've been using, we've declared the circle position
and radius to be public fields. In fact, the Circle class is one
where it may well make sense to keep those visible -- it is a simple enough class, with no
dependencies between the variables.
On the other hand, suppose we wanted to impose a maximum radius on objects of the Circle class.
Then it would be better to hide the r variable so that it could not be set directly. Instead of a
visible r variable, we'd implement a setRadius() method that verifies that the
specified radius isn't too large and then sets the r variable internally. The example that follows
shows how we might implement Circle with encapsulated data and a restriction on radius size. For
convenience, we use protected fields for the radius and position variables. This means that subclasses
of Circle, or cooperating classes within the shapes package are able to access these
variables directly. To any other classes, however, the variables are hidden. Also, note the private
constant and method used to check whether a specified radius is legal. And finally, notice the public
methods that allow you to set and query the values of the instance variables.
package shapes;
// Specify a package for the class.
public class Circle {
// Note that the class is still public!
protected double x, y;
// Position is hidden, but visible to subclasses
protected double r;
// Radius is hidden, but visible to subclasses
private static final double MAXR = 100.0;
// Maximum radius (constant).
private boolean check_radius(double r) {
return (r <= MAXR);
}
// Public constructors
public Circle (double x, double y, double r) {
this.x = x; this.y = y;
if (check_radius(r))
this.r = r;
else
this.r = MAXR;
}
public Circle (double r) { this (0.0, 0.0, r); }
public Circle () { this (0.0, 0.0, 1.0); }
// Public data access methods
public void moveTo(double x, double y) {
this.x = x; this.y = y;
}
public void move (double dx, double dy) {
x += dx; y += dy;
}
public void setRadius(double r) {
this.r = (check_radius(r))?r:MAXR; /* a-ha! */
}
// Declare these trivial methods final so we don't get dynamic
// method lookup and so that they can be optimized by the compiler.
public final double getX () { return x;}
public final double getY () { return y;}
public final double getRadius () { return r;}
}
And here's the last part of this overview.
Abstract Classes and Methods: A Brief Tutorial
An abstract method has no body, only a signature followed by a semicolon. For example:
abstract double area();
Any class with an abstract method is automatically
abstract itself, and must be declared as such. So we
need the blue keyword below, it just has to be there:
abstract class Shape {
abstract double area();
}
A class may be declared abstract even if it has no
abstract methods. This prevents it from being instantiated.
For example:
oldschool.cs.indiana.edu%ls -l
total 1
-rw------- 1 dgerman 134 Jul 16 12:28 Example.java
oldschool.cs.indiana.edu%cat Example.java
abstract class Shape {
double area() { return -1; }
public static void main(String[] args) {
Shape a = new Shape();
}
}
oldschool.cs.indiana.edu%javac Example.java
Example.java:4: class Shape is an abstract class. It can't be instantiated.
Shape a = new Shape();
^
1 error
oldschool.cs.indiana.edu%
An abstract class cannot be instantiated.
This could be seen in the example above.
It can however have a main method with no problem:
oldschool.cs.indiana.edu%ls -l
total 1
-rw------- 1 dgerman 172 Jul 16 12:33 Example.java
oldschool.cs.indiana.edu%cat Example.java
abstract class Shape {
double area() { return -1; }
public static void main(String[] args) {
System.out.println("Hello!");
// Shape a = new Shape();
}
}
oldschool.cs.indiana.edu%javac Example.java
oldschool.cs.indiana.edu%java Shape
Hello!
oldschool.cs.indiana.edu%
A subclass of an abstract class can be instantiated if it
overrides each of the abstract methods of its superclass and
provides an implementation (i.e., a method body) for all of them.
Here's an example that does that and notice the inherited variables too.
oldschool.cs.indiana.edu%ls -l
total 1
-rw------- 1 dgerman 511 Jul 16 12:40 Example.java
oldschool.cs.indiana.edu%cat Example.java
abstract class Shape {
int x, y;
abstract double area();
}
class Circle extends Shape {
int radius;
double area() { return 2 * Math.PI * radius * radius; }
double manhattanDistanceToOrigin() {
return Math.abs(x) + Math.abs(y);
}
}
class Tester {
public static void main(String[] args) {
Circle c = new Circle();
c.radius = 10;
c.x = -3; c.y = 5;
System.out.println("Area is " + c.area() +
" and the distance is " + c.manhattanDistanceToOrigin());
}
}
oldschool.cs.indiana.edu%javac Example.java
oldschool.cs.indiana.edu%java Tester
Area is 628.3185307179587 and the distance is 8.0
oldschool.cs.indiana.edu%
If a subclass of an abstract class does not implement all
of the abstract methods it inherits, that subclass is itself
abstract.
To see this in the code above remove the definition of area()
in class Circle and recompile:
oldschool.cs.indiana.edu%ls -l
total 1
-rw------- 1 dgerman 577 Jul 16 12:44 Example.java
oldschool.cs.indiana.edu%cat Example.java
abstract class Shape {
int x, y;
abstract double area();
}
class Circle extends Shape {
int radius;
/*** let's take area() out, see if it still compiles:
double area() { return 2 * Math.PI * radius * radius; }
****/
double manhattanDistanceToOrigin() {
return Math.abs(x) + Math.abs(y);
}
}
class Tester {
public static void main(String[] args) {
Circle c = new Circle();
c.radius = 10;
c.x = -3; c.y = 5;
System.out.println("Area is " + c.area() +
" and the distance is " + c.manhattanDistanceToOrigin());
}
}
oldschool.cs.indiana.edu%javac Example.java
Example.java:6: class Circle must be declared abstract. It does not define double area() from class Shape.
class Circle extends Shape {
^
Example.java:18: class Circle is an abstract class. It can't be instantiated.
Circle c = new Circle();
^
2 errors
oldschool.cs.indiana.edu%
It doesn't compile, and for two reasons, not just one.
But the two reasons are closely related.
I hope
you enjoyed this tutorial.
I strongly hope the information presented in it was quite manageable.
Please let me know if you have any questions.