Dev Learning Lab

The labs on this site need JavaScript to run. The same content is written out below.

Fundamentals › Java

Classes and objects

How is a class different from an object?

A class is a blueprint that you write with class. It only states which values (fields) and methods something has, and declaring a class does not create anything. An object is the real thing that new creates from that blueprint. Each time new runs, one object is created, and each object has its own values for the fields. A variable is not the object itself but something that refers to an object, so if it refers to no object, you cannot call a method of that object. In this lab you remove the class declaration and new, or replace new with null. Each time, the same code is compiled and run, so you can see what the compiler reports and how many Card objects are created.

Checked against real execution · Twelve combinations were compiled with javax.tools on JDK 21, run, and checked against the results; the number of objects created was measured by adding a counting line

Concepts in this lab

Classes and objects
A class is a blueprint that states which fields (data) and methods (behavior) something has, and an object is the actual thing that is created in memory (on the heap) by using new with that blueprint. An object is also called an instance. You can create many objects from one class, and each object has its own field values. A variable holds not the object itself but a reference to where the object is.
Field
A field is a variable that is declared inside a class. In class Card { String owner; }, owner is a field of type String. A field without static is also called an instance variable, and one is created separately for each object whenever a new object of that class is created. So even objects created from the same class can have different values in their fields. If you do not give a value, the field gets a default value, and for a field of a reference type it is null.
new
new is the operator that creates an object from a class. When new Card() runs, a new object is created according to the class Card, and a reference to that object becomes the value of the expression. Usually you store this reference in a variable, as in Card a = new Card();. This expression creates a new object each time it runs, so if new runs twice, two objects are created. Declaring a class does not create an object. An object is created only when new runs.
Default values of fields
These are the values that are automatically placed in the fields that are not given a value when an object is created. Numeric types such as int get 0, boolean gets false, and reference types such as String get null. These values are what you see until a field initializer or a constructor assigns a value. Local variables inside a method have no default value, so reading one without assigning a value to it is a compile-time error.
NullPointerException
This exception is thrown when code tries to call a method or read a field through a reference that is null. null means that there is no object that the reference refers to, and an object that does not exist cannot be asked to do anything, so the exception is thrown. Whether a value is null depends on the values at run time, so the compiler cannot catch it in advance and it shows up only when the code runs (it is an unchecked exception, so handling is not forced either). This exception is also thrown when null is passed to a method that is written not to accept null (such as Optional.of or Objects.requireNonNull). In places where a value may be absent, you make this exception less likely by checking for null first or by using Optional to show in the type that the value may be absent.

Try these

Running with the default settings

  1. The compiler reads the source from the top. First it reads the class Card declaration, and learns about the class Card, which has a value (field) named owner and a method named show. A class is a blueprint. It only states the blueprint, so this declaration alone does not create even one Card object.
  2. The compiler checks the line Card a = new Card(); The class Card exists, so new Card() can be used. The compiler does not run this line. It only checks it. The object is created at this line when the program runs.
  3. The compiler checks the lines a.owner = "Alice"; and a.show();. Card has an owner field and a show method, and variable a has a value stored in it, so there is no error.
  4. Compiling succeeds, and the Main class file to run is created. So far, 0 Card objects have been created. Compiling only checks the source and does not run new.
  5. You run it with java Main. Running starts at the main method. The class Card, the blueprint, is in the code, but 0 Card objects have been created so far.
  6. The line Card a = new Card(); runs. new Card() creates one object from the Card blueprint. Nothing has been stored in the owner of this object yet, so it is null, that is, empty.
  7. In the same line, = stores the created object in variable a. What is stored in variable a is not the object itself but a reference, which points to where the object is, so a now refers to that object.
  8. The line a.owner = "Alice"; runs. It stores "Alice" in the owner of the object that a refers to.
  9. The line a.show(); runs. The show method of the object that a refers to runs, reads the owner of that object, and prints "Alice's card". show is a method written in the blueprint, and the owner it reads is a value stored in the object.

Result of this run

Code

// Blueprint: Card has a value (field) named owner and a method named show
class Card {
    String owner;
    void show() {
        System.out.println(owner + "'s card");
    }
}

public class Main {
    public static void main(String[] args) {
        // Object: create a real card from the blueprint and store it in variable a
        Card a = new Card();
        a.owner = "Alice";
        a.show();
    }
}

Assumptions of this model

The compiler and the runtime environment are JDK 21.0.12.1 (javac 21). All twelve combinations were compiled with javax.tools from the same source as the code on the screen. The ones that compiled successfully were loaded with a new class loader and Main.main was run, and the results were checked against the captured standard output and exceptions. What was checked is whether compiling succeeded, the diagnostic code of the first error (compiler.err.cant.resolve.location and var.might.not.have.been.initialized, which are shown on the screen as the two error descriptions), the number of errors and the lines they occurred on, whether the Main class file was created, the output when run, and the kind of exception thrown during the run. The number of Card objects created was measured, not guessed. Only in the source used for checking, a line that increases a number each time an object is created (an instance initializer block) was added inside Card and the number was read after the run, and the checking script removes that one line when it compares with the code on the screen. The combinations that failed to compile were not run, so their count is 0. In the combination that stores null, the exception is a NullPointerException, and its message said that the variable was null when the code tried to store a value in a.owner. This exception occurs at the a.owner line, so the b lines below it did not run, and the number of objects was 0. The explanation in a step sentence that the owner of a newly created object is null before a value is stored was also confirmed by a separate run (new Card().show() printed the card of null). The documentation was checked by opening the Java Language Specification (Java SE 21) directly. That a class declaration defines a class follows section 8.1; that a field, and a field that is not static, is created anew for each instance whenever an instance is created, follows sections 8.3 and 8.3.1.1; that an object is a class instance or an array, that a reference is a pointer to an object, and that there is a null reference that points to nothing follows section 4.3.1; that a new object is created each time new is evaluated and its fields are filled with default values follows section 15.9.4; that the default value of a reference type is null follows section 4.12.5; that accessing an instance field, or calling an instance method, without an object (with null) throws a NullPointerException follows sections 15.11.1 and 15.12.4.4; and that reading a local variable in which no value was stored is a compile-time error follows the beginning of chapter 16. Section 15.26.1 (the order of evaluation of an expression that assigns to a field) could not be opened, and this lab does not rely on that order. The kinds of errors, the number of errors, and the lines they occur on are not set by the specification but were observed with this javac. When the class Card declaration was missing, one error occurred at each place where Card was used (two for a line with new, one for the type of the variable and one after new), and no separate error occurred on the lines that use the fields or methods of Card. For a variable in which no value was stored, an error occurred only on the line where it was first used. The code on the screen has the classes together in one file, with the package and the access modifiers left out. A class can have more than fields and methods, such as constructors, and there is also the matter of where in memory an object is placed, but this lab does not cover them. Where variables and objects are placed is covered in "In-depth lab: Stack and heap".

Previous lab: Methods · Next lab: References

Concepts in this lab

What this lab shows

Settings

Code


      
    

Diagram

Run comparison

Try these