Things already covered in Review 1:
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 a 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. 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:
Object Creationpublic 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; } }
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. 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:
The part that we have not seen before (that is, the constructor) is inpublic class Circle { public double x, y, r; // The center and the radius of the circle // The constructor method 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; } }
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:
Again, the new code is inpublic 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; } }
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:
Here thepublic 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); }
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:
The part that we have taken from the previous class is inpublic 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 */ } }
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
.
The part that we take frompublic 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); } }
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:
Every class you define has a superclass. If you do not specify the superclass with anpublic class GraphicCircle extends Circle { ... }
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.
The complete class hierarchy for the Java API is diagrammed here.Object -+- Circle --- GraphicCircle | +- Math | +- System | +- Component -+- Container --- Panel --- Applet | +- Button | +- List
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:
The constructor relies on the fact thatpublic 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; }
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:
Note that if the superclass,public GraphicCircle() { super(); }
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:
If a class does not declare any constructor, it is given aclass A { int i; public A() { // Implicit call to super(); here i = 3; } } class B extends A { // Default constructor: public B() { super(); } }
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
:
Now, with this resolution variable declared, when we use the variablepublic 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); } }
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
,
then you can refer to these different variables from classA(x) -+ | +- B(x) -+ | +- C(x)
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:
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 ofclass 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. } }
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.
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.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()
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:
Recall that when you useclass 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. } }
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. I mentioned yesterday in class that we need 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
:
Apublic 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. }
private
field of a class is visible only in methods defined within that class. (We do not discuss
inner classes in A202). 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;} }