Asking an object to do something
By now you can create objects (Topic 1.13). Objects exist so that you can use them, and you use an object by calling its instance methods. These are the behaviors of Topic 1.7: what an object can do, or what can be done with it.
Instance methods are called on objects of the class. The dot operator is used along with the object name to call instance methods. The form is
objectName.methodName(arguments)
Compare this with Topic 1.10. A class method is called with the class name and needs no object (Math.sqrt(16)). An instance method is called with the name of an object (small.area()) because it needs a particular object to work on.
Here is a class Rectangle. As in Topic 1.13, you can focus on how it is used: a constructor creates a rectangle, and four methods report on it or change it. (You will write classes of your own in Unit 3.)
class Rectangle {
private int width;
private int height;
public Rectangle(int w, int h) {
width = w;
height = h;
}
public int area() {
return width * height;
}
public int perimeter() {
return 2 * (width + height);
}
public void grow(int amount) {
width = width + amount;
height = height + amount;
}
public String describe() {
return width + " x " + height;
}
}
The next program creates two rectangles and calls the same method on each.
public class Instances {
public static void main(String[] args) {
Rectangle small = new Rectangle(2, 3);
Rectangle big = new Rectangle(10, 10);
System.out.println(small.area());
System.out.println(big.area());
System.out.println(small.area() + big.area());
}
}
6
100
106
The method area is the same code in both cases, but small.area() multiplies small’s own width and height (2 and 3), while big.area() uses big’s (10 and 10). The data belong to the object; the behavior belongs to the class.
Void and non-void instance methods
Everything from Topic 1.9 still applies.
- A non-void method such as
area()returns a value, so a call is an expression: store it, print it, or use it in a larger expression such assmall.area() + big.area(). - A void method such as
grow(int amount)has no return value, so it is called as a statement. It works by changing the object: aftersmall.grow(1);the object’s width and height are one greater, and every later call onsmallsees the new values. - The arguments must match the parameters in number, order and type, and are passed by value.
A call needs an object, so the class name alone is not enough
Because an instance method works with a particular object’s data, you cannot call it through the class name. Given the class above, these statements in another class fail to compile:
public class Wrong {
public static void main(String[] args) {
Rectangle r = new Rectangle(1, 1);
int a = Rectangle.area();
int b = area(r);
int c = r.grow(2);
r.grow();
r.grow("2");
int d = r.volume();
}
}
| Statement | Fault |
|---|---|
Rectangle.area() |
area is an instance method: it needs an object, so it cannot be called through the class name |
area(r) |
there is no such method in this class; the object name and dot operator are missing |
int c = r.grow(2); |
grow is void, so there is no value to store |
r.grow(); |
grow needs one int argument |
r.grow("2"); |
a String cannot be passed to an int parameter |
int d = r.volume(); |
Rectangle has no method volume |
Calling a method on null
A reference variable can hold null, meaning it refers to no object (Topic 1.4 and Topic 1.13). An instance method needs an object to work on, so a method call on a null reference results in a NullPointerException. This is a run-time error, an exception like the ArithmeticException of Topic 1.1: the code compiles, because the compiler does not know which object a variable will refer to, but the program stops when the call is executed.
public class NullDemo {
public static void main(String[] args) {
Rectangle t = null;
System.out.println("before");
System.out.println(t.area());
System.out.println("after");
}
}
The output is before, then the program stops with a NullPointerException at the call, and after is never printed. The cure is to make sure the variable refers to an object before using it: either create one (Rectangle t = new Rectangle(2, 3);) or, in Unit 2, test for null first.
Why you use methods, not attributes
You may have noticed that the Rectangle attributes are marked private, and that every example reaches the data only through methods such as area() and describe(). This is deliberate. The class keeps its data inside, and offers a menu of behaviors outside. The code that uses the class cannot change a width to a nonsensical value, because it can only ask the object to do something through its methods. Writing small.width from another class would be rejected by the compiler. The idea is called encapsulation, and you will use it again when you write classes in Unit 3.
For tracing, this has a practical consequence: the only way an object’s data can change is through a method call on it (or a constructor). When you follow a long program, scan for lines of the form name.method(...). Those lines are exactly the places where an object might change.
Calls inside calls
Because a non-void call is an expression of its return type, you can use one call as an argument of another, or use its result as the target of the next call. Work from the inside out, left to right.
- As an argument:
new Rectangle(small.area(), 2)first callssmall.area()(6), then creates a 6 by 2 rectangle. - On a returned object: a method that returns a
Stringlets you call aStringmethod on the result.word.substring(1).length()first creates the substring and then asks for its length. This pattern is common with strings (Topic 1.15). - In a condition or calculation: later units will use calls such as
list.size()orword.length()inside loops andifstatements. They work in exactly the same way.
A tracing routine
- Make a column per object, not per variable, if two variables can be aliases. Label which variables refer to it.
- For each call, find the object named before the dot. If that variable is
null, the program stops with aNullPointerException. - Check the arguments against the method’s parameters, then run the method on that object’s data.
- Update the object’s column if the method is void and changes it. Write down the returned value if the method is non-void.
- Continue with the next statement, using the updated values.
Worked example 1: Trace calls on two objects
Task. State the output.
Rectangle p = new Rectangle(2, 3);
Rectangle q = new Rectangle(4, 4);
p.grow(1);
q.grow(2);
int total = p.area() + q.perimeter();
System.out.println(p.describe() + " " + q.describe() + " " + total);
Step 1, create. p is 2 by 3 and q is 4 by 4. They are separate objects.
Step 2, void calls. p.grow(1); adds 1 to both sides of p: 3 by 4. q.grow(2); adds 2 to both sides of q: 6 by 6. Each call changes only the object it was called on.
Step 3, the expression. p.area() is 3 * 4 = 12. q.perimeter() is 2 * (6 + 6) = 24. The sum is 36, stored in total.
Step 4, print. p.describe() returns "3 x 4" and q.describe() returns "6 x 6".
| After statement | p |
q |
total |
|---|---|---|---|
Rectangle p = new Rectangle(2, 3); |
2 x 3 | - | - |
Rectangle q = new Rectangle(4, 4); |
2 x 3 | 4 x 4 | - |
p.grow(1); |
3 x 4 | 4 x 4 | - |
q.grow(2); |
3 x 4 | 6 x 6 | - |
int total = p.area() + q.perimeter(); |
3 x 4 | 6 x 6 | 36 |
The output is 3 x 4 6 x 6 36.
Worked example 2: Two names, then null
Task. State what is printed and where the program stops.
Rectangle r = new Rectangle(1, 1);
Rectangle s = r;
s.grow(4);
System.out.println(r.area());
Rectangle t = null;
System.out.println(t.area());
System.out.println("done");
Step 1. r refers to a 1 by 1 rectangle. Rectangle s = r; copies the reference, so s refers to the same object (Topic 1.12).
Step 2. s.grow(4); changes that single object to 5 by 5. Calling r.area() asks the same object, so it returns 25 and 25 is printed.
Step 3. Rectangle t = null; makes t refer to no object. t.area() tries to call an instance method on null, so a NullPointerException occurs, the program stops, and done is never printed.
25
Check. The output before the failure is a single line, 25, followed by the exception message from the system. The calls compile because the compiler only checks that Rectangle has an area() method; it cannot know that t will be null at that moment.
String objects use instance methods too
String is a class, so its methods from Topic 1.7 are instance methods, called on a String object, which can be a variable or a literal. That is why you write word.length(), not String.length(word).
public class OnStrings {
public static void main(String[] args) {
String word = "programming";
System.out.println(word.length());
System.out.println(word.indexOf("g"));
System.out.println("programming".length() + "programming".indexOf("g"));
}
}
11
3
14
If word held null, word.length() would throw a NullPointerException, for the same reason as above.
Common misconceptions
- “I can call an instance method with just the class name.” You need an object:
Rectangle.area()does not compile. - “A method call on one object affects all objects of the class.” It affects only the object it is called on (or any other name for the same object).
- “A
NullPointerExceptionis a compile-time error.” It occurs at run time; the code compiles. - “A void method returns a value I can store.” It returns nothing.
- “Two variables of the same class share their data.” Only if they refer to the same object.
- “An instance method works the same for every object.” The code is the same, but it uses each object’s own attributes.
- “
nullis an object with no data.” It is the absence of an object.
Where this leads
Practise with the Topic 1.14 practice questions, recap with the revision notes and tick off the checklist. The final topic of the unit, 1.15 String Manipulation, puts everything together on the most used class in the course.
Quick questions
What is the difference between calling a class method and an instance method?
A class method is called with the class name (Math.sqrt(16)) and does not need an object. An instance method is called on an object (box.area()) and works with that object's own attributes. You cannot call an instance method using only the class name.
Why does the same method give different answers for different objects?
Because each object has its own attribute values. small.area() uses small's width and height; big.area() uses big's. The method is the same code; the data it works on is different.
What causes a NullPointerException?
Calling an instance method on a reference variable that holds null, meaning it refers to no object. There is no object to carry out the method, so the program stops at that line with the exception.
Can a method call be part of a bigger expression?
Yes, when the method returns a value. For example small.area() + big.area() adds two returned ints. A void method returns nothing, so it cannot be used inside an expression.