Lesson 15Phase 2: Objects
The this keyword and object references
What this points to, why two variables can share one object, and what equality really compares.
By BasithUpdated 55 min
- this
- object references
- aliasing
- null
- reference vs value
- == vs equals
You have been writing this.name = name since the constructors lesson, copying the pattern because it works. This lesson looks underneath it. What is this actually pointing at? Once you know, a stranger question answers itself: how can two different variables turn out to be the very same object?
- Say what
thisrefers to, and use it to reach a field, to return the current object, and to hand the current object to another one. - Explain that an object variable holds a reference to the object, not the object itself.
- Predict what happens when two variables share one object and the object changes through one of them.
- Predict what a method can and cannot change when you pass it an object.
- Recognize
null, and say why using the dot on it throws aNullPointerException. - Tell
==apart fromequalsfor objects, and say which one asks “same object?”.
What does this point at?#
Every instance method runs on some object. When you write b.resize(99), the method resize runs, and the whole time it runs there is one particular box it is working on: the box that b points at. Inside the method, the word this is the name for that box. thismeans “the object this method is running on right now”.
That one fact explains the pattern you have been copying. Look at a setter whose parameter has the same name as the field:
public class Main { public static void main(String[] args) { Box b = new Box(10); System.out.println(b.size); b.resize(99); System.out.println(b.size); }} class Box { int size; Box(int size) { this.size = size; } void resize(int size) { this.size = size; }}Inside resize there are two things called size: the parameter that just arrived, and the field that lives in the object. A bare size means the closer one, the parameter. So this.size is how you say “the size that belongs to this object”, and this.size = size reads: the field of this object gets the value of the parameter. Step through it. The narration for that line says Set this object's size field to 99, and the program prints 10 and then 99.
Key idea
thisis the object the method is currently running on.this.sizealways means the field of that object, even when a parameter has the same name.
size already means the field, and this.size is a longer way to write the same thing. You only need this to tell apart a field and a parameter (or local variable) that share a name. Many people write this. every time anyway, for clarity. Both styles are fine.Returning this
Because this is a real value, a method can also return it, handing back the very object it is running on. That lets you chain calls together. Watch each bump hand the counter back so the next bump can run on it.
public class Main { public static void main(String[] args) { Counter c = new Counter(); c.bump().bump().bump(); System.out.println(c.value); }} class Counter { int value; Counter bump() { this.value = this.value + 1; return this; }}Each bump adds one to its own value and then says return this, giving back the same counter so that the next .bump() has something to run on. Three bumps, so it prints 3.
Handing yourself to another object
A method can also pass this to another object. Here two people become friends. Each Person has a friend field that can point at another Person, and befriend sets up both directions at once.
public class Main { public static void main(String[] args) { Person ada = new Person("Ada"); Person bob = new Person("Bob"); ada.befriend(bob); System.out.println(ada.name + "'s friend is " + ada.friend.name); System.out.println(bob.name + "'s friend is " + bob.friend.name); }} class Person { String name; Person friend; Person(String name) { this.name = name; } void befriend(Person other) { this.friend = other; other.friend = this; }}Step through befriend and watch the memory view. The call ada.befriend(bob)runs on Ada's object, so inside the method this is Ada. The first line points Ada's friend field at the object other points at, which is Bob. The second line, other.friend = this, reaches into Bob's object and points its friend field back at Ada. When the method ends, each object holds an arrow to the other, and the program prints Ada's friend is Bob and Bob's friend is Ada.
So this is itself a reference: an address that you can store in a field or pass to a method like any other. References are the subject of the rest of this lesson.
A variable does not hold the object#
Here is the idea that surprises almost everyone, so take it slowly. When you write Box b = new Box(10), the variable b does not contain the box. It contains a referenceto the box. A reference is the box's address: a note that says “the actual box is over there”. The box itself, with its size field, lives somewhere else in memory, and b only knows where to find it.
Compare that with a plain int. When you write int n = 10, the variable n holds the 10 itself. An int variable holds a value. An object variable holds an address. That one difference explains everything strange that follows.
* or -> to write, and no arithmetic on addresses. Python names work the same way as Java references here: after a = [1, 2] and b = a, both names refer to one list.Assigning copies the reference, not the object
If a holds an address, then Box b = a copies that address. Afterwards a and b hold the same address, so they point at one and the same box. There is still only one box. It just has two names, and a change made through either name changes the one object both of them see. Run this and watch the memory view: one box, two arrows pointing at it.
public class Main { public static void main(String[] args) { Box a = new Box(5); Box b = a; b.size = 100; System.out.println(a.size); System.out.println(b.size); }} class Box { int size; Box(int size) { this.size = size; }}We only ever touched b. The narration says Set b.size to 100, and nothing in the program writes to a.size. Yet both lines print 100, because a and b are two names for one box. When two variables refer to the same object like this, they are called aliases of each other, and the situation is called aliasing. There is no second box with 5 still in it. There is one box, and you changed it.
Key ideaAssigning one object variable to another copies the reference, not the object. Both names then point at the same single object, and a change made through one is visible through the other.
An alias lasts only until one of the variables is pointed somewhere else. Assigning a new object to a variable moves that variable's arrow and leaves every other variable where it was.
a.size?Box a = new Box(5); Box b = a; b = new Box(9); b.size = 10;Since assignment moves arrows rather than objects, you can swap which object two variables point at without touching either object. You need a third variable to hold one address for a moment, the way you would need a spare hand to swap the things in your left and right hands.
left and right swap boxes and the program prints left holds 2 and then right holds 1. Neither box changes; only the arrows move.Tap a line, then tap where it should go. Or use the arrows.
Aliasing reaches through methods too, including the getters and setters from encapsulation. Two names, one account: a deposit made through one name shows up through the other.
public class Main { public static void main(String[] args) { Account x = new Account(100); Account y = x; y.deposit(50); System.out.println(x.getBalance()); System.out.println(y.getBalance()); }} class Account { private int balance; Account(int balance) { this.balance = balance; } void deposit(int amount) { this.balance = this.balance + amount; } int getBalance() { return this.balance; }}We deposited through y. Both x and y report 150, because there is one account and they both point at it. balance is still private, so the change could only happen through deposit. The object still guards its own field; it just has two names.
Account object to a method and that method changes it, the change sticks, because the method received a copy of the reference pointing at your one real account. This is one more reason encapsulation matters: a private field can only change through methods you wrote, so the object controls its own changes even when it is shared.Passing an object to a method#
The methods lesson showed that Java passes arguments by value: the method gets a copy. For an int, the copy is the number, so the method cannot change your variable. For an object, the copy is the reference. The method's parameter then points at the same object as your variable, so the method can change that object's fields. What it cannot do is make your variable point at a different object. Decide what this program prints before you run it.
public class Main { public static void main(String[] args) { Box box = new Box(5); Workshop shop = new Workshop(); shop.grow(box); System.out.println(box.size); shop.replace(box); System.out.println(box.size); }} class Box { int size; Box(int size) { this.size = size; }} class Workshop { void grow(Box b) { b.size = b.size + 10; } void replace(Box b) { b = new Box(99); b.size = b.size + 1; }}The workshop gets the same box twice. What are the two printed sizes?
It prints 15 and then 15. grow received a copy of the reference, so its b and main's box pointed at the same box, and b.size = b.size + 10 changed that box from 5 to 15. replace also started with b pointing at that box, but its first line, b = new Box(99), pointed b at a brand-new box. From then on the method worked on the new box, raised it to 100, and forgot it when the method ended. box in main still points at the original box, which holds 15. Open it in the playground and step through replace: the narration shows b being set to the new box, and main's box never moves.
Key ideaA method that receives an object can change the object's fields, and the caller sees the change. It cannot make the caller's variable point at a different object: assigning to the parameter only moves the method's own copy of the reference.
Making a real copy
Sometimes you want the opposite of aliasing: a second, separate object that starts with the same contents, so that changing one leaves the other alone. Assignment cannot give you that, since it only copies the address. You have to build the second object with new, and a method on the class can do it for you.
copy() returns this, so the “copy” is only an alias and changing it changes the original. Fix copy() so that it returns a new, separate box with the same size. The program should then print original: 5, copy: 50 and false.Null: a reference that points at nothing#
A reference variable holds an address. So what does it hold before you give it an object to point at? The answer is a special value called null. nullmeans “this variable points at no object at all”. It is the empty slot, the note with no house written on it.
You can set a reference to null on purpose, and an object field you never assigned starts as null too. The trouble comes when you forget, and then reach through a null with the dot. There is no object on the other end, so there is nothing to reach into, and Java stops the program at once. Run this and read the error it reports.
public class Main { public static void main(String[] args) { Box b = null; System.out.println(b.size); }} class Box { int size; Box(int size) { this.size = size; }}NullPointerException: Cannot read field "size" because "b" is null
A variable held null, so there was no object to use.
The program halts on b.size with a NullPointerException. The dot means “reach into the object b points at”, but b points at nothing, so there is no size to read. This is one of the most common errors you will ever meet in Java. When you see it, the question to ask is always the same: which variable did I expect to hold an object, and why is it still null?
x.something, you are assuming x points at a real object. If x is null, that assumption is wrong and you get a NullPointerException. The fix is to make sure x was given an object (usually with new) before you use the dot, or to check for null first.null using ==: if (b == null) is true exactly when b points at nothing. That comparison never crashes, because it does not use the dot; it only asks what the variable holds. Following the reference with a dot is the only thing that fails on null.Box b = null;, which line throws a NullPointerException?Two kinds of equal: == and equals#
Now that you know a variable holds a reference, the difference between == and equals makes sense. For objects, == compares the references. It asks: are these two names pointing at the same one object? It does not look inside at the fields at all.
So two boxes that both hold 5 are still two separate boxes at two different addresses, and == says they are not equal. == says true only when two variables point at the very same object. Watch.
public class Main { public static void main(String[] args) { Box a = new Box(5); Box b = new Box(5); System.out.println(a == b); Box c = a; System.out.println(a == c); }} class Box { int size; Box(int size) { this.size = size; }}a == b prints false: two separate boxes with the same contents. Then c = a makes c an alias of a, so a == c prints true: same object, same address. == asks about identity, not about contents.
What about equals?
There is a second way to ask about equality: the equals method, which every object has. The promise of equals is that it compares contents: are these two objects equal in the ways that matter, even if they are separate objects? But here is the part to know now. Until a class is taught how to compare its own contents, equals does exactly what == does: it checks identity.
public class Main { public static void main(String[] args) { Box a = new Box(5); Box b = new Box(5); System.out.println(a.equals(b)); Box c = a; System.out.println(a.equals(c)); }} class Box { int size; Box(int size) { this.size = size; }}Both lines match the == results: a.equals(b) is false (separate objects) and a.equals(c) is true (same object). So for the classes you write today, equals and == give the same answer. That is not the end of the story.
5: false.5, with the default equals: false.== means.null: b == null only checks the reference.b.equals(...) when b is null throws a NullPointerException.equals that looks at the fields. Writing one needs two ideas you have not met yet, inheritance and overriding, and the full recipe is in equals, hashCode and toString. Until then, the rule to carry is simple: for your own classes, == and the default equalsboth mean “the same object”.Try it
Answer each one in your head or on paper first, then open the answer.
this refer to?void resize(int size) { this.size = size; }, why is the this. needed here, when it is optional in many other methods? Trace the broken version below, where this. was dropped, and explain the printed result.Box a = new Box(5); Box b = a; b.size = 100; System.out.println(a.size); System.out.println(b.size);void reset(Box b) contains the single line b = new Box(0);. After reset(myBox), has myBox changed? What would reset have to do instead to set the box's size to 0?NullPointerException on the line account.deposit(50);. In your own words, what does that tell you about account?Box a = new Box(5) and Box b = new Box(5). What does a == b print, and what does a.equals(b) print for this class? Why are they the same?Practice
Work through these on your own. Each one builds on this page.
Box c = b;, change c.size to 7, and print a.size, b.size and c.size. Before you run it, write down what you expect each to be, then check. Explain in one line why all three agree.Pencil class with one field int length and a constructor. In main, declare Pencil p = null; and then try to print p.length. Run it, read the exact error, and write one sentence saying what would have to change for the line to work.Counter from the chaining example a second method Counter reset() that sets value back to 0 and returns this. Then write one chained line that bumps four times, resets, and bumps once, and predict the final value. Confirm with a print.void unfriend() to the Person class from the friends example. It should clear both directions: this person's friend field and the friend's friend field both become null. Be careful about the order of the two lines, and about a person who has no friend at all. Test it on Ada and Bob.Box a = b; does not make a copy of the box. Use the words reference and address, and say what a == b would print afterwards.Further reading#
- Using the this Keyword, from the dev.java tutorial
- Passing Reference Data Type Arguments, the dev.java explanation of what a method can change in an object you pass it
- Reference equality operators in the Java Language Specification, section 15.21.3, the exact meaning of == on objects
- Object.equals in the Java SE 25 API documentation, including the default behavior
Short guides on this topic
Quick answers to what people search for about it, with code to run.
Spotted a mistake, or a part that did not make sense? Report it by email