1. Overview

In this article, we'll look into Object-Oriented Programming (OOP) concepts in Java. We'll discuss classes, objects, abstraction, encapsulation, inheritance, and polymorphism.

2. Classes

Classes are the starting point of all objects, and we may consider them as the template for creating objects. A class would typically contain member fields, member methods, and a special constructor method.

We'll use the constructor to create objects of the class:

public class Car {
 
    // member fields
    private String type;
    private String model;
    private String color;
    private int speed;
 
    // constructor
    public Car(String type, String model, String color) {
        this.type = type;
        this.model = model;
        this.color = color;
    }
     
    // member methods
    public int increaseSpeed(int increment) {
        this.speed = this.speed + increment;
        return this.speed;
    }
     
    // ...
}

Note that a class may have more than one constructor. We can read more about the classes in our classes article.

3. Objects

Objects are created from classes and are called instances of the class. We create objects from classes using their constructors:

Car veyron = new Car("Bugatti", "Veyron", "crimson");
Car corvette = new Car("Chevrolet", "Corvette", "black");

Here, we've created two instances of the class Car. Read more about them in our objects article.

4. Abstraction

Abstraction is hiding complexities of implementation and exposing simpler interfaces.

If we think about a typical computer, one can only see the external interface, which is most essential for interacting with it, while internal chips and circuits are hidden from the user.

In OOP, abstraction means hiding the complex implementation details of a program, exposing only the API required to use the implementation. In Java, we achieve abstraction by using interfaces and abstract classes.

We can read more about abstraction in our abstract class and interface articles.

5. Encapsulation

Encapsulation is hiding the state or internal representation of an object from the consumer of an API and providing publicly accessible methods bound to the object for read-write access. This allows for hiding specific information and controlling access to internal implementation.

For example, member fields in a class are hidden from other classes, and they can be accessed using the member methods. One way to do this is to make all data fields private and only accessible by using the public member methods:

public class Car {

    // ...
    private int speed;

    public int getSpeed() {
        return color;
    }

    public void setSpeed(int speed) {
        this.speed = speed;
    }
    // ...
}

Here, the field speed is encapsulated using the private access modifier, and can only be accessed using the public getSpeed() and setSpeed() methods. We can read more about access modifiers in our access modifiers article.

6. Inheritance

Inheritance is the mechanism that allows one class to acquire all the properties from another class by inheriting the class. We call the inheriting class a child class and the inherited class as the superclass or parent class.

In Java, we do this by extending the parent class. Thus, the child class gets all the properties from the parent:

public class Car extends Vehicle { 
    //...
}

When we extend a class, we form an IS-A relationship. The Car IS-A Vehicle. So, it has all the characteristics of a Vehicle.

We may ask the question, why do we need inheritance? To answer this, let's consider a vehicle manufacturer who manufactures different types of vehicles, such as cars, buses, trams, and trucks.

To make the work easy, we can bundle the common features and properties of all vehicle types into a module (a class in case of Java). And we can let individual types inherit and reuse those properties:

public class Vehicle {
    private int wheels;
    private String model;
    public void start() {
        // the process of starting the vehicle
    }
    
    public void stop() {
        // process to stop the vehicle
    }
    
    public void honk() { 
        // produces a default honk 
    }

}

The vehicle type Car will now inherit from the parent Vehicle class:

public class Car extends Vehicle {
    private int numberOfGears;

    public void openDoors() {
        // process to open the doors
    }
}

Java supports single inheritance and multilevel inheritance. This means a class cannot extend from more than one class directly, but it can use a hierarchy:

public class ArmoredCar extends Car {
    private boolean bulletProofWindows;
    
    public void remoteStartCar() {
        // this vehicle can be started by using a remote control
    }
}

Here, the ArmouredCar extends Car, and Car extends Vehicle. So, ArmouredCar inherits properties from both Car and Vehicle.

While we inherit from the parent class, a developer could also override a method implementation from the parent. This is known as method overriding.

In our above example of the Vehicle class, there is the honk() method. The Car class extending the Vehicle class can override this method and implement in the way it wants to produce the honk:

public class Car extends Vehicle {  
    //...

    @Override
    public void honk() { 
        // produces car-specific honk 
    }
 }

Note that this is also termed a runtime polymorphism, as explained in the next section. We can read more about inheritance in our Java inheritance and inheritance and composition articles.

7. Polymorphism

Polymorphism is the ability of an OOP language to process data differently depending on their types of inputs. In Java, this can be the same method name having different method signatures and performing different functions:

public class TextFile extends GenericFile {
    //...
 
    public String read() {
        return this.getContent()
          .toString();
    }
 
    public String read(int limit) {
        return this.getContent()
          .toString()
          .substring(0, limit);
    }
 
    public String read(int start, int stop) {
        return this.getContent()
          .toString()
          .substring(start, stop);
    }
}

In this example, we can see that the method read() has three different forms with different functionalities. This type of polymorphism is static or compile-time polymorphism and is also called method overloading.

There is also runtime or dynamic polymorphism, where the child class overrides the parent's method:

public class GenericFile {
    private String name;
 
    //...
 
    public String getFileInfo() {
        return "Generic File Impl";
    }
}

A child class can extend the GenericFile class and override the getFileInfo() method:

public class ImageFile extends GenericFile {
    private int height;
    private int width;
 
    //... getters and setters
     
    public String getFileInfo() {
        return "Image File Impl";
    }
}

Read more about polymorphism in our polymorphism in Java article.

8. Conclusion

In this article, we learned about the basic fundamental concepts of OOP with Java.

The code samples in this article are available over on Github.