Java for Beginners: Object-Oriented Programming Concepts

Explore Java's object-oriented programming features including classes, objects, inheritance, polymorphism, and encapsulation.

Java for Beginners: Object-Oriented Programming Concepts

Welcome to the second part of our "Java for Beginners" series! In our previous post, we covered the basics of Java and wrote our first program. Today, we'll explore one of Java's most powerful features: Object-Oriented Programming (OOP).

Object-oriented programming is a programming paradigm that uses "objects" to model real-world entities. Java was designed from the ground up with OOP principles in mind, making it an excellent language for learning these concepts.

The Four Pillars of OOP

Java implements the four fundamental principles of object-oriented programming:

  1. Encapsulation: Bundling data and methods that operate on that data within a single unit (class)
  2. Inheritance: Creating new classes that inherit properties and behaviors from existing classes
  3. Polymorphism: The ability of different objects to respond to the same method in different ways
  4. Abstraction: Hiding complex implementation details and showing only the necessary features

Let's explore each of these concepts in detail.

Classes and Objects

In Java, a class is a blueprint for creating objects. It defines the properties (attributes) and behaviors (methods) that objects of that class will have.

Creating a Class

Here's a simple example of a Car class:

public class Car {
    // Attributes (instance variables)
    private String make;
    private String model;
    private int year;
    private double fuelLevel;
    
    // Constructor
    public Car(String make, String model, int year) {
        this.make = make;
        this.model = model;
        this.year = year;
        this.fuelLevel = 100.0; // Full tank by default
    }
    
    // Methods
    public void drive() {
        if (fuelLevel > 0) {
            System.out.println("The " + year + " " + make + " " + model + " is driving.");
            fuelLevel -= 10;
        } else {
            System.out.println("Out of fuel!");
        }
    }
    
    public void refuel() {
        fuelLevel = 100.0;
        System.out.println("The car has been refueled.");
    }
    
    // Getters and setters
    public String getMake() {
        return make;
    }
    
    public String getModel() {
        return model;
    }
    
    public int getYear() {
        return year;
    }
    
    public double getFuelLevel() {
        return fuelLevel;
    }
}

Creating Objects

Once we have a class, we can create objects (instances) of that class:

public class Main {
    public static void main(String[] args) {
        // Create two Car objects
        Car myCar = new Car("Toyota", "Corolla", 2020);
        Car friendsCar = new Car("Honda", "Civic", 2019);
        
        // Use the objects
        myCar.drive();
        friendsCar.drive();
        
        System.out.println("My car's fuel level: " + myCar.getFuelLevel());
        myCar.refuel();
        System.out.println("After refueling: " + myCar.getFuelLevel());
    }
}

Encapsulation

In our Car class example, we've already implemented encapsulation by:

  1. Making the attributes private so they can't be accessed directly from outside the class
  2. Providing public methods (getters and setters) to access and modify these attributes

This protects the internal state of our objects and ensures that any changes to the data happen in a controlled manner.

Inheritance

Inheritance allows us to create new classes that reuse, extend, and modify the behavior defined in other classes. Let's create an ElectricCar class that inherits from our Car class:

public class ElectricCar extends Car {
    private int batteryCapacity;
    
    public ElectricCar(String make, String model, int year, int batteryCapacity) {
        super(make, model, year); // Call the parent class constructor
        this.batteryCapacity = batteryCapacity;
    }
    
    // Override the drive method
    @Override
    public void drive() {
        System.out.println("The " + getYear() + " " + getMake() + " " + getModel() + 
                           " is driving silently on electricity.");
    }
    
    // Override the refuel method
    @Override
    public void refuel() {
        System.out.println("The electric car is charging.");
    }
    
    // New method specific to ElectricCar
    public void displayBatteryInfo() {
        System.out.println("Battery capacity: " + batteryCapacity + " kWh");
    }
}

Now we can use both types of cars:

public class Main {
    public static void main(String[] args) {
        Car regularCar = new Car("Toyota", "Corolla", 2020);
        ElectricCar electricCar = new ElectricCar("Tesla", "Model 3", 2021, 75);
        
        regularCar.drive();  // Uses Car's drive method
        electricCar.drive(); // Uses ElectricCar's overridden drive method
        
        regularCar.refuel();  // "The car has been refueled."
        electricCar.refuel(); // "The electric car is charging."
        
        electricCar.displayBatteryInfo(); // Method specific to ElectricCar
    }
}

Polymorphism

Polymorphism allows objects of different classes to be treated as objects of a common superclass. The most common use of polymorphism in Java is when a parent class reference is used to refer to a child class object.

public class Main {
    public static void main(String[] args) {
        // Polymorphism in action
        Car[] cars = new Car[2];
        cars[0] = new Car("Toyota", "Corolla", 2020);
        cars[1] = new ElectricCar("Tesla", "Model 3", 2021, 75);
        
        // The same method call behaves differently depending on the actual object type
        for (Car car : cars) {
            car.drive(); // Will call the appropriate drive method based on the actual object type
        }
    }
}

Abstraction

Abstraction allows us to hide complex implementation details and show only the necessary features of an object. In Java, abstraction is achieved using abstract classes and interfaces.

Abstract Classes

public abstract class Vehicle {
    private String make;
    private String model;
    
    public Vehicle(String make, String model) {
        this.make = make;
        this.model = model;
    }
    
    // Abstract method - must be implemented by subclasses
    public abstract void move();
    
    // Concrete method
    public void displayInfo() {
        System.out.println("Make: " + make + ", Model: " + model);
    }
    
    // Getters
    public String getMake() { return make; }
    public String getModel() { return model; }
}

Interfaces

public interface Chargeable {
    void charge();
    int getBatteryPercentage();
}

Implementing these:

public class ElectricVehicle extends Vehicle implements Chargeable {
    private int batteryPercentage;
    
    public ElectricVehicle(String make, String model) {
        super(make, model);
        this.batteryPercentage = 100;
    }
    
    @Override
    public void move() {
        System.out.println("The electric vehicle moves silently.");
        batteryPercentage -= 10;
    }
    
    @Override
    public void charge() {
        batteryPercentage = 100;
        System.out.println("Vehicle charged to 100%");
    }
    
    @Override
    public int getBatteryPercentage() {
        return batteryPercentage;
    }
}

Conclusion

In this post, we've explored the fundamental concepts of object-oriented programming in Java. We've learned about classes and objects, and the four pillars of OOP: encapsulation, inheritance, polymorphism, and abstraction.

Understanding these concepts is crucial for becoming proficient in Java programming. They allow you to write more organized, reusable, and maintainable code.

In the next and final part of our series, we'll explore more advanced Java topics including exception handling, collections, and file I/O.

Happy coding!

Java for Beginners

This post is part of a series. Explore the rest of the series here.

Java for Beginners: Object-Oriented Programming Concepts | Vinr Academy