Object Oriented Programming

June 13, 2020 Java 14 min

We'll all heard that Java is an Object Oriented Programming language, but what does tha really mean? It means that everything in Java is part of an object, sort of like a container, that holds variables and methods which get executed. These objects can interact with one another to perform various tasks created by the programmer.

Classes

Classes are created by the programming as blueprints from which objects are then made. The basic definition for a class in Java is the following:

public class Name {
    // All code in here
}

Technically, classes can also be private or protected but these (specifically private) are much rarer because it limits where a class can be accessed. The creation of an object is done using the new keyword as such:

new Name();

What parameters this takes in and how to use this new object is explained in further sections.

It is also important to note that classes act as data types, meaning that to store this new object, one has to declare a variable of the same type:

Name name = new Name();

Instance Variables

Classes can contain various variables to store data within an instance of a class. Each of these objects will have a copy of the variable with its own value (unless it is declared static, as described below). As an example, let's create a Point class which stores an x and y coordinate.

public class Point() {
  private int x;
  private int y;
}

Instance variables should be declared as private and only accessed through getters and setter (described below) for encapsulation. The purpose of these methods is to make it easier to change the internals of a class without affecting the other parts of the program that use it. For example, in our case, if we change the two variables to the short type, we don't want other classes to have to be changed. Instead, this change can simply be done in the getter and setter methods. Writing these methods is described below.

Methods

Apart from instance variables, classes can also have various methods to interact with the internal components. For example, let's create a method called translate which will move the point in this class by a specified x and y distance.

public void translate(int dx, int dy) {
    x += dx;
    y += dy;
}

Alternatively, let's create a method that find the difference between two points and returns it.

public double distanceTo(int x2, y2) {
    return Math.sqrt(Math.pow((x2-x), 2), Math.pow((y2-y), 2));
}

This method can calculate the distance from the location of this object's x and y coordinates, to a separate set of provided coordinates.

Getters and Setters

As explained before, methods should be used to retrieve and set variables within a class, so let's do that for the Point class.

public void setX(int x) {
    this.x = x;
}

public void setY(int y) {
    this.y = y;
}

public int getX() {
    return x;
}

public int getY() {
    return y;
}

What's interesting here is the use of the keyword this. Java will by default assume when assigning variables to use the instance variables found within this class, so the use of this in this example isn't strictly necessary. However, because it can get confusing to say x = x, one can use the this keyword to indicate to the Java interpreter that it should refer to the instance variable directly, and not the parameter. this actually refers to the very instance of this class which means it can be passed around to other method as a reference for them to use.

Constructors

Now that we have these instance variables and methods, we need to create an instance of this class, called an object. To do so, we can define a constructor (or else the default Object contructor is used). In the example below, two constructors are define: one that takes no parameters and creates a point at (0, 0), and another that takes in the x and y coordinates as parameters.

public Point() {
    x = 0;
    y = 0;
}

public Point(int x, int y) {
    this.x = x;
    this.y = y;
}

Now, if we call new Point(3, 4), a new instance of the Point class will be created and the two variables in it will contain the integer values 3 and 4. An even better way of writing these constructors is by calling one from the other:

public Point() {
    this(0, 0);
}

public Point(int x, int y) {
    this.x = x;
    this.y = y;
}

This is better because it promotes code reuse and makes it easier to change other aspects of the class while keeping up with new requirements that may be added.

Complete Example

Combining all of this together, we get a Point class which looks like so:

public class Point() {
    private int x;
    private int y;

    public Point() {
        this(0, 0);
    }

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public void setX(int x) {
        this.x = x;
    }

    public void setY(int y) {
        this.y = y;
    }

    public int getX() {
        return x;
    }

    public int getY() {
        return y;
    }
}

In practice, let's use it the program's main method.

public class Main() {
    public static void main(String[] args) {
        Point p1 = new Point();
        Point p2 = new Point(3, 4);
        Point p3 = new Point(1, 2);
        p3.setX(5);
        p3.setY(5);

        // Testing
        System.out.println(p1.getX() + " " + p1.getY());
        System.out.println(p2.getX() + " " + p2.getY());
        System.out.println(p3.getX() + " " + p3.getY());
        System.out.println(p1.distanceTo(p3.getX(), p3.getY()));
    }
}

This will output:

0 0
3 4
5 5
7.0710678118654755

Inheritance

Now that we have a class, suppose we want to extend this point into a 3 dimensional world. Well, because of inheritance, we don't actually have to make a complete new class and define all the variables. Instead, we can build off of this already made Point class with inheritance. This will create a class that has the same properties (it will inherit the methods and variables) with some extra additions.

public class Point3D extends Point {
    private int z;

    public Point() {
        this(0, 0, 0);
    }

    public Point(int x, int y, int z) {
        super(x, y);
        this.z = z;
    }

    public void setZ(int z) {
        this.z = z;
    }

    public int getZ() {
        return z;
    }
}

We do this with the use of the extends keyword. Keep in mind that a class can only extend one class, so you cannot, for example, do extends Point, Space. We also see the use of the super keyword. This will call the original Point class's constructor with the x and y parameters. If used with dot notation as in super.<something>, it can access the non-inherited version of methods.

There are actually many different forms of inheritance, all based off of this prinicple.

Forms of inheritance in Java

Static Keyword

It's now time to examine the static keyword. All it does is make an instance variable or method have exactly one copy. This means that instead of the normal structure where every object will have a separate version of a variable, static variables will only ever exist and be created once. Thus, modifying a value of a static variable in one object will modify it in all others of the same type. This also persists across object creation and removal.

In terms of methods, a static method may be called without ever creating an instance of an object. This is handy for classes like a Utils utility class which may have simple functions that are used often. It would be silly to create a new instance of this class every time it is necessary, so instead one can use static methods as such:

public class Utils {
    public static int dotProduct(...) {
        // Code to calculate dot product
    }
}

Polymorphism

Another cool feature of Java is called Polymorphism. In literal terms, it means "of many shapes" which accurately describes the class structure. The two main forms of polymorphism are method overloading and method overriding.

Method overloading

In method overloading, two methods share a name but have different parameters. The interpreter then picks which one to call based on the parameters provided. Here are two methods that add numbers, but one takes two integers and the other takes three.

public int add(int a, int b) {
    return a + b;
}

public int add(int a, int b, int c) {
    return a + b + c;
}

Method overriding

In a single class, methods cannot have identical names and parameters, but this is allowed between subclasses. For example, let's create a Greeting method and a SpecialGreeting method which both have the method greet that prints a String. The SpecialGreeting method will extend the Greeting method and change what is outputted.

public class Greeting {
    public void greet() {
        System.out.println("Hey there!");
    }
}

public class SpecialGreeting {
    public void greet() {
        System.out.println("Greetings your Majesty!");
    }
}

This can then be used cleanly with another concept called Upcasting, described below.

Upcasting

Upcasting is a term for a class being automatically casted to a common superclass. It can be used to store a subclass in a variable of a superclass type. This is extremely useful when there are multiple different subclasses that all do slightly different things, but share a common superclass. In this case, we can store and use an array of Greetings as such:

Greeting[] greetings = {
    new Greeting(),
    new SpecialGreeting()
};
greetings[0].greet();
greetings[1].greet();

This works and will output the below text because Java automatically "converts" the SpecialGreeting into a Greeting, but still uses the SpecialGreeting's variables and methods (including any overrides).

Hey there!
Greetings your Majesty!

Abstraction

Finally, we move on to abstractness. Abstracting a class allows it to have abstract methods — methods that don't run any code, acting as templates. This further extends the template nature of classes.

Abstract Classes

An abstract class can contain normal instance variables and methods, as well as abstract methods.

public abstract class AbstractTest {
    private int variable;
    public void method() {
      System.out.println("Yay!");
    }

    // Notice the semi-colon
    public abstract void run();
}

One can then subclass this class and has to define all of the abstract methods. Java doesn't allow you to create a standalone instance of an abstract class, but because of upboxing it allows you to store objects which inherit it into the abstract class's type.

Interfaces

A step further into abstraction, an interface contains only constant variables and abstract methods. It, like an abstract class, cannot be instantiated on its own. Unlike a class though, many interfaces can be used using the interface keyword as such:

public interface Disposable {
    public abstract void dispose();
}

public WaterBottle implemnets Disposable {
  public void dispose() {
    // Must be implemented!
  }
}