C SC 160 Chapter 4: Defining Your Own Classes (basics)
major resource: An Introduction to Object-Oriented Programming with Java, fourth
edition,
Wu, McGraw Hill, 2006
[ previous
| schedule
| next ]
Note: most of these topics are also covered in Dr. Pete's No-Nonsense Guide: Writing Java Classes
General Structure of a Class
The ordering of elements within a class definition is flexible, but the textbook author (and I)
tend to follow these conventions
- If import statements were needed, they must be above the
class declaration.
- Class name defined on first line, and its bracket pair { } groups
all elements of class
- Data members (class and instance variables and constants) are declared next.
- Any constructors are defined next.
- The methods are defined next. They can be listed in any order. I generally
define all public methods before any private methods.
- First line of each method is its signature (info needed by clients
to invoke it).
- Example method signature: public String dollarToString(double dollar)
- public -- the visibility modifier, any client
can invoke this method
- String -- the type of value this method will return
when client invokes it
- dollarToString -- the method name
- ( -- marks beginning of parameter list, required even
if no parameters
- double dollar -- declaration of parameter, which will
receive argument value when client invokes
- ) -- marks end of parameter list, required even if no
parameters
A class to inspect: the ClickCounter from our Preliminary Lab
class ClickCounter {
public static final int MINIMUM = 0;
private int maximum;
private int count;
public ClickCounter(int max) {
maximum = max;
count = MINIMUM;
}
public boolean isAtMinimum() {
return count == MINIMUM;
}
public boolean isAtMaximum() {
return count == maximum;
}
public int inc() {
if (! this.isAtMaximum()) {
count++;
}
return count;
}
public void reset() {
count = MINIMUM;
}
public int getCount() {
return count;
}
public String toString() {
return new Integer(count).toString();
}
}
Visibility of class members
Class members declared public or private to control visibility.
- public means accessible to all clients outside the class declaration
- private means not accessible outside the class declaration
- between these extremes are two other degrees of visibility, to be studied later
In general, we don't want outsiders messin' with our instance variables directly. We will gladly
provide them methods to use however. Thus the general guidelines:
- instance and class variables should be declared private
- class constants provided for the convenience of our clients (e.g. Math.PI) are declared
public
- methods provided for the convenience of our clients are declared
public
- methods intended for internal use only are declared private
The principles behind these guidelines are information hiding and encapsulation.
Information hiding principle: Hide the How! Implementation details, the "how" of a class, should be secret.
Advertise the What! Specification details, the "what" of a class, should be published.
The "what" means what behaviors the
class offers its clients, the "how" means how the behaviors are implemented.
Encapsulation principle: the state of an object should be controlled only by its
own methods.
Considerations for writing methods: Constructors
A constructor is a special method which creates objects. Here are some of its characteristics:
- Its name is same as class name
- There is no return type or return value
- Normally public. private is possible but not accessible to client
- Its purpose is to initialize instance variables for the new object (so it will start life in a consistent state)
- A class can have several constructors but each must have distinguishable parameter list. More on this in a later chapter.
- If, and only if, no constructors are defined, a default constructor with no parameters is provided
Considerations for writing methods : accessor and mutator methods
If you follow visibility guidelines, clients cannot directly access instance variables.
- It is common practice to provide methods through which the client can indirectly
get or set the value of an instance variable.
- The "getter" methods are also known as accessor or query methods.
- The "setter" methods are also known as mutator methods, because they modify the object.
- There need not be a one-to-one correspondence between getters/setters and instance variables.
Example: A Parcel class to represent shipping items will have a "weight" attribute.
Weight getters and setters are provided for both pounds and kilograms, but weight is represented internally
by just one instance variable.
- Example: java.util.Calendar has the general get() method for accessing
various properties such as month or year, and the general set() method for modifying them.
It also has specific methods like getTime() to get a Date object representing
the current calendar date and setTime() to modify the current calendar date using an
existing Date object.
Considerations for writing methods: parameters
Parameters are the means by which client data are given to a method at the
moment of invocation. For each parameter defined in a method, the caller (invoker)
must provide a corresponding argument.
There are five things you must understand about parameters and arguments
First, you must understand the difference between a parameter
and an argument.
- a parameter is a declaration listed in the method signature and documentation
- an argument is the actual value passed from client to method at runtime
(arguments are sometimes called "actual parameters" for this reason). The argument is never a
declaration!!
Example: A currency converter. The parameter and arguments are highlighted
in bold
class CurrencyConverter {
private double exchangeRate;
public void setExchangeRate(double rate) { //<--- "double rate" is parameter
exchangeRate = rate;
}
/******* other methods omitted *******/
}
public class ConverterClient {
public static void main(String[] args) {
double mult = 3.0;
CurrencyConverter convert = new CurrencyConverter();
convert.setExchangeRate(2.5); //<--- "2.5" is argument
convert.setExchangeRate(mult); //<--- "mult" is argument
convert.setExchangeRate(mult*2+5); //<---"mult*2+5" is argument
}
}
Second, you must understand that the argument is an expression evaluated at the time of invocation
and the expression value is passed to the method.
- the argument can be a constant, a variable, or more complicated expression
(the example above shows all three)
- the argument has a type, and its type must match or be compatible with the parameter's type
- for type-compatibility, it works like an assignment of the form: parameter
= argument;
- only the expression value is passed to the method; this is called pass-by-value
- Java allows only pass-by-value. Some languages support other techniques.
Third, you must understand that the parameter name is local to its method, and has no relationship
to the argument name (if the argument is a name)
- the argument name can be the same as the parameter name
- the argument name can be different from the parameter name
- it doesn't matter; they're independent of each other!!!
Fourth, you must understand that for parameters of primitive type (int, double,
etc.) the mapping from argument to parameter is one-way and the argument cannot be modified by the method
- if the argument is a variable name, its value cannot be changed as a consequence of being passed to the
method
- otherwise it would be like the assignment a = b; changing the
value of b!! Can't happen.
- this is the essence of pass-by-value for primitives
- this is obvious when the argument is an expression, like convert.setExchangeRate(2.5);
Fifth, you must understand that for parameters of reference
type (String, GregorianCalendar, etc.) the mapping from argument
to parameter is also one-way, with a twist. The argument cannot be modified
by the method, but the object the argument refers to, can be modified
by the method, if the object is mutable!
- the argument can only be a variable name or an anonymous object reference
- the argument's value is the reference to the object
- this means the parameter gets a reference to the same object that the argument refers to
- this is the essence of pass-by-value for reference types
- if the argument is a variable name and the method makes changes to the object, those changes "stick" after the return!
- Note: an array in Java is an object, even if its elements are primitives, so this applies to all arrays.
Example: focus your interest on the bolded code below
public class Person {
private String name;
public Person() {
name = "Pat";
}
public String getName() {
return name;
}
public void setName(String newName) {
name = newName;
}
/********* other class members omitted *********/
}
public class PersonClient {
public void process(Person who) {
String name = who.getName();
name = name.replace('a', 'i');
who.setName(name);
}
public static void main(String[] args) {
PersonClient pc = new PersonClient();
Person p = new Person();
p.setName("Sally");
System.out.println(p.getName());
pc.process(p);
System.out.println(p.getName());
}
}
What is the output produced by main?
Considerations for writing methods : return values
If your method is to respond with a resulting value when invoked, e.g. 2-way communication,
then it must follow the rules for return values. Examples are accessor
methods and the two examples just above.
- determine what type of value is to be returned; it can be primitive or reference
type
- a method can return only one value (which can be an object reference)
- specify return type in the method signature, just before the method name
- method body must have at least one return statement
- method completes execution when return statement reached
- there must be no possible execution path that reaches end of method
- return statement must have form: return expression ;
- expression is a value, variable or expression of the return type
Example: See the ageAtEndOfCurrentMonth() method just above. It returns an int value.
Example: In the same Person class, you could have an accessor method
that simply returns the person's date of birth, as a GregorianCalendar reference.
public class Person {
private GregorianCalendar dateOfBirth;
public GregorianCalendar getDateOfBirth() {
return dateOfBirth;
}
/******* other class members omitted *******/
}
It is risky for an accessor method to return an object reference, because the
client now has a reference to the person's birth date object and can change it!
It would be safer to create a new object for the same date and return a reference
to it instead. It would look something like this:
public class Person {
private GregorianCalendar dateOfBirth;
public GregorianCalendar getDateOfBirth() {
return new GregorianCalendar(dateOfBirth.get(Calendar.YEAR),
dateOfBirth.get(Calendar.MONTH),
dateOfBirth.get(Calendar.DATE) );
}
/******* other class members omitted *******/
}
Let's consider the other side, the responsibilities of the invoker (caller):
- If the method is void, no return value, the method invocation stands alone as a Java statement
- If the method has a return value,
- invoker (caller) is responsible for capturing the return value and doing something with it
- the invocation is one component of an expression, e.g. part of a numerical formula
or the right side of an assignment
- when invocation is complete, the invocation is replaced by the returned value
- In general the invoker can do one of three things with the return value:
- ignore (and lose) it -- usually undesirable. Occurs if invocation is stand-alone statement
- assign it into a variable of the same type -- this keeps it for potentially unlimited future use
- use it as (part of) an expression or argument -- this is an immediate one-time-only use
Considerations for writing methods : local variables
Methods may use instance variables but this is not always enough. If a method
requires additional variables for a calculation or computation, they should be
declared inside the method itself. Such variables are called local variables.
- a local variable can be accessed only inside the method that declares it
- its visibility is fixed, so declaring it public or private causes a compiler error
- a local variable exists only during the execution of its method
- in general, if a variable will be used only inside one method then make it a local variable
- in general, if a variable will be used by more than one method and seems to describe
the object then make it an instance variable
- beware: if you declare a local variable having the same name as an
instance variable, then to access the instance variable inside that method you have to precede
its name with "this.". Many programmers prefer to do this anyway, for clarity.
Example:
import java.awt.Color;
public class Square {
private double height;
private Color color;
public double howMuchRed() {
double area;
area = height * height;
return area * color.getRed();
}
/********* remainder of class omitted *********/
}
In this example, the local variable was not really needed since the method body could be written as:
return height * height * color.getRed(); For many well-written methods, local variables are
not needed.
Here's an example where the need is more obvious:
Example:
import java.util.*;
public class Person {
private GregorianCalendar dateOfBirth;
public int ageAtEndOfCurrentMonth() {
int yearsOld;
GregorianCalendar today;
today = new GregorianCalendar();
yearsOld = today.get(Calendar.YEAR) - dateOfBirth.get(Calendar.YEAR);
if ( today.get(Calendar.MONTH) < dateOfBirth.get(Calendar.MONTH) ) {
yearsOld--;
}
return yearsOld;
}
/******* other class members omitted *******/
}
This method uses two local variables, the primitive variable yearsOld
and the reference variable today. It also uses the instance variable
dateOfBirth, whose value has been previously set, most likely in a constructor
not shown.
[ C
SC 160 | Peter
Sanderson | Math Sciences server
| Math Sciences home page
| Otterbein ]
Last updated:
Peter Sanderson (PSanderson@otterbein.edu)