Learn Java

Lesson 11Phase 2: Objects

Creating classes and objects

Writing your first class with fields, making objects with new, and reading and writing their state.

By Updated 60 min

  • class definition
  • fields
  • new
  • instance
  • dot notation
  • default values

So far objects have been an idea: a blueprint, and the cookies pressed from it. Now you build them. You will write a class of your own from a blank file, make objects from it, and look inside them in the memory view as their fields change.

By the end of this lesson
  • Write a class of your own with fields, from nothing.
  • Create objects, instances of your class, with new, and read and write their fields with the dot.
  • Predict what a field holds before you have set it, using the default-value rules.
  • Explain why a variable holds a reference to an object, and what happens when you copy that variable.
  • Keep many objects in one array and walk them with a loop.
From before
From why objects you already have the two words this whole lesson rests on: a class is a blueprint written once, and an object is a real thing built from it with new. You also have variables, types and arrays. That is all you need. Nothing on this page does more than read and set fields; methods that give objects behavior come in the next lesson.

Defining a class with fields#

Pick something concrete: a student. A student hasa name, an age and a grade-point average. Those three things are the student's state, the data that belongs to it. In a class, each piece of state is written as a field: a variable that lives inside the object instead of inside a method.

Student.java
1public class Main {
2 public static void main(String[] args) {
3 System.out.println("The Student class is defined below.");
4 }
5}
6
7class Student {
8 String name;
9 int age;
10 double gpa;
11}
Prints
›The Student class is defined below.

Read the Student block carefully, because the shape is the whole point. The keyword class says “I am about to describe a kind of thing”. Then comes a name, Student, with a capital letter by convention. Then, inside the braces, three field declarations. Each one is a type followed by a name, just like declaring a variable, but with no value attached and no method around it. That is a field.

The program prints only its one line. Defining a class builds no students at all: main never asks for one, so none exists. The class just sits there, ready to be used.

Key idea

A field is a variable that belongs to the object. Every Student object you build carries its own name, its own age and its own gpa.

A field is not a local variable
Every variable you have declared so far lived inside main. A variable declared inside a method is called a local variable: it exists only while that method runs. A field is different. It lives as long as its object lives: when the object is built, its fields come into being, and while the object exists, the fields hold its state. The declaration looks the same; the home and the lifetime are different.
Coming from Python?
A Python object gets its attributes when you first assign them, usually in __init__, and you can add a new one to a single object later. A Java object has exactly the fields its class declares, no more and no fewer, and each field holds one type.

Making objects and setting their fields#

The class on its own builds nothing. To get a real student you use new, exactly as in why objects. Then you reach into the fresh object with the dot and set each field.

Main.java
1public class Main {
2
3 public static void main(String[] args) {
4 Student s1 = new Student();
5 s1.name = "Mara";
6 s1.age = 19;
7 s1.gpa = 3.6;
8
9 Student s2 = new Student();
10 s2.name = "Theo";
11 s2.age = 21;
12 s2.gpa = 3.9;
13
14 System.out.println(s1.name + ", age " + s1.age + ", gpa " + s1.gpa);
15 System.out.println(s2.name + ", age " + s2.age + ", gpa " + s2.gpa);
16 }
17}
18
19class Student {
20 String name;
21 int age;
22 double gpa;
23}
Prints
›Mara, age 19, gpa 3.6
›Theo, age 21, gpa 3.9

Press Run and step through it slowly, watching the memory view under the code. The first new Student() adds a Student object there, with every field at a starting value, and the three lines after it fill those fields in. The second new Student() adds a completely separate object, and filling its fields never touches the first one. Each of them is an instance of Student: two cookies from one cutter.

The dot does two different jobs depending on which side of the = it sits on. On the left, s1.age = 19 writes into the object: it sets the age field belonging to s1. On the right, inside the println, s1.age reads the same field back out. Underneath, the dot always means the same thing: reach into this one object and point at one field inside it.

What you write
What it means
new Student()
Build one fresh Student object and hand back a reference to it.
s1.name = "Mara"
Write: set the name field of the object s1 points at.
s1.name (in an expression)
Read: fetch the current name field of that object.
s2 after its own new
A different object. Setting s1's fields never changes it.
Capital for classes, lowercase for variables
Student starts with a capital and s1 does not. Java does not force this, but everyone follows it: class names are Capitalized, and variables, including those holding objects, start in lowercase. When you read code later, the capital letter is your instant signal “this is a type, a blueprint” rather than “this is one thing”.

What a field holds before you set it#

Here is a question that catches people. The moment new Student() runs, before you have assigned anything, what is in name? What is in age? They are not garbage, and they are not missing. Java gives every field a fixed default value the moment the object is built. Run this and read each line of output.

Defaults.java
1public class Main {
2
3 public static void main(String[] args) {
4 Student s = new Student();
5
6 System.out.println("name: " + s.name);
7 System.out.println("age: " + s.age);
8 System.out.println("gpa: " + s.gpa);
9 System.out.println("enrolled: " + s.enrolled);
10 }
11}
12
13class Student {
14 String name;
15 int age;
16 double gpa;
17 boolean enrolled;
18}
Prints
›name: null
›age: 0
›gpa: 0.0
›enrolled: false

Step through and look at the memory view before any printing happens: the fields already hold values. The whole-number field age starts at 0. The decimal field gpa starts at 0.0. The boolean enrolled starts at false. And name, which is a String, starts at null.

That last one deserves a moment. nullis a special value that means “points at no object at all”. A Stringfield is not a number or a true-or-false value; it refers to a text object somewhere, and until you give it one, it points at nothing. So its default is the “nothing here yet” value, null, and printing it shows the word null.

The rule is for fields (and array slots). A local variable inside main gets no default at all. Declare int count; and print it before giving it a value, and the program does not even compile: javac stops with variable count might not have been initialized.

Quick check
A program runs Student s1 = new Student();, then Student s2 = new Student();, then s1.age = 19;. What does System.out.println(s2.age); print?
null is a trap waiting to be sprung
A name that is null looks harmless until you use it as if it were real text. Asking a null for its length or its letters stops the program with a NullPointerException, Java's complaint that there was no object to ask. The cure is simple: set your fields to real values before you lean on them.
There is a better way to set fields
Setting s.name, s.age and s.gpa one line at a time works, but it is easy to forget one and leave a null or a 0 behind. Constructors let you fill every field at the moment of new, in one step, so an object is never born half-built. And encapsulation will later stop outside code from writing nonsense, such as a negative age, into your fields at all. You do not need either yet.

The variable does not hold the object#

Now the idea that quietly runs underneath everything above. When you write Student s1 = new Student(), it is tempting to picture the whole student living inside s1, the way a number lives inside an int variable. That picture is wrong, and getting it right now saves you real confusion later.

The object, with its name and age and gpa, lives in memory on its own. The variable s1 does not contain it. s1 holds a referenceto it, which you can think of as the object's home address. The variable holds the address; the object lives at that address. When you write s1.age, Java follows the address to the object and reads the field there.

This sounds like hair-splitting until you copy one variable into another. Watch what happens when a program does exactly that.

Alias.java
1public class Main {
2
3 public static void main(String[] args) {
4 Student a = new Student();
5 a.name = "Mara";
6 a.age = 19;
7
8 Student b = a;
9 b.age = 20;
10
11 System.out.println(a.name + " is " + a.age);
12 System.out.println(b.name + " is " + b.age);
13 }
14}
15
16class Student {
17 String name;
18 int age;
19}
Prints
›Mara is 20
›Mara is 20

Step through it and keep your eye on the memory view. new runs once, so there is only ever one Student object there. The line Student b = a does not build a second student. It copies the address in a into b, so now a and b both point at the same object: two names for one thing. So when b.age = 20 runs and you then read a.age, you get 20 as well. Both lines print Mara is 20, because both variables are looking at the very same student.

Key idea

A variable of a class type holds a reference, an address, not the object. Copy the variable and you copy the address, so both names now reach the one object behind it.

Predict the outputAges.java
1public class Main {
2
3 public static void main(String[] args) {
4 Student a = new Student();
5 a.age = 19;
6
7 Student b = new Student();
8 b.age = 19;
9
10 Student c = a;
11 c.age = 30;
12 b.age = b.age + 1;
13
14 System.out.println(a.age + " " + b.age + " " + c.age);
15 }
16}
17
18class Student {
19 String name;
20 int age;
21}

Two objects are built here, and one of them gets a second name. What does the last line print?

It prints 30 20 30. new ran twice, so there are two students. c = a copied an address, so c is a second name for the first student, and setting c.age to 30 changes the age a sees too. b is the other student, untouched by all of that, and its own age goes from 19 to 20. Count the news to count the objects; every other assignment of an object variable only copies an address.

Compare this with a plain int
Try it in your head with numbers. int x = 19; int y = x; y = 20; leaves x at 19, because y got its own copy of the number. Objects work differently: the value being copied is the address, not the student, so both variables end up at the same student. This is the single biggest surprise for newcomers, and now you have seen it before it bit you.
Quick check
Which of these lines builds a new Student object?

Many objects in one array#

An object can go anywhere a value can, including into an array. A Student[] is an array whose slots hold references to Student objects. This is the clean answer to the parallel arrays in why objects: one array, where each slot leads to a whole student with all of its fields together.

Roster.java
1public class Main {
2
3 public static void main(String[] args) {
4 Student[] roster = new Student[3];
5 System.out.println(roster[0]);
6
7 roster[0] = new Student();
8 roster[0].name = "Mara";
9 roster[0].age = 19;
10
11 roster[1] = new Student();
12 roster[1].name = "Theo";
13 roster[1].age = 21;
14
15 roster[2] = new Student();
16 roster[2].name = "Ines";
17 roster[2].age = 20;
18
19 int total = 0;
20 for (Student s : roster) {
21 System.out.println(s.name + " is " + s.age);
22 total = total + s.age;
23 }
24 System.out.println("Average age: " + total / roster.length);
25 }
26}
27
28class Student {
29 String name;
30 int age;
31}
Prints
›null
›Mara is 19
›Theo is 21
›Ines is 20
›Average age: 20

new Student[3] makes the array, not the students. Its three slots are references, and like any reference they start as null, which is what the first line prints. Each roster[i] = new Student() puts a real student in a slot, and roster[0].name reaches through the slot into that student. The for-each loop then hands you each student in turn as s. Step through it and watch three separate Student objects appear in the memory view, one for each slot.

This fixes the trouble with parallel arrays. A student's name and age travel together, so sorting or removing students can never pair a name with the wrong age. And a field added to Student once, such as gpa, is there for every student in the array.

A new array of objects holds no objects
new Student[3] does not run new Student() for you. Use a slot before you fill it, as in roster[0].name = "Mara"; on a fresh array, and the program stops with a NullPointerException, because there is no student in that slot to reach into.
Put the lines in orderPets.java
Put the lines in order so the program fills an array with two pets and prints their names, Biscuit and then Mochi. Careful: a slot must hold a pet before you can set its name.

Tap a line, then tap where it should go. Or use the arrows.

ExerciseBooks.java
Write the Book class yourself, with three fields: title, author and pages. Then, in main, build the two books described in the comments, print one line for each in the format shown, and print their total number of pages.

Try it

Answer each one in your head or on paper first, then open the answer.

1
Write the field lines for a Book class that has a title, a number of pages and a price. Use sensible Java types.
2
Given the Student class with String name; int age; double gpa;, a fresh object is made with Student s = new Student(); and nothing else. What do s.name, s.age and s.gpa hold right now?
3
In s1.gpa = 3.6; and then later System.out.println(s1.gpa);, the same text s1.gpa appears twice. Is it doing the same job both times?
4
Take the alias example. After Student b = a; and b.age = 20;, why does a.age also read 20?
5
After Student[] group = new Student[4];, how many Student objects exist, and what does group[2] hold?

Practice

Work through these on your own. Each one builds on this page.

1
In the playground, define a Car class with the fields String make;, String model; and int year;. In main, build two different cars with new, set all three fields on each, and print a line describing each car. Confirm the two cars are independent.
2
Add a boolean electric; field to your Car, but do not set it on one of the cars. Print that car's electric field. Before you run it, write down what you expect, then check whether you were right and say why.
3
Build one car a, set its fields, then write Car b = a; and change b.year. Print a.year and b.year. Then, separately, build a truly second car with its own new Car() and show that changing one of those does not affect the other. In one or two sentences, explain the difference between the two situations.
4
Put three cars in a Car[] array. Then write a loop that finds the oldest car (the smallest year) and prints its make and model. Your loop should still work if you add a fourth car to the array.

Further reading#

Spotted a mistake, or a part that did not make sense? Report it by email