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 3The 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.
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 type | Default |
|---|---|
int, double, and other numbers | 0 |
boolean | false |
any object type, including String | null |
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.