Instance methods and this
On This Lesson 11 topics
Lesson Overview
So far your objects hold data but cannot do anything. This lesson adds behavior: instance methods, methods that run on one particular object and can read and change that object's fields. With them, an Account can deposit, withdraw, and describe itself.
The lesson also explains this, the keyword you have been copying since Lesson 2. In the blueprint picture, every house built from the plan has a front door with a name plate. When a method runs, this is the name plate of the house it is currently working in.
Why this matters: nearly all backend business logic lives in methods that operate on an object's own data. And the shadowing bug you will meet today, assigning a parameter to itself, is one of the most common silent bugs in Java code.
This lesson builds on Lessons 1 and 2, and on parameters and return values from the methods module.
Learning Objectives
By the end of this lesson you will be able to:
- Write an instance method (no
static) that reads and changes fields. - Choose between
voidand a return type for an instance method. - Explain that the same method code runs on different objects with different results.
- Explain what
thisrefers to and call another instance method from inside one. - Predict and fix the "parameter shadows field" bug.
- Compare an instance method with the
staticmethods you wrote before.
Written Explanation
From data to behavior
An instance method is declared inside the class, like a field, but it has a body. The difference from the methods you wrote in the methods module is that it does not use static. Because it is not static, it runs on an object and has direct access to that object's fields.
void deposit(double amount) {
balance += amount;
}Look at what is missing: there is no account parameter and no a.balance. The method just says balance, and Java understands it as "the balance of the object this method was called on". You call it with the dot:
ana.deposit(50); // changes ana's balance
omar.deposit(50); // changes omar's balance, not ana'sThe code of deposit exists once, in the class. The state it changes comes from whichever object is in front of the dot. That is the whole idea of object-oriented methods: behavior is shared, state is per object.
Return values
Instance methods can be void or return a value, exactly like the methods you know. A good rule: if the caller needs to know whether something worked or needs an answer, return it. Here withdraw answers "did it work?":
boolean withdraw(double amount) {
if (amount > balance) {
return false; // not enough money: nothing changes
}
balance -= amount;
return true;
}And describe builds text from the object's own fields:
String describe() {
return owner + " (" + number + "): " + balance;
}Returning a String rather than printing inside the method keeps the method flexible. The caller decides whether to print it, log it, or send it over a network.
What this means
Inside an instance method or a constructor, this is a reference to the current object: the object whose method is running. The Java tutorial defines it that way. Written in full, deposit could be:
void deposit(double amount) {
this.balance += amount; // identical to: balance += amount;
}Both forms do the same thing. this. is optional when there is no clash of names. When you call ana.deposit(50), this is ana; when you call omar.deposit(50), this is omar.
An instance method can also call another instance method of the same object. You can write describe() or this.describe(); both refer to the current object.
The bug: a parameter shadows a field
Suppose a parameter has the same name as a field, which is very common, because the best name for the owner you receive is owner. Inside the constructor, the plain name owner now refers to the parameter. The parameter shadows (hides) the field of the same name.
public class Shadow {
String owner;
Shadow(String owner) {
owner = owner; // assigns the parameter to itself
}
}The compiler accepts this, because assigning a variable to itself is legal. But nothing reaches the field. Creating new Shadow("Ana") and printing s.owner shows:
nullThe fix is the pattern you have been using: this.owner = owner;. The left side is the field of the current object, the right side is the parameter. Remember the symptom: a field stays at its default (null, 0) even though you passed a value. Whenever you see that, check for a missing this.
Some programmers avoid shadowing by choosing a different parameter name, such as startOwner. Both approaches work. The convention in most Java code is to keep the same name and use this.
Instance versus static methods
You wrote static methods in the methods module, and main is one of them. A static method is called by class name and has no current object, so it has no this. An instance method is called on an object and has one. As a rule of thumb: if the method needs one object's data, it is an instance method. Lesson 5 compares the two properly; for now, write static only for main and for helpers that need no object.
Examples
Example 1: deposit and describe (simple)
public class Account {
String owner;
String number;
double balance;
Account(String owner, String number, double balance) {
this.owner = owner;
this.number = number;
this.balance = balance;
}
void deposit(double amount) {
balance += amount;
}
String describe() {
return owner + " (" + number + "): " + balance;
}
}Account a = new Account("Ana", "A-1", 100);
Account b = new Account("Omar", "A-2", 80);
a.deposit(50);
System.out.println(a.describe());
System.out.println(b.describe());Output (JDK 21):
Ana (A-1): 150.0
Omar (A-2): 80.0The same deposit ran once, on a. Omar's account is unchanged.
Example 2: a method that answers yes or no (practical)
Adding withdraw from above to Account, then:
boolean ok = b.withdraw(200);
System.out.println(b.describe() + " withdraw ok? " + ok);Output:
Omar (A-2): 80.0 withdraw ok? falseOmar tried to take out 200 with only 80 in the account. withdraw returned false and did not change the balance. The caller can now react: show a message, ask for a smaller amount. Compare this with a method that silently subtracts and leaves a negative balance. The method protects the object's data by checking first. (A later module on hiding data shows how to make this protection stronger.)
Example 3: methods on a product (realistic)
A shop sells products, and each sale reduces the stock:
public class Product {
String name;
double price;
int stock;
Product(String name, double price, int stock) {
this.name = name;
this.price = price;
this.stock = stock;
}
boolean sell(int qty) {
if (qty <= 0 || qty > stock) {
return false;
}
stock -= qty;
return true;
}
double stockValue() {
return price * stock;
}
}Product tea = new Product("Tea", 2.5, 10);
System.out.println(tea.sell(4) + " " + tea.stock);
System.out.println(tea.sell(7) + " " + tea.stock);
System.out.println(tea.stockValue());Output:
true 6
false 6
15.0The first sale succeeds and leaves 6. The second asks for 7 but only 6 remain, so it returns false and stock stays 6. The value of the stock is 2.5 * 6 = 15.0. All three methods work on the product in front of the dot, with no extra parameters for price or stock.
Backend Insight
In a backend, the methods of your classes are the business rules: "an order can be cancelled only before it ships", "a withdrawal cannot exceed the balance". Putting the rule next to the data it protects, in an instance method, means the rule exists in one place. A web controller or a scheduled job then calls account.withdraw(amount) and cannot forget the check.
Notice also what the method returns. Backend code often needs to tell its caller what happened, and returning a result (here true or false; later, richer results and exceptions) is how business rules talk to the rest of the program. Finally, the shadowing bug matters in backend work because frameworks often create objects through constructors and setters. A field silently left null may reach a database insert, and the error appears far from its cause.
Common Mistakes
Mistake 1: Forgetting this. when a parameter has the same name as a field
- Incorrect:
Fix(String name) { name = name; }, which leaves the fieldnull. - Correct:
Fix(String name) { this.name = name; }.
If a field stays at its default after construction, look here first.
Mistake 2: Declaring an instance method static
Adding static to deposit removes the current object. The method can no longer use balance, and the compiler reports non-static variable balance cannot be referenced from a static context. Lesson 5 shows this error in detail.
Mistake 3: Passing the object's own data as parameters
- Awkward:
double stockValue(double price, int stock) { return price * stock; }, where the caller has to pass in what the object already knows. - Better:
double stockValue() { return price * stock; }.
If the data is in the object, the method should read it from the object.
Mistake 4: Printing inside every method
A method that both computes and prints is harder to reuse. Prefer returning the result (or text), and let main or another caller print.
Mistake 5: Ignoring the returned value
b.withdraw(200); on its own compiles, but throws away the answer. If a method returns whether it worked, use the result.
Exercises
Try each exercise yourself before opening the solutions. We continue with Product; write the code and run it where possible.
Level A: Foundation
A1. In a.deposit(50), what does this refer to inside deposit?
Show solution A1
A1. To a, the object in front of the dot. For b.deposit(50) it would be b.
A2. Does an instance method need a parameter to read the object's balance? Explain.
Show solution A2
A2. No. The method is called on an object and reads the fields of that object directly (balance, or this.balance). A parameter is needed only for values that come from outside, such as the amount.
A3. Why does owner = owner; in a constructor compile but not work?
Show solution A3
A3. Assigning a variable to itself is legal Java, so the compiler accepts it. But owner on both sides means the parameter (it shadows the field), so the field is never assigned and stays null. Use this.owner = owner;.
Level B: Application
B1. Add two instance methods to Product: restock(int qty) that adds to the stock (ignore non-positive quantities), and isAvailable() that returns true when stock is above zero. Show a main that uses both.
Show solution B1
B1.
void restock(int qty) {
if (qty > 0) {
stock += qty;
}
}
boolean isAvailable() {
return stock > 0;
}Product tea = new Product("Tea", 2.5, 0);
System.out.println(tea.isAvailable()); // false
tea.restock(20);
System.out.println(tea.isAvailable()); // trueB2. The next class has a bug. Find it, say what new Item("Pen", 3).describe() returns, and fix it.
public class Item {
String name;
int qty;
Item(String name, int qty) {
this.name = name;
qty = qty;
}
String describe() { return name + " x" + qty; }
}Show solution B2
B2. The line qty = qty; assigns the parameter to itself, so the field qty stays 0. The call returns "Pen x0". Fix: this.qty = qty;.
B3. What does this print? Trace the two objects.
Product p = new Product("Tea", 2.5, 10);
Product q = new Product("Mug", 6, 3);
p.sell(2);
q.sell(5);
System.out.println(p.stock + " " + q.stock);Show solution B3
B3. It prints 8 3. Product p had 10 and sold 2, leaving 8. Product q had 3 and the request for 5 was refused, so it keeps 3. Each object has its own stock.
Level C: Challenge
C1. Write a Counter class with an int value starting at 0, a method increment(), and a method incrementBy(int n) that calls increment() n times in a loop. Show a main that makes two counters, increments them differently, and prints both.
Show solution C1
C1.
public class Counter {
int value;
void increment() {
value++;
}
void incrementBy(int n) {
for (int i = 0; i < n; i++) {
increment();
}
}
}Counter a = new Counter();
Counter b = new Counter();
a.incrementBy(5);
b.increment();
System.out.println(a.value + " " + b.value); // 5 1value starts at 0 through the default for int fields. The call increment() inside incrementBy runs on the same object (this).
C2. Add to Product a method applyDiscount(double percent) that lowers the price by that percentage, and refuses (returns false) if the percentage is below 0 or above 100. Show one accepted and one refused call.
Show solution C2
C2.
boolean applyDiscount(double percent) {
if (percent < 0 || percent > 100) {
return false;
}
price = price * (100 - percent) / 100;
return true;
}For a product priced 10.0, applyDiscount(20) returns true and the price becomes 8.0; applyDiscount(150) returns false and the price stays as it was. (Floating-point results may show tiny rounding differences for other numbers, a reminder that double is not ideal for money.)
Mini Challenge
Extend your own class from the earlier Mini Challenges, or Account, with three instance methods:
- One
voidmethod that changes a field (for exampledeposit). - One method that returns
booleanand refuses an invalid request (for examplewithdraw). - One method that returns a
Stringdescription.
Then write a main with two objects, call each method on at least one of them, and print the descriptions. Finally, temporarily change one constructor assignment to field = field;, run it, and note which value goes missing. Restore this.
Check your work: the second object is unaffected by calls to the first, and no method prints and computes at the same time except main.
Summary & Next Lesson
Key takeaways
- An instance method has no
static, runs on one object, and reads or changes that object's fields directly. - The method code is shared; the state is per object.
thisis the object whose method or constructor is running, and it is optional unless a name clashes.- A parameter with the same name as a field shadows it.
owner = owner;leaves the field unset; usethis.owner = owner;. - Return a result (a value,
trueorfalse, or aString) rather than printing inside the method.
Next lesson
You can now define a full Account. The next lesson, "Creating and using objects", looks closer at what new and the variable really give you: references. It explains why two variables can point at one object and what null means when you call a method on it.
Self-check
- What is the difference between an instance method and the
staticmethods you wrote earlier? - In a method call
x.f(), what isthisinsidef? - What is the symptom of a missing
this.in a constructor? - Why is returning a
Stringoften better than printing inside the method? - How can one instance method call another on the same object?
Show answers
- An instance method is called on an object and can use that object's fields and
this; astaticmethod is called on the class and has no current object. - The object
x. - The field keeps its default (
null,0) even though a value was passed. - The caller can decide what to do with the text: print it, log it, or send it.
- By calling it by name,
g(), or withthis.g().
References
- Oracle, The Java Tutorials, Using the this Keyword. Supports:
thisis a reference to the current object, and a parameter with the same name as a field shadows it, sothis.xis needed to reach the field. - Oracle, The Java Tutorials, Understanding Class Members. Supports: instance methods access instance variables directly, and class (
static) methods cannot usethis. - All outputs in this lesson (including the
nullfrom the shadowing bug and theProductresults) come from compiling and running the code on JDK 21. The solutions to B1, B2, B3, C1, and C2 were also compiled and run.
Practice
Apply this lesson with the module's lab, exercises and quiz.
Marking a lesson complete is optional and you can undo it any time. Your progress stays in this browser.