Course outline · 0% complete

0/29 lessons0%

Course overview →

Your first class

lesson 5-1 · ~7 min · 13/29

From lesson 4-2, word.length() needs a String object to act on, while Math.sqrt(2.0) is called on the class itself.

length() is an instance method, belonging to one particular String object. sqrt is a static method, belonging to the Math class.

This unit is where you build the objects that instance methods live on, so both halves of that distinction finally have code behind them.

Classes model things

Classes were invented to solve a scaling problem. In a big program the data describing one thing, such as a user's name, email, and balance, drifts apart from the code that operates on it, and every change breaks something elsewhere.

A class fuses the two into one unit. This is the core move of object-oriented programming, and it is how essentially all large Java systems are organized, including the standard library you have been calling all along. String with its .length() is a class, and now you build your own.

So far Main has been a wrapper. A class becomes interesting when it describes a kind of thing: what data each one carries, its fields, and what each one can do, its methods.

class Dog {
  String name;   // field
  int age;       // field

  void bark() {  // instance method, no static
    System.out.println(name + " says woof");
  }
}

An object, also called an instance, is one concrete dog built from that blueprint:

Dog rex = new Dog();
rex.name = "Rex";
rex.bark();

new Dog() allocates a fresh object, and each object gets its own copy of the fields. Note that bark carries no static, because it belongs to one dog, and inside it name means this dog's name.

Two dogs from one blueprint

One class, two objects, each holding its own data.

public class Main {
  public static void main(String[] args) {
    Dog rex = new Dog();
    rex.name = "Rex";
    rex.age = 3;

    Dog bella = new Dog();
    bella.name = "Bella";
    bella.age = 5;

    rex.bark();
    bella.bark();
    System.out.println(rex.name + " is " + rex.age);
  }
}

class Dog {
  String name;
  int age;

  void bark() {
    System.out.println(name + " says woof");
  }
}

Output

Rex says woof
Bella says woof
Rex is 3

The same bark body printed two different names, because name inside an instance method refers to the object the call was made on. There is one copy of the method and two copies of the fields.

A second class can sit in the same file below Main as long as it is not marked public, which is why Dog needs no file of its own here. Real projects give each class its own file.

Dog rex(reference)Dog bella(reference)Dog objectname = "Rex"age = 3Dog objectname = "Bella"age = 5
One class, two objects. Each new Dog() creates a separate object with its own field values. The variables hold references pointing at them.

Fields start with default values

A local variable must be assigned before use, and the compiler refuses otherwise. Fields are different: every field gets an automatic default value the moment new runs, because the object has to be in some defined state before a constructor or your code fills it in.

The defaults are all zero-like:

Field typeDefault
int, double, and other numbers0
booleanfalse
any object type, including Stringnull
Dog stray = new Dog();
stray.age    // 0
stray.name   // null, no String here yet

null means this reference points at nothing. Calling a method through it, as in stray.name.length(), throws a NullPointerException, the most famous crash in Java.

That is the practical reason the next lesson exists. Constructors make sure no object ever leaves new half-empty.

The state of a brand-new object

Right after Dog d = new Dog(); with no constructor and no assignments, d.name is null and d.age is 0.

Fields get automatic defaults so the object is well-defined from birth: numbers become 0, booleans become false, and object references become null.

null is not the empty string. "" is a real String object with a length of 0 that you can call methods on, while null is the absence of any object, so d.name.length() throws a NullPointerException rather than returning 0.

The distinction between "empty" and "absent" runs through the rest of the language, and later units meet it again in collections and in database columns. An empty list and a missing list are different problems.

A Book class with two instances

Fields are declared like variables inside the class, and the instance method uses them directly.

public class Main {
  public static void main(String[] args) {
    Book b1 = new Book();
    b1.title = "Dune";
    b1.pages = 412;

    Book b2 = new Book();
    b2.title = "Emma";
    b2.pages = 320;

    b1.describe();
    b2.describe();
  }
}

class Book {
  String title;
  int pages;

  void describe() {
    System.out.println(title + " has " + pages + " pages");
  }
}

Output

Dune has 412 pages
Emma has 320 pages

describe has no static and no parameters. It reads title and pages from whichever object it was called on, which is what makes one method body serve every book.

The three-step shape in main, allocate with new, assign the fields, then call a method, is exactly the clumsiness the next lesson removes. Forgetting the second step leaves the defaults in place and prints null has 0 pages.