Skip to main content

Section 2.2 First look at classes

Subsection 2.2.1 Key parts of a class

So here is an example of a very simple "letter grade" class:
public class LetterGrade {
    public String letter;
    public double points;

    public LetterGrade(String letter, double points) {
        this.letter = letter;
        this.points = points;
    }

    public boolean countsForCredit() {
        return letter.equals("W");
    }
}
This is really not a great class, but it will do as a first example. So what all do we have here?
  • A class is declared with the keyword class. It is followed by the name for the class, which is customarily in "upper camel case" form. Then the customary curly braces demarcate the contents of the class definition.
  • Within the class definition’s body we encounter three kinds of information: variable declarations, function (method) declarations or definitions, and inner class definitions.
  • Typically the first part of the class definition contains declarations of the variables we call fields. They come in two flavors, non-static and static, depending on whether they are specific to each object of the class or whether they are specific to the class. The ones in our example are non-static, or object-specific. We often call these member variables or instance variables. These are variables that are visible to all methods of the object and last for the lifetime of the object.
  • In most cases what follows is the constructor, on line 5 in our example. The constructor is a special method that is called when a new object is created. Its job is to initialize the object, typically by assigning values to the object’s fields and doing any other needed prep work. In our example, this constructor is provided with two parameters in the form of a letter string and a points value. It then assigns these values to the fields of the object, via assignments like this.letter = letter. In this assignment, the left-hand-side refers to the fieldletter declared in line 2, while the right-hand-side refers to the parameterletter that was passed in to the constructor in line 5. Parameters are short-lived variables that cease to exist the moment the function exits. By storing the values in the fields instead we can ensure that other object methods will have access to them later.
  • Lastly, we have methods, an example of which we can see in line 10. It is a public method (we will discuss this later), that returns a boolean value, it is called countsForCredit, and takes no parameters. This method calls a method of the letterString object, or to be more precise the String object stored in the letter field. That method, equals, checks to see if the two strings are the same string.
Let’s take a brief look at some code that uses this class:
LetterGrade g1 = new LetterGrade("A", 4.00);
LetterGrade g2 = new LetterGrade("W", 0.00);
g1.letter;   // This is "A"
g2.letter;   // This is "W"
g1.points;   // This is 4.00
g1.countsForCredit(); // This is true
g2.countsForCredit(); // This is false
We start by creating two objects of this class, and store them in the variables g1 and g2. We called the constructor to accomplish that, using the new keyword.
Then we access the fields of those objects, via the "dot notation" object.property. Get used to this notation, because you will be using it a lot. If you are coming from a C or C++ background, you should think of this more like the arrow notation in those languages.
Notice that each object has its own value for letter (and also for points for that matter). This is the essential feature of the member/instance variables: The values of those fields travel along with the object instance, wherever it goes. When we call an object’s method, like the countsForCredit in our example, the body of that method executes and it can reference these field values.

Subsection 2.2.2 Class example: Linked Lists

Let’s take a look at another and slightly more complicated example, that of a "singly" linked list. If you are not familiar with this structure, you should read up on it before continuing here, as my explanation below will probably not do it justice.
A linked list is comprised of nodes. Each node contains a value but also points to the next node on the list. That way the nodes are chained to each other, from some start node towards a final node, which points to nothing; we represent that "nothing" in Java with the keyword null.
null can be used in any place where an object is expected. Calling a method on this "null" object will result in a NullPointerException.
Null values are a bit of a pain, alright a huge pain, and we’ll see later ways to avoid them when possible. But for now we’ll just learn to live with them.
So here is how our linked list class might look like. I’m calling it LList instead of the more appropriate name of LinkedList because there is a built-in linked list class in Java, and the names would conflict. We’ll discuss the implementation after the code.
  public class LList {
    private Node head = null;

    public void insertFront(int value) {
        head = new Node(value, head);
    }

    public boolean contains(int value) {
        Node curr = head;
        while (curr != null) {
            if (curr.value == value) return true;
            curr = curr.next;
        }
        return false;
    }

    private static class Node {
        private int value;
        private Node next;

        Node(int value, Node next) {
            this.value = value;
            this.next = next;
        }

        Node(int value) {
            this(value, null);
        }
    }
}
This class starts innocently enough, with line 2 declaring the initial head node to be null.
What is Node you ask? Well, that is a private inner class, that only LList knows about, defined starting on line 17. The keyword static there means that it is the same class for all LList objects. Note that it has two fields, one to hold an integer value and one to point to the next Node in the list. And two constructors, one that takes both a value and a next node, and one that takes only a value. This second constructor is a bit unusual. It uses the funky this(...) syntax to call the other constructor.
When possible, make sure all your constructors eventually call the same constructor, the one with the most parameters. This way there is a single "point of entry" into the class, and we can do any needed checks there, and only once.
Our LList class has only two methods, to keep the exposition simple. the insertFront method inserts a new value at the front. It has a simple job, it creates a new Node from the provided value, makes that node point to the old head, then sets it as the new head. You should draw a picture to make sure you understand how that worked out.
The other method is perhaps more interesting: It will search through the list for a provided value, and if it finds it returns true, otherwise returns false. It uses a local variable curr to point to the currently examined node. Then it simply checks that node and if needed advances to the next, until it reaches null.
You might like to take some time here and write other methods for the LList for practice. For example:
  1. A method that returns the size of the list.
  2. A method that inserts a new value at the end.
  3. A method that inserts a new value at index i.
  4. A method that removes the node at index i and returns its value.

Checkpoint 2.2.1.

Choose and correctly order some of the following, to produce a function that computes the size of a list. You may not need to use all the provided blocks.

Checkpoint 2.2.2.

Choose and reorder the blocks below, to form a function that inserts a new value at the end of a list. You may not need to use all the provided blocks.