Zero Day Java Guide 3 – Object-Oriented Programming

Introduction to Objects

Object-oriented programming (shortened to OOP) applies the concepts of real-world objects to programming. For example, think of an object near you: a pen, a chair, a computer. All of these things can be modeled in an OOP language like Java. In this section, we’re going to move from low-level, concrete structures like control flow, variables, and loops to more abstract structures like objects. We’ll talk about the principle behind OOP, then move on to classes and instances.

Object-Oriented Programming

As mentioned before, objects are the fundamental structure to OOP. For example, take a car. We know that a car has a chassis color, make, model, and other properties. With cars, we can also accelerate, decelerate, change gears, and perform other actions. In other words, each object has a state and a behavior. This is true in programming as well, except we call state “member variables” and behavior “methods”. We’ll talk more about member variables in the next section, but, conceptually, we use them to hold the state or properties of an object. We’ll also learn more about methods later, but they allow us to represent behaviors or actions of an object.

OOP languages like Java and C++ have many benefits over functional programming languages like C:

  1. Modularity and Reusability: We can easily reuse components from one project to another project. Suppose we were writing a zoo program and we created a representation of a whale. We can easily reuse this component in another project related to marine biology, for example. In fact, we can even have generic types where the component itself can be modularized to a particular type. In the case of a Java List, we can have a list of anything so it is considered a generic type.
  2. Software Design: We can design very complicated systems and applications using the concepts of OOP. For example, suppose we were writing a weather station app that pulled data from the web. Instead of having one giant main method that sets up the connection, pulls the weather data from the web, parses it, and displays it, we could write a software component, perhaps called WeatherFetcher, to pull data from the web and parse it. The main method would just display the data pulled down by the WeatherFetcher. This drastically simplifies our main method and makes it easier to debug.
  3. Encapsulation: We’ll talk more about encapsulation later, but the principle behind it is to only allow access to variables if it is absolutely necessary. In our previous example with the weather station app, our main method doesn’t need to know how the WeatherFetcher grabs data from the web, just that it does and returns us data in a known format that we can display. It prevents everyone from having access to everyone’s data. If some data is corrupt, it become very difficult to determine exactly what is corrupting data. With encapsulation, we can narrow down exactly what operations are modifying the data.

These just illustrate a few of the many benefits of OOP. In this section, we covered an introduction to OOP. We learned that objects have states and behaviors. Now we’re going to focus on how we can implement this concept of how OOP is implemented in Java.

Classes and Instances

Let’s go back to the car analogy. A car of a particular make and model is made in a factory in an assembly line and they all come out to be the same initially. Then they’re sold off to customers who may use them very frequently or very infrequently. After they’re sold, now each car might have a different number of miles on it, a different type of brake fluid, or even a different color!

We can shift this conceptual analogy to a more concrete analogy in Java using classes. A class is a blueprint for a conceptual object. Think of it like the car specifications given to the factory. In Java, we use a class to define all of the states and behaviors an object can have.

An instance is a realization of a class. For example, suppose we have a car variable called bobsCar . This variable might have started out the same as danielsCar , but, after some time in the program, they might have a different state.

For the next few sections, we’re going to be referring to this following Car class. We’ll be looking at all of the components of this class, and, by the end, we’ll know everything there is to know about the Car class, as well as how to create an instance of it and actually use it!

public class Car {
    private String color;
    private String make;
    private String model;
    private int gear;
    private boolean running;
    private int speed;

    public static final int MAX_SPEED = 200;
    public static final int MAX_GEAR = 9;

    private static int numberOfCars = 0;

    public Car() {
        this.color = "Black";
        this.make = "Unknown make";
        this.model = "Unknown model";
        this.gear = 1;
        this.running = false;
        this.speed = 0;
        numberOfCars++;
    }

    public Car(String color, String make, String model) {
        super();
        this.color = color;
        this.make = make;
        this.model = model;
    }

    public String getColor() {
        return this.color;
    }

    public String getMake() {
        return this.make;
    }

    public String getModel() {
        return this.model;
    }

    public int getGear() {
        return this.gear;
    }

    public boolean isRunning() {
        return this.running;
    }

    public int getSpeed() {
        return this.speed;
    }

    public static int getNumberOfCars() {
        return numberOfCars;
    }

    public void setMake(String make) {
        this.make = make;
    }

    public void setModel(String model) {
        this.model = model;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public void startCar() {
        if (!this.running) {
            this.running = true;
        }
    }

    public void stopCar() {
        if (this.running) {
            this.running = false;
            this.gear = 1;
            this.speed = 0;
        }
    }

    public void changeGear(int gear) {
        if (gear > 0 && gear <= MAX_GEAR) {
            this.gear = gear;
        }
    }

    public void accelerate(int amount) {
        int sum = this.speed + amount;
        if (this.running && sum <= MAX_SPEED) { 
            this.speed += amount; 
        } 
    } 

    public void decelerate(int amount) { 
        int diff = this.speed - amount; 
        if (this.running && diff >= 0) {
            this.speed -= amount;
        }
    }
}

Primitive and Reference Types

Several times in this text, I’ve made the point that something only applies to “primitive types” for example. Since we’re going to be looking into OOP, this section is going to look at what’s actually going on under the hood with our computer’s memory when we’re writing our program. We can imagine our computer’s member to be a giant table of little cells that have addresses and values.

java Reference-Types

As we see above, a primitive type is one whose value is stored directly in some block of memory. On the other hand, a reference type is one that has a pointer to another block of memory. A pointer is essentially a variable that holds the address of some other block of memory. In Java, any class is a reference type and the primitive types are the ints, booleans, chars, and other types of variables discussed in the variable type section.

Reference-Variables

This is significant because, depending on the kind of type, different operators and modifiers have a different effect. For example, remember back to the operators section. I said that the assignment operator, for primitive types, sets the value of one side to that of the other. However, for reference types, it creates an alias to the same block of memory. An alias is just another name for something. That means two variables now point to the same block of memory: changing one variable will alter the memory and any alias will do the same thing. This usually results in dangerous situations where we don’t know what is changing a variable might do to other alias. Imagine using a backup program having dangerous aliasing that just overwrites every backup with the most current one!

In this section, we learned about object-oriented programming and its benefits over functional programming languages. We can use OOP to abstractly represent and use software components. What makes these components objects are that they have a state and behavior. We’ll be referring back to that Car class for the next few sections. We also learned about what actually happens when we declare a primitive and reference variables. Primitive types are types whose values are stored directly in the block of memory; reference types are those that point to another block of memory where other values are stored. Pointers are just that: variables that hold a memory address to a cell, and therefore point to another block of memory.

Member Variables

In this section, we’re going to look at the first portion of the Car class that starts with what seems to be variable declarations. They’re not quite just variables, but, more specifically, they’re called member variables or fields. In our concept of an object, they represent the state or properties. We’ll learn how to declare and access these variables as well as the different types of access modifiers that we can apply on them and their effects on our member variables.

Declaring Member Variables

We can seen an example of declaring a member variable in the Car class:

private String color;

The first thing we need is to define a scope or access modifier, but we’ll talk more about these in later. Essentially, the private modifier makes the field only visible to the class it’s declared in. The next part looks just like a plain variable declaration: a type and a variable name. It’s really this simple to declare member variables! To summarize, we have a String member variable called color that represents the color of the particular car.

Accessing Member Variables

Similar to our predicament in previous sections, now that we have member variables, we actually want to do something with them. We need to access them in the class to check variables or perform basic operations. For example, let’s look at the the code that starts the car:

public void startCar() {
    if (!this.running) {
        this.running = true;
    }
}

This code is wrapped in what we call a method, but those will be discussed in the next section. In this block of code, however, we are checking to make sure the car is not running, then we are starting up the car by setting the running field to be true. This demonstrates both accessing a field and modifying it as well.

We see another keyword in that block of code however: this . The this keyword corresponds to the current instance of a class being acted upon. For example, suppose we have bobsCar. In our class, this.running will refer to bobsCar  or any other instance that we create of the Car class. Calling a method on an instance like bobsCar.startCar()  will change bobsCar’s boolean field running to true. Fields don’t have to be prefixed with the this  but there are cases, especially with constructors, that it is needed to differentiate a method parameter and the class’s member variable.

Access modifiers

Access-Modifiers

If we look at the statements that declare our member variables, we notice that before we actually state the type of those variables, we have this keyword private as well as the public keyword. We call these keywords access modifiers because using a particular one limits the scope of a field. The private modifier is the most restrictive access modifier since it restricts the visibility of a variable to just the class it’s defined in. On the other hand, the least restrictive access modifier is the public modifier since it allows the variable to be visible outside out of the class. We’ll see why this is generally a bad idea when we get to encapsulation. There also exists the very discouraged default modifier which allows access inside the package; the protected modifier only allows access to subclasses, but we’ll learn more about subclasses when we get to inheritance.

It turns out all variables in Java have a scope, or visibility. One of the simplest ways to determine variable scope is through the nearest set of curly braces. Take the following code snippet as an example:

int sum = 0;
for (int i = 1; i <= 100; i++) { 
    if (i % 5 == 0) { 
        sum += i; 
    } 
}

The above code sums up all of the numbers between 1 to a hundred that are divisible by five. Note that the scope of the sum  variable is the entire code snippet, which is why we can access it in the loop. On the other hand, the scope of the counter variable is limited to the for loop. Trying to access it outside the for loop would cause an error since that variable disappeared after the ending curly brace of the for loop.

Additional modifiers

public static final int MAX_SPEED = 200; 
public static final int MAX_GEAR = 9; 
private static int numberOfCars = 0;

In addition to the access modifiers that we have, we also have the static and final modifiers. The final modifier, for primitive types, makes the value of the variable immutable, or unable to be changed. In our case, we’re using it in conjunction with the static modifier to declare the constants MAX_SPEED  and MAX_GEAR . Reference variables, on the other hand, can still be modified, but you can’t re-assign the reference to another variable or new memory.

Static-Variables

A static variable essentially means that each instance of a particular class shares the same variable. They’re also called class variables since there’s only one per class, not one per instance. The above image illustrates this concept. This is why it makes sense to have a variable like numberOfCars . Each instance’s numberOfCars  variable points to the same thing. Conventionally, we access these static variables through the class name rather than an instance variable for this exact reason. We don’t need an instance to access a variable whose value is the same for all instances!

When we combine the static and final modifiers, we can declare variables to be constant. Final makes the variable immutable and static makes each instance share the same value. It wouldn’t be a constant if each instance had its own! By declaring the constant as public, we can allow other classes to access, or we can declare it as private and only allow our class to access it.

In this section, looked at member variables and how they’re used in our classes to hold the state of an object. We can simply declare one inside of our class like we would any other variable, except we have to be careful about access modifiers that we assign to it. Public means the variable can be seen by anyone and everyone, which is generally a bad idea. On the other hand, private means the variable is only visible within our own class. By declaring a variable to be static, we make sure that only one copy exists per class, not per instance, and, by making a variable final, we say that its value cannot be changed.

Methods

In this section, we’re going to talk about how to write methods. Other programs may call these subroutines or functions, but they’re essentially the same thing: a (parameterized) block of code that executes and may or may not return a value. They make our code easier to read and provide the foundation for the OOP principle of encapsulation that we’ll talk about later.

Simple Methods

To talk about methods, let’s first define a very simple method that takes in a number and returns the square:

public double squareNum(double x) { 
    return x * x; 
}

Breaking down this syntax, we first need an access modifier to tell us the visibility of this method. Then we can have a return type or not (denoted by void). In our case, this method’s return type is double. Next we have the name of the method. Method names follow the same convention as variable names: camelCase. Afterwards comes any input parameters or empty parentheses if the method doesn’t accept any parameters. The code block comes next; it’s a bit of code that is separate from any other method. However, if we said that the method had a return type, we need give back that return type by saying “return “ then a variable or value that corresponds to the return type. Methods can be more or less complicated than this one. Take a look at the following methods:

public void printSomething(String something) { … } 
private double hypotenuse(double a, double b) { … } 
public void sayHello() { … }

All of the above methods have different method signatures. The signature of a method comes from the line that declares it and includes the access modifier, return type, name, and the types of the parameters it takes in the order that they appear.

Method Parameters

As we’ve seen before, methods can take in any number of parameters of different types. Once we have a method with parameters, we can treat those parameters like any other variable and perform operations on them like in the previous squareNum(double)  method. When we’re calling a method, we can pass in either a variable or the value itself. Let’s see how we can actually call the method in code:

int val = 5; 
System.out.println(squareNum(val)); // should give us 25 

System.out.println(squareNum(5)); // should also give us 25

Both of the above ways work fine. The first approach uses a variable to hold the value then passes in that variable; the second approach simply substitutes the value of that variable to the method. Inside of the method, x  has a value of 5 in both cases. For primitive types, the value is only copied, so any changes to the parameter won’t affect the variable that we passed into the method. This is called pass-by-value. On the other hand, if we were using a mutable reference type, like a Java List or StringBuilder, then changes done to the parameter would be reflecting in the variable. This is called pass-by-reference because the parameter points to the same memory that the variable does so changing one will affect the other.

Constructors

When we instantiate a new object, all of the fields, whether public or private, of that instance are set to the default value. For primitive types, this isn’t an issue. However, the default value of any reference type is what we call null. This means that there isn’t any computer memory for that reference variable to point anywhere. Trying to do anything with a variable that’s null is going to result in Java throwing an error. To prevent this, we have special methods called constructors that allow us to initialize an object’s fields with values right when we instantiate it. Let’s look at a few of Car’s constructors:

public Car() { 
    this.color = "Black"; 
    this.make = "Unknown make"; 
    this.model = "Unknown model"; 
    this.gear = 1; 
    this.running = false; 
    this.speed = 0; 
    numberOfCars++; 
} 

public Car(String color, String make, String model) { 
    super(); 
    this.color = color; 
    this.make = make; 
    this.model = model; 
}

We notice that there are two constructors: one without parameters and one with parameters. The constructor without parameters is called the default constructor, and the one with parameters is called the parameterized constructor. These special methods essentially initialize all of the fields when a new instance is created like so:

Car car = new Car(); 
Car stingray = new Car(“Red”, “Chevrolet”, “Corvette Stingray");

The first instance of Car is a black car of an unknown make and model. It’s in first gear, isn’t running, and has a speed of zero. The second instance uses the parameterized constructor so the color, make, and model are both set immediately; hence, the second instance will have everything the first instance has (thanks to the call to super() that we’ll discuss later) with the exception of the color, make, and model.

Encapsulation

Encapsulation

We use many devices today that we don’t fully understand or have control of. Think of a car or computer. We don’t have direct access to the fuel injector or to the transistors on our computer’s chips. Instead, we only have access to some portions of the computer and car. The principle is the same with encapsulation, also called data hiding. To implement the concept in code, we make all of the fields private so they can’t be accessed by the outside world. Instead, we use methods called getters and setters allow access to these private variables. One reason that we can’t let others access to a class’s data is so that users can’t set the data values to invalid values, like setting the gear to a negative value.

public void changeGear(int gear) { 
    if (gear > 0 && gear <= MAX_GEAR) {
        this.gear = gear;
    }
}

We can see in the above code that we only set this Car’s gear to be method parameter’s gear only if it falls within the valid bounds of what we define to be a Car’s gear.

Getters and Setters

In the previous section, we learned about encapsulation and data hiding. Let’s take a look at an example of getters and setters from our Car class:

public String getMake() {
    return this.make;
}

public String getModel() {
    return this.model;
}

public void setMake(String make) {
    this.make = make;
}

public void setModel(String model) {
    this.model = model;
}

You can see why we call them getters and setters! The getter is used to retrieve the value of the field, and the setter is used to set the value of the field. We can use getters and setters to do any kind of input validation before changing the internal state or any nice formatting before outputting the internal state.

In this section, we discussed methods. We can use them as subroutines to segment our code. They can return values or void, meaning they don’t return a value, and they can have any number of parameters. We also learned how we can initialize fields using constructors. In addition, we discussed the important concept of OOP: encapsulation. Finally, we discussed getters and setters, an example of how we can implement encapsulation. They can be used to retrieve the value of a field and set the value of a field.