|
CSCI A348/548
|
Part 1: Classes and Objects
1. Introduction
Java programs are built from classes.
From a class definition you can create any number of
objectsthat are known as instances of that class.
(Think of a class as a factory with blueprints and instructions to build gadgets - objects are the gadgets the factory makes. Each class is a factory, and the factory can make 0, 1, or more gadgets.)
Here's a declaration of a simple class that might represent a point on a two-dimensional plane:
class Point {
public double x, y;
}
Objects are created using an expression containing the new
keyword. Creating an object from a class definition is also known as instantiation; thus, objects are often called instances.new Point();
All objects in Java are accessed via object references - any variable that appears to hold an object actually contains a reference to that object.
Object references arep = new Point();
null when they do not reference any
object. Objects in Java have a type; the type is the object's class.
A class can contain two kinds of members:
Fields are data belonging either to the class itself or to objects of the class. They make up the state of the object or class. Fields store results of computations performed by the methods.
Methods are collections of statements that operate on the fields to manipulate the state. Methods contain the executable code of a class. The ways in which methods are invoked, and the statements contained within these methods, is what ultimately directs program execution.
The Point class
x and y coordinates of a point
The fields in objects are known as instance variables, because there is a unique copy of the field in each object (instance) of the class.
EachPoint lowerLeft = new Point(); Point upperRight = new Point(); Point middlePoint = new Point(); lowerleft.x = 0.0; lowerleft.y = 0.0; upperRight.x = 1280.0; upperRight.y = 1024.0; middlePoint.x = 640.0; middlePoint.y = 512.0; double d = lowerLeft.distance(upperRight); // [1]
Point object is unique and has its own copy of the
x and y fields. Changing x in
lowerLeft, for example, does not affect the value of
x in the upperRight object. Per-object fields are usually what you need. You usually want a field in one object to be distinct from the similarly named field in every other object instantiated from that class.
Sometimes, though, you want fields that are shared among all objects of that class.
The shared variables are known as class variables - variables specific
to the class as opposed to objects of the class. In Java, you obtain
class-specific fields by declaring them static, and they
are therefore sometimes called static fields.
Keep in mind:
static fields == class variables == one per class (or factory)
A static field is there no matter how many objects are
created, even if none are created. It relates to the class not to the
instances (or objects) of the class.
In addition to these kinds of variables, methods can use (as local workspace, or scratch paper) method variables and parameters. They are gone when the method terminates and they are different from instance and class variables.
Members of a class can have various levels of visibility. The
public declaration of x and y
in the Point class means that any code with access to a
Point can read or modify those values. (Other levels of
visibility limit member access to code in the class itself, or to
other related classes).
Objects of the Point class as defined above are exposed to
manipulation by any code that has a reference to a Point object,
because its fields are declared public. (See the code snippet
above). The real benefits of object orientation, however, come from hiding
the implementation of a class behind operations performed on its internal
data. In Java, operations of a class are declared via its methods -
instructions that operate on an object's data to obtain results. Methods
access implementation details that are otherwise hidden from other objects.
Hiding data behind methods so that it is inaccessible to other objects is
the fundamental basis of data encapsulation.
Methods have zero or more parameters. A method can return a value, or it
can be declared void to indicate that it does not return any
value.
A method's statements appear in a block of code between curly braces
{ and } that follow the method's name and the
declaration of its signature.
The signature is the name of the method and the number, order and types of the method's parameters.
We enhance the Point class with a simple clear
method.
class Point {
double x, y;
public void clear() {
x = 0;
y = 0;
}
public double distance (Point theOtherPoint) {
double xDiff, yDiff;
xDiff = x - theOtherPoint.x;
yDiff = y - theOtherPoint.y;
return Math.sqrt(xDiff * xDiff + yDiff * yDiff);
}
}
The clear method has no parameters, hence the empty
( and ) after its name. In addition
clear is declared void because it does
not return any value.
Inside a method, fields and other methods of the class can be named
directly - we can simply say x and y without
an explicit object reference.
Objects in general do not operate directly on the data of other objects
although, as we saw in the Point class, a class can
make its fields publicly accessible. (In general, though, well-designed
classes hide their data so that it can be changed only by methods of that
class.)
To invoke a method, you provide an object reference and the method name, separated by a dot. Same rule applies for instance or class variables.
Parameters are passed to the method as a comma-separated list of values enclosed in parentheses. Methods that take no parameters still require the parentheses, with nothing between them.
The object on which the method is invoked (the object receiving the object invocation) is often known as the receiving object (or the receiver).
A method can return a single value as a result. To return more than one value from a method, create an object whose sole purpose is to hold return values in a single unit, and return that object.
We have also enhanced the Point class with a
distance method. The distance method
accepts another point object as a parameter, computes the
Euclidean distance between itself and the other point, and
returns a double precision floating-point result.
Based on our lowerLeft and upperRight
objects created earlier in this section one could invoke
distance as indicated in line [1] in
the first code snippet above. Also note that so far we have been
talking only about instance (or per object) methods.
Occasionally, the receiving object needs to know its own reference.
For example, the receiving object might want to add itself to a list of objects somewhere.
An implicit reference called this is available to (instance)
methods,
and this is a reference to the current (receiving) object.
The following definition of clear is equivalent to the one
just presented:
public void clear() {
this.x = 0;
this.y = 0;
}
You usually use this as a parameter to other methods that need
an object reference.
The this reference can also be used to explicitly
name the members of the current object. Here's another one of
Point's methods called move that sets
the x and y fields to specified values.
class Point {
double x, y;
public static Point origin = new Point(); // explain!
public void clear() {
this.x = 0;
this.y = 0;
}
public double distance (Point theOtherPoint) {
double xDiff, yDiff;
xDiff = x - theOtherPoint.x;
yDiff = y - theOtherPoint.y;
return Math.sqrt(xDiff * xDiff + yDiff * yDiff);
}
public void move(double x, double y) {
this.x = x;
this.y = y;
}
}
This move method uses this to clarify which
x and y are being referred to. Naming the
parameters of move "x" and "y" is reasonable,
because you pass x and y coordinates to the method.
But then those parameters have the same names as Point's
fields, and therefore the parameter names are said to hide the
field names.
(If we simply write
we would assign the value of thex = x
x parameter to itself, not to the
x field as required. The expression
refers to the object'sthis.x
x field, not the x
parameter of move). So we review and say once again that variables could belong to
Just as you can have per-class (static) fields, you can also have per-class (static) methods, often known as class methods.
Class methods are usually intended to do class-like operations
specific to the class itself, and usually on static fields, not
on specific instances of that class. Class methods are declared
using the static keyword, and are therefore also
known as static methods.
A static method cannot directly access non-static members.
When a static method is invoked, there's no specific object reference
for the method to operate on, (so using this in a static
method is completely inappropriate and causes an error).
An explicit object reference can be passed to the static method as a parameter if we want to work on (or with) a certain specific instance, but in general static methods do class kind of operations and non-static methods do object kind of things.
This is the end of the first part. Now let's go over the same things again and in a more systematic manner.
2. Reinforcing These Concepts
The fundamental unit of programming in Java is the class. Classes contain methods - collections of executable code that are the focus of computation. Classes also provide the structure for objects, plus the mechanism to manufacture objects from the class definitions (the so-called constructors).
You can compute with only primitive types - integer, floating-point, and so on - but almost any interesting Java program will create and manipulate objects.
Each object is an instance of a class.
A newly created object is given an initial state.
When a method is invoked on an object, the class is examined to find the code to be run.
The basic elements of a class are:
Body that could be used to store
data about celestial bodies such as comets, asteroids, planets, and stars:
class Body {
public long idNum;
public String nameFor;
public Body orbits;
public static long nextID = 0;
}
First we declare the name of the class. A class declaration creates a type name in Java, so that references to objects of that type can be declared with a simple
This declaration states thatBody mercury;
mercury is a reference to
an object of class Body.
The declaration does not create an object - it declares only
a reference to a Body object. The reference is initially
null, and the object referenced by mercury
does not actually exist until you create it explicitly. (In this respect
Java is different from languages where objects are created when you declare
variables - this just for the record.)
This first version of Body is poorly designed. This is
intentional: we will demonstrate the value of certain language features
as we improve the class below.
Recall that a class's variables are called fields; the
Body class's nameFor and orbits
are examples. They are instance variables.
Every Body object has its own specific instances of these
fields:
long that uniquely identifies the body from all others
String that is its name
Body around which it orbits
Changing the orbits field in one Body object does
not affect the orbits field in any other Body object.
Sometimes, though, you want only one instance of a field shared by all objects of a class.
You obtain such fields by declaring them static, so they are
called static fields or class variables.
When you declare a static field in a class, all objects created from that class share a single copy of that field.
In our case Body has one static field,
nextID, which contains the next body identifier to use.
The nextID field is initialized to zero when the class is
initialized after it is loaded and linked.
We will see below that each newly created Body object
will have the current value of nextID as its identifier.
All fields and methods of a class are always available to (the) code (that
is described) in the class itself. Members declared private
are accessible only in the class itself). Members declared
public are accessible anywhere the class is accessible,
and they are inherited by subclasses.
We declared the Body class's fields public
because programmers need access to them to do the work the class is
designed for. In a later version of the Body class, we
will see that such a design is not usually a good idea.
In this first version ofBody sun = new Body(); sun.idNum = Body.nextID++; sun.nameFor = "Sol"; sun.orbits = null; // in solar system sun is in the middle Body earth = new Body(); earth.idNum = Body.nextID++; earth.nameFor = "Earth"; earth.orbits(sun);
Body, objects that represent
particular celestial bodies are created and initialized.
First we declare two references (sun and earth)
to hold objects of type Body. As mentioned before, these
declarations do not create objects; they only declare variables
that reference objects. The references are initially null
and the objects they may reference must be created explicitly.
We create the sun using the new operator. When
you create an object with the new operator, you specify the
type of object you want to create and any parameters to its constructor.
The Java runtime system allocates enough space to store the fields of the object and initializes it in ways you will soon see. When initialization is complete, the runtime system returns a reference to the new object.
Having created a new Body object, we initialize its variables.
Each Body object needs a unique identifier, which it gets from
the static nextID field of Body. The code must
increment nextID so that the next Body object
created will get a unique identifier.
This example builds a solar system model. In this model, the Sun is
in the center, and sun's orbits field is
null because it doesn't orbit anything. When we create
and initialize earth, we set its orbits
field to sun. A Moon object that orbited the Earth
would have its orbits field set to earth.
Constructors
A newly created object is given an initial state. Fields can be initialized
with a value when they are declared, which is sometimes sufficient to ensure a
correct initial state (the rule is that if no value is assigned to a field it
will be a zero, \u0000, false or null,
depending on its type). But often you need more than simple data initialization
to create the initial state; the creating code may need to supply initial data,
or perform operations that cannot be expressed as simple assignment.
For purposes other than simple initialization, classes can have
constructors. Constructors have the same name as the class
they initialize. Like methods they take zero or more parameters, but
constructors are not methods and thus have no return type. Parameters,
if any, are provided between the parentheses that follow the type name
when the object is created with new. Constructors are
invoked after the instance variables of a newly created object of the
class have been assigned their default initial values, and after their
explicit initializers are executed.
This improved version of the Body class uses constructors
to set up the initial state, partly by initialization and partly by the
constructor:
class Body {
public long idNum;
public String name = "<unnamed>";
public Body orbits = null;
private static long nextID = 0;
Body() {
idNum = nextID++;
}
}
The constructor for Body takes no arguments, but it performs an important function, namely,
assigning a proper idNum to the newly created object. In the original code, a simple programmer
error -- forgetting to assign the idNum, or not incrementing nextID after use -- could
result in different Body objects with the same idNum, creating bugs in code relying
on the part of the contract that says "all idNum values are different".
By moving responsibility for idNum generation inside the Body class, we have prevented
errors of this kind. The Body constructor is now the only entity that assigns idNum, and
is therefore the only entity needing access to nextID. We can and should make nextID
private, so that only the Body class can access it. By doing so, we remove a source of error for
programmers using the Body class.
We also are now free to change the way idNums are assigned to Body objects.
A
future implementation of this class might, for example, look up the name in a database of known astronomical
entities and assign a new idNum only if an idNum had not already been assigned. This
change would not affect any existing code, because that existing code wouldn't have been involved at all in the
mechanism for idNum allocation.
The data initializations for name and orbits set them to reasonable values. Therefore,
when the constructor returns from the invocation shown below, all data fields in the new Body object
have been set to some reasonable initial state. You can then set state in the object to the values you want:
TheBody sun = new Body(); // idNum is 0 sun.name = "Sol"; Body earth = new Body(); // idNum is 1 earth.name = "Earth"; earth.orbits = sun;
Body constructor is invoked while the new operator creates the object, but
after name and orbits have been set to their initial values. Initializing
orbits to null means that sun.orbits doesn't need to be set in our code. The case shown here, where you create a body knowing its name and what it orbits, is likely to be fairly common. You can provide another constructor that takes both the name and the orbited body:
Body(String bodyName, Body orbitsAround) {
this();
name = bodyName;
orbits = orbitsAround;
}
As shown here, one constructor can invoke another constructor from the same class using the
this() invocation as its first executable statement. This is called an explicit
constructor invocation. If the constructor you want to invoke has parameters, they can be passed
to the constructor invocation. Here we use it to call the constructor that has no arguments in order
to set up the idNum. Now the allocation code is much simpler:
Body sun = new Body("Sol", null);
Body earth = new Body("Earth", sun);
You could, if you wanted, provide a one-argument constructor for those cases where you're
constructing a Body object that doesn't orbit anything, rather than invoking
the two-argument Body constructor with a second argument of null.
Some classes always require that the creator supply certain kinds of data. For example, your
application might require that all Body objects have a name. To ensure that all
statements creating Body objects supply a name, you would define all Body
constructors with a name parameter.
Here are some common reasons for providing specialized constructors:
Body is an example)
public restricts who can create objects using it. (You could,
for example, prevent programmers using your package from extending a class by making all its constructors
accessible only inside the package. You can also mark as protected constructors that make sense
only for subclasses. You may also ignore this comment for now.)
If you don't provide any constructors of any kind in a class, the language provides a default no-arg constructor that does nothing. This constructor is provided automatically only if no other constructors exist because there are classes for which a no-arg constructor would be incorrect!
If you want both a no-arg constructor and one or more constructors with arguments, you can explicitly provide a no-arg constructor. The automatically provided no-arg constructor for a class that has no superclass is equivalent to the following
class SimpleClass {
/** Same as default constructor */
public SimpleClass () { }
}
The default constructor is public if the class is, and not if the class isn't.
Methods
A class's methods typically contain the code that understands and manipulates an
object's state. Some classes have public fields for programmers to manipulate
directly, but in most cases this isn't a very good idea. Many objects have tasks that cannot be represented
as a simple value to be read or modified, but require computation.
Methods are invoked as operations on objects via references using the . (dot) operator:
Each method takes a specific number of parameters. Java does not include methods that can accept a variable number of parameters. Each parameter has a specified type, either a primitive type or a reference type. Methods also have a return type, which is declared before the method name. For example here is a method of theobjectReference.methodName(parameters)
Body class to create a String that
describes a particular Body object:
public String toString() {
String desc = idNum + " (" + name + ")";
if (orbits != null)
desc += " orbits " + orbits.toString();
return desc;
}
This method uses += and -= to concatenate String
objects. It first builds a string that describes the identifier and name. If the body orbits
another body, we append the string that describes that body by invoking its
toString method. This recursion builds a string of bodies orbiting other bodies
until the chain ends with some object that doesn't orbit anything.
The toString method is special. If an object has a method named toString
that takes no parameters and returns a String, it is invoked to get a String
when that object is used in a string concatenation using the + operator. In these
expressions:
System.out.println("Body " + sun);
System.out.println("Body " + earth);
the toString methods of sun and earth are invoked implicitly,
and produce the following output:
Methods can return more than one result in several ways: return references to objects that store results as fields, take one or more parameters that reference objects in which to store the results, or return an array containg the results.Body 0 (Sol) Body 1 (Earth) orbits 0 (Sol)
If a method does not return any value, the place where a return type would go is filled
with a void. In methods that return a value, every path through the method
must return a value assignable to a variable of the declared return type.
Parameter Values to Methods
All parameters to methods Java are "call by value." That is, values of parameter
variables in a method are copies of the values the invoker specified. If you pass
a boolean to a method, its parameter is a copy of whatever value
was being passed, and the method can change it without affecting values in the
code that invoked the method. For example:
class PassByValue {
public static void main(String[] args) {
double one = 1.0;
System.out.println("before: one = " + one);
halveIt(one);
System.out.println("after: one = " + one);
}
public static void halveIt (double arg) {
arg /= 2.0; // divide the arg(ument) by two
System.out.println("halved: arg = " + arg);
}
}
The following output illustrates that the value of arg
inside halveIt is divided by two without affecting the
value of the variable one in main:
When the parameter is an object reference, however, the object reference is what is passed "by value," not the object itself. Thus, you can change which object a parameter refers to inside the method without affecting the reference that was passed. But if you change any fields of the objects, or invoke methods that change the object's state, the object is changed for every part of the program that holds a reference to it. Here's an example to show this distinction:before: one = 1 halved: arg = 0.5 after: one = 1
class PassRef {
public static void main(String[] args) {
Body sirius = new Body("Sirius", null);
System.out.println("before: " + sirius);
commonName(sirius);
System.out.println("after: " + sirius);
}
public static void commonName (Body bodyRef) {
bodyRef.name = "Dog Star";
bodyRef = null;
}
}
This produces the following output:
Notice that the contents of the object have been modified with a name change, while the referencebefore: 0 (Sirius) after: 0 (Dog Star)
bodyRef still refers to the Body object,
even though commonName changed the value of its bodyRef
parameter to null.
One could draw a picture to show the state of the references just after main
invokes commonName. At that point, the two references sirius
(in main) and bodyRef (in commonName) both refer to the
same underlying object. When commonName changes the field bodyRef.name,
the name is changed in the underlying object that the two references share. When commonName
changes the value of bodyRef to null, only the value of the bodyRef
reference is changed, while the value of sirius remains unchanged, since the parameter
bodyRef is a pass-by-value copy of sirius. Inside the method
commonName, all you are changing is the value in the parameter variable bodyRef,
just as all you changed in halveIt was the value in the parameter variable arg.
If changing bodyRef affected the value of sirius in main, the
"after" line would say "null". However, the variables bodyRef in
commonName and sirius in main both refer to the same
underlying object, so the change made inside commonName is reflected
in the object that sirius refers to.
Using Methods to Control Access
The Body class with its various constructors is considerably easier to
use than its simple data-only form, and we have ensured that the idNum is
set both automatically and correctly. But a programmer could still mess up the object by
setting its idNum field after construction, because the idNum field
is public and therefore exposed to change. The idNum should be
read-only data. Read-only in objects is common, but there is no keyword to apply to a
field that allows read-only access outside the class.
To enforce read-only access, you must hide the field. You do this by making the
idNum field private and providing a new method so that code
outside the class can read its value using that method:
class Body {
private long idNum; // now "private"
public String name = "<unnamed>";
public Body orbits = null;
private static long nextID = 0;
Body() {
idNume = nextID++;
}
public long id() {
return idNum;
}
// ...
}
Now programmers who want to use the body's identifier will invoke the id method, which
returns the value. There is no longer any way for the programmers to modify the identifier -- it has
effectively become a read-only value outside the class. It can be modified only by the internal methods
of the Body class.
Methods that regulate access to internal data are sometimes called accessor methods. You
could also use accessor methods to protect the name and orbits fields, and
you probably should.
Even if an application doesn't require fields to be read-only, making fields private and adding methods to set and fetch them enables you to add actions that may be needed in the future. If programmers can access a class's fields directly, you have no control over what values they will use or what happens when values are changed.
The this (Host) Reference
We have already seen how you can use an explicit constructor invocation
to invoke another one of your class's constructors at the beginning of a
constructor. You can also use the special object reference this
inside a non-static method, where it refers to the current object on which the
method was invoked. The this reference is most commonly used as a way to pass
a reference to the current object as a parameter to other methods. Suppose a method requires
adding the object to a list of objects awaiting some service. It might look something like this:
Let's look at another example. The assignment toService.add(this);
str in this class:
class Name {
public String str;
Name() {
str = "<unnamed>";
}
}
is equivalent to the following:
Conventionally, you usethis.str = "<unnamed>"
this only when needed which is when the name of the field
you need to access is hidden by a variable or parameter declaration. For example:
class SweedishChef {
String chefsName;
SweedishChef(String chefsName) {
this.chefsName = chefsName;
}
}
The chefsName field is hidden inside the constructor by the parameter of the same
name. To ensure we access the chefsName field instead the chefsName parameter,
we prefix it with this to specify that the field is the one belonging to this object,
the one that is currently being created. Deliberately hiding identifiers in this manner is considered good programming practice only in this idiomatic use in constructors and accessor methods.
Overloading Methods
In Java, each method has a signature, which is its name together with the number and types
of its parameters. Two methods can have the same name if their signatures have different numbers or
types of parameters. This feature is called overloading, because the simple name of the
method has overloaded (more than one) meaning. When a programmer invokes a method, the compiler
compares the number and type of parameters to find the method that best matches the available
signatures. Here are some orbitsAround methods for our Body class.
public Body orbitsAround() {
return orbits;
}
public void orbitsAround(Body around) {
orbits = around;
}
These methods are written using a programming style that uses overloading to differentiate between fetching
a value (no parameters) and setting the value (a parameter with the new value). The number of parameters is
different so the overload resolution is simple. If orbitsAround is invoked with no parameters,
the method that returns the current value is used. If orbitsAround is invoked with one argument
that is a Body, the method that sets the value is used. If the invokation matches neither of
these signatures, it is invalid, and the code will not compile.
Static Members
A class has two kinds of members: fields and methods. Each member specifies how it may be accessed
and how it may be inherited. Each member can also be made static if so desired.
A static member is a member that is only one per class, rather than one in every object created
from that class. For static fields (class variables), there is exactly one variable, no matter how many
objects (even zero) there are of the class. The nextID field in our Body class
is an example.
The static fields of a class are initialized before any static field
in that class is used or any method of that class is run.
A class can also have static initialization blocks to set up static fields or other necessary states. A static initializer is most useful when simple initialization clauses on the field declaration aren't up to the task of initialization. For example creating a static array and initializing its members often must be done with executable statements.
The order of static initialization within a class is left-to-right and top-to-bottom.
A static method is invoked on behalf of an entire class, not on a specific object instantiated from that class. Such methods are also known as class methods. A static method might perform a general task for all objects of the class -- such as returning the next available serial number or something of that nature.
A static method can access only static variables and static methods of the class. There is
no this reference, because there is no specific object being operated upon.
Outside of a class, a static member is usually accessed by using the class name, rather than through an object reference.
The main Method of a Class
Details of invoking a Java application vary from system to system, but whatever
the details, you must always provide the name of a Java class that drives the
application. When you run a Java program, the system locates and runs the main
method for that class. The main method must be public, static,
and void (it returns nothing), and it must accept a single argument of type
String[] args. Here's an example that prints its parameters:
class Echo {
public static void main(String[] args) {
for (int i = 0; i < args.length; i++)
System.out.print(args[i] + " ");
System.out.println();
}
}
The arguments in the string array are the "program arguments." These are usually typed
by users when they run the program. For example, on a command-line system such as UNIX
or a DOS shell, you might invoke the Echo application like this:
In this command,java Echo in here
java is the Java bytecode interpreter, Echo
is the name of the class, and the rest of the parameters are the program arguments. The
java command finds the compiled bytecodes for the class Echo,
loads them into a runtime in a virtual machine, and invokes Echo.main with
the program arguments contained in strings in the String array. The result
is the following output:
The name of the class is not included in the strings passed toin here
main.
You already know the name because it is the name of the enclosing class.
An application can have any number of main methods, since each class can
have one. Only one main is used for any given program. The main
that's actually used is specified when the program is run, as Echo was above.
Being able to have multiple main methods has one salutary effect -- each class
can have a main tests its own code, providing an excellent hook for unit-testing
a class.
And that's where these notes will start picking some momentum.
Part 2: Extending Classes
Things already covered in Part 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:
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. 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
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. 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:
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 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;}
}
And here's the last part of this overview.
Abstract Classes and Methods: A Brief Tutorial
abstract method has no body, only a signature followed
by a semicolon. For example:
abstract double area();
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();
}
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%
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%
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%
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.
A348/A548