For the complete documentation index, see llms.txt. This page is also available as Markdown.

2. Defining and Using Classes

Static vs. Non-Static Methods

Static Methods

All code in Java must be part of a class (or something similar to a class, which we'll learn about later). Most code is written inside of methods. Let's consider an example:

public class Dog {
    public static void makeNoise() {
        IO.println("Bark!");
    }
}

If we try running the Dog class, we'll simply get an error message:

$ java Dog
Error: Main method not found in class Dog, please define the main method as:
       public static void main(String[] args)

The Dog class we've defined doesn't do anything. We've simply defined something that Dog can do, namely make noise. To actually run the class, we'd either need to add a main method to the Dog class, as we saw in chapter 1.1. Or we could create a separate DogLauncher class that runs methods from the Dog class. For example, consider the program below:

public class DogLauncher {
    void main() {
        Dog.makeNoise();
    }
}
$ java DogLauncher
Bark!

A class that uses another class is sometimes called a "client" of that class, i.e. DogLauncher is a client of Dog. Neither of the two techniques is better: Adding a main method to Dog may be better in some situations, and creating a client class like DogLauncher may be better in others. The relative advantages of each approach will become clear as we gain additional practice throughout the course.

Instance Variables and Object Instantiation

Not all dogs are alike. Some dogs like to yap incessantly, while others bellow sonorously, bringing joy to all who hear their glorious call. Often, we write programs to mimic features of the universe we inhabit, and Java's syntax was crafted to easily allow such mimicry.

One approach to allowing us to represent the spectrum of Dogdom would be to create separate classes for each type of Dog.

As you should have seen in the past, classes can be instantiated, and instances can hold data. This leads to a more natural approach, where we create instances of the Dog class and make the behavior of the Dog methods contingent upon the properties of the specific Dog. To make this more concrete, consider the class below:

As an example of using such a Dog, consider:

When run, this program will create a Dog with weight 20, and that Dog will soon let out a nice "bark. bark.".

Some key observations and terminology:

  • An Object in Java is an instance of any class.

  • The Dog class has its own variables, also known as instance variables or non-static variables. These must be declared inside the class, unlike languages like Python or Matlab, where new variables can be added at runtime.

  • The method that we created in the Dog class did not have the static keyword. We call such methods instance methods or non-static methods.

  • To call the makeNoise method, we had to first instantiate a Dog using the new keyword, and then make a specific Dog bark. In other words, we called d.makeNoise() instead of Dog.makeNoise().

  • Once an object has been instantiated, it can be assigned to a declared variable of the appropriate type, e.g. d = new Dog();

  • Variables and methods of a class are also called members of a class.

  • Members of a class are accessed using dot notation.

Constructors in Java

As you've hopefully seen before, we usually construct objects in object oriented languages using a constructor:

Here, the instantiation is parameterized, saving us the time and messiness of manually typing out potentially many instance variable assignments. To enable such syntax, we need only add a "constructor" to our Dog class, as shown below:

The constructor with signature public Dog(int w) will be invoked anytime that we try to create a Dog using the new keyword and a single integer parameter. For those of you coming from Python, the constructor is very similar to the __init__ method.

Array Instantiation, Arrays of Objects

In Java, Arrays are instantiated using the new keyword. For example:

Similarly, we can create arrays of instantiated objects in Java, e.g.

Observe that new is used in two different ways: Once to create an array that can hold two Dog objects, and twice to create each actual Dog.

Class Methods vs. Instance Methods

Java allows us to define two types of methods:

  • Class methods, a.k.a. static methods.

  • Instance methods, a.k.a. non-static methods.

Instance methods are actions that can be taken only by a specific instance of a class. Static methods are actions that are taken by the class itself. Both are useful in different circumstances. As an example of a static method, the Math class provides a sqrt method. Because it is static, we can call it as follows:

If sqrt had been an instance method, we would have instead the awkward syntax below. Luckily sqrt is a static method so we don't have to do this in real programs.

Sometimes, it makes sense to have a class with both instance and static methods. For example, suppose want the ability to compare two dogs. One way to do this is to add a static method for comparing Dogs.

This method could be invoked by, for example:

Observe that we've invoked using the class name, since this method is a static method.

We could also have implemented maxDog as a non-static method, e.g.

Above, we use the keyword this to refer to the current object. This method could be invoked, for example, with:

Here, we invoke the method using a specific instance called d.

Static Variables

It is occasionally useful for classes to have static variables. These are properties inherent to the class itself, rather than the instance. For example, we might record that the scientific name (or binomen) for Dogs is "Canis familiaris":

Static variables should be accessed using the name of the class rather than a specific instance, e.g. you should use Dog.binomen, not d.binomen.

While Java technically allows you to access a static variable using an instance name, it is bad style, confusing, and in my opinion an error by the Java designers.

Java Before and After 2025

Before September 2025, Java required a fair amount of ceremony just to print something. All code had to live inside a class, printing was done with System.out.println, and the main method had to be declared with the full incantation public static void main(String[] args). Compare the old and new versions of Hello World:

Java 25 made the simpler form official: you can now write void main() with no enclosing class, and use IO.println for printing. Many older 61B resources use the pre-2025 syntax.

In case you're curious about the old style main declaration, if we break it into pieces, we have:

  • public: Indicates that this class or method can be used by any class (more on this in a later chapter).

  • static: It is a static method, not associated with any particular instance.

  • void: It has no return type.

  • main: This is the name of the method.

  • String[] args: This is a parameter that is passed to the main method.

Command Line Arguments (extra content)

Since main is called by the Java interpreter itself rather than another Java class, it is the interpreter's job to supply these arguments. They refer usually to the command line arguments. For example, consider the program ArgsDemo below:

This program prints out the 0th command line argument, e.g.

In the example above, args will be an array of Strings, where the entries are {"these", "are", "command", "line", "arguments"}.

Lists in Java

In programming languages, a list is an ordered sequence of objects, often represented by comma-separated values in-between brackets.

For example, in Python, one can create an empty list and append to it as follows:

Let's try to write the equivalent Java program. A natural first attempt is:

If you try to compile the code above, the compiler will complain that it "can't resolve symbol List". The simplest way to resolve this is to add an import statement:

Unfortunately this code STILL doesn't compile. This time the compiler complains that "List is abstract, cannot be instantiated." The issue is that we need to pick a specific type of list. The most common choice is an ArrayList.

This compiles and prints [a, b, c]. Note that Java's method for appending is called add, not append.

Note: This code is written in a very old school style from the Java 5.0 days (circa 2002). We'll see more modern Java code (e.g. List<String> L in the next chapter).

Abstract Data Types vs. Concrete Implementations

Let's return to the distinction between a List and an ArrayList.

In introductory Python code, the programmer simply instantiates a list using [] syntax. A list is a list is a list.

By contrast, in Java, a java.util.List is an "Abstract Data Type". Any List is guaranteed to have all of the operations listed at https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/util/List.html.

Each implementation, e.g. java.util.LinkedList or java.util.ArrayList is a "Concrete Implementation". The underlying implementations may be radically different, but they have the exact same behavior from the perspective of the user of the List, i.e. from above the abstration boundary.

Why bother having multiple implementations of the same idea? Two big reasons:

  • Performance. Different implementations are fast at different things. For example, a LinkedList is very fast at removing its front item, while an ArrayList is very slow at it.

  • Extra operations. Some implementations offer operations beyond the basic guarantee. For example, the Stack implementation adds push and pop.

We'll explore these concepts in much more detail over the coming chapters.

Imports in Java (extra content)

Note: Imports work a bit differently in Java than in Python. It's not strictly necessary to import something in order to use it in Java; an alternate approach is to use the full canonical name of the thing you're trying to use. That is, rather than writing:

You could instead write:

Last updated

Was this helpful?