Lecture 9: Classes and Objects (I)
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:
Objects are created using an expression containing theclass Point { public double x, y; }
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.
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. These method variables are the ones with which you have worked in A201.
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 delcared 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.
Theclass 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); } }
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:
You usually usepublic void clear() { this.x = 0; this.y = 0; }
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.
Thisclass 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; } }
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 introduction. 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 basics of a class are:
Body
that could be used to store
data about celestial bodies such as comets, asteroids, planets, and stars:
First we declare the name of the class.class Body { public long idNum; public String nameFor; public Body orbits; public static long nextID = 0; }
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 in this chapter.
Recall that a class's variables are called fields; the
Body
class's nameFor
and orbits
are examples. They are instance variables. y
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 (ahem!) 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 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 construction.
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:
The constructor forclass Body { public long idNum; public String name = "<unnamed>"; public Body orbits = null; private static long nextID = 0; Body() { idNum = nextID++; } }
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 idNum
s 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:
As shown here, one constructor can invoke another constructor from the same class using theBody(String bodyName, Body orbitsAround) { this(); name = bodyName; orbits = orbitsAround; }
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:
You could, if you wanted, provide a one-argument constructor for those cases where you're constructing aBody sun = new Body("Sol", null); Body earth = new Body("Earth", sun);
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
The default constructor isclass SimpleClass { /** Same as default constructor */ public SimpleClass () { } }
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:
This method usespublic String toString() { String desc = idNum + " (" + name + ")"; if (orbits != null) desc += " orbits " + orbits.toString(); return desc; }
+=
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:
theSystem.out.println("Body " + sun); System.out.println("Body " + earth);
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:
The following output illustrates that the value ofclass 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); } }
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
This produces the following output: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; } }
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 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:
Now programmers who want to use the body's identifier will invoke theclass 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; } // ... }
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:
is equivalent to the following:class Name { public String str; Name() { str = "<unnamed>"; } }
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:
Theclass SweedishChef { String chefsName; SweedishChef(String chefsName) { this.chefsName = chefsName; } }
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.
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. Ifpublic Body orbitsAround() { return orbits; } public void orbitsAround(Body around) { orbits = around; }
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.
y 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:
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 theclass Echo { public static void main(String[] args) { for (int i = 0; i < args.length; i++) System.out.print(args[i] + " "); System.out.println(); } }
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.