Introduction to OOPS in Java

This article assumes that you are familiar with procedural programming. So, let’s begin with an example of the same. Assume we need to create an application that maintains student records, including name, age, roll number, and marks in Science, English, and Mathematics. The application should also perform functions like calculating percentage marks of each student, changing the roll number of a student, increasing marks in a particular subject, etc.

In procedural programming, one way to approach this problem would be to create six arrays, one for each attribute: name, age, roll number, and marks in Science, English, and Mathematics. In this case, the same index of different arrays can have values corresponding to one student. The application would look like something we have below.

public class StudentDemo {
    public static void main(String args[]) {
        String name[] = new String[10];
        int rollNumber[] = new int[10];
        int age[] = new int[10];
        int mathMarks[] = new int[10];
        int englishMarks[] = new int[10];
        int scienceMarks[] = new int[10];
        // name[i] and rollNumber[i] would represent name and roll number of ith student
        // Take input for these arrays        
    }
    
    public float calculatePercentage(int mathMarks, int englishMarks, int scienceMarks, int maxMarksPerSubject) {
        return (mathMarks + englishMarks + scienceMarks) * 100 / (float)(maxMarksPerSubject * 3);
    }
    // more functions to calculate different stats
}

If a new requirement comes and we also want to store the address of each student, we will have to create one more array. If the maximum marks of each subject change, then we will have to alter the arguments of the method calculatePercentage(). If we want to store more details about each subject, then we will need another set of arrays to store properties of subjects like subjectName[], subjectCode[]. With each new requirement, the complexity of this code will increase drastically.

Thus, we have classes and objects to make code more efficient and reusable. It enables us to think about the problem in a way that would align with our natural thinking and thus, reduces the complexity enormously. So, how would the above solution translate in the world of classes and objects?

public class Student {
    private String name;
    private int age;
    private int rollNumber;
    private int englishMarks;
    private int scienceMarks;
    private int mathMarks;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    // other getters and setters
    public float calculatePercentage(int maxMarksPerSubject) {
        return (mathMarks + englishMarks + scienceMarks) * 100 / (float)(maxMarksPerSubject * 3);
    }
    public void changeRollNumber(int newRollNumber) {
        this.rollNumber = newRollNumber;
    }
}

Here we have a Student class that contains all the properties (or attributes) for a student. We can say class Student defines a template for each student objecti.e., each student will have the attributes and methods defined in the class. An object is a real entity that represents an instance of the class. Thus, objects are also called instances of the class.

We can also say that class is just a specification for the objects. It defines the properties (attributes) and methods that all objects of a particular type should have, and then each object is a real entity that defines the values for those attributes. If Car is a class with properties like topSpeed, horsePower, etc, then real cars like Nissan Micra, Fiat Puntoetc. are objects (of Car class) that have different values for those specifications.

Just like we have variables of type int, we can have variables of Student type. We can declare them as shown below:

Student studentA = new Student();
studentA.setName("Ayush");

Here, new Student() creates a student object and assigns its reference to the variable studentA. And studentA.setName(“Ayush”) sets the name of the student. Similarly, we can call other setters on the object to set other attributes.

Each object has two things — state and behavior. The attributes —like name, age, marks — define the state of the object and methods — like changeRollNumber() — define the behavior (algorithms that operate on attributes) of the object.

public static void main(String args[]) {
    Scanner sc= new Scanner(System.in);
    Student student[] = new Student[100];
    for(int i = 0; i < 100; i++) {
        student[i] = new Student();
        student[i].setName(sc.nextLine());
        student[i].setAge(sc.nextInt());
        // take other inputs            
    }        
}

Earlier code consisting of six different arrays (one for each attribute) got reduced to this, and now we only need one array to represent any number of attributes and any number of students. If we want to add a new attribute then we can just add it to the student class and we don’t need to create another array for it. If we have different properties for subjects then we can create a Subject class and use it inside Student class as shown below:

public class Subject {
    String name;
    String code;
    int maxMarks;
    
    // getters and setters
}
public class Student {
    private String name;
    private int age;
    private int rollNumber;
    private Subject[] subjectList;    

    //getters and setters
}

Now we don’t even need separate attributes for different subjects. We can see how classes and objects helped to gracefully and elegantly manage the complexity of the code. What we did was, just defined a class for each real-world entity we wanted to capture in our code. Thus, classes and objects help us to map the real-world entities to simple programming constructs.

Besides reducing complexity, the object-oriented approach provides other advantages as well. Before delving into the advantages, let’s first discuss (on a high level) the four main concepts involved in object-oriented programming so that it’s easier to understand the advantages it has to offer.

Four Basic Concepts Involved in Object-Oriented Programming:

  1. Inheritance: As already discussed, the object-oriented approach simplifies the translation of the physical world around us into the programming domain. Like in the above examples, we used Student objects to represent real students studying in a school or a university.
    In the real world, many entities share common properties, and thus, they are generalized into a more abstract entity that describes them. For example, we have the abstract entity ‘Person’. All the people in the real world will have common properties like date of birth, gender, height, weight, etc. We know all the teachers and students in a school ultimately belong to this generalization ‘Person’ as they all share these common properties.
    Suppose we are required to store details of all the teachers and students present in a school. The direct solution would be to create two classes — ‘Student’ and ‘Teacher’ with their respective attributes and methods, and then create their instances corresponding to the teachers and students present in the school. Each of these classes will have many common attributes and methods like dateOfBirth, address, getAge(), etc. Code will like as shown below:

    class Teacher {
       private String firstName;
       private String lastName;
       private String address;
       private Integer age;
    
       private Long employeeId;
       private Long salary;
       private String grade;
       // ... more properties and methods
    }
    class Student {
       private String firstName;
       private String lastName;
       private String address;
       private Integer age;
    
       private Integer rollNumber;
       private Integer englishMarks;
       private Integer scienceMarks;
       private Integer mathMarks;
       // ... more properties and methods
    }
    

    This problem of redundant code can be solved using inheritance. Inheritance is a mechanism by which one class can inherit the properties and methods of another class. Thus, it enables to mimic the concept of real-world hierarchy in programming. In our particular example, we need to create the parent class ‘Person’ with attributes and methods common to all the people in general. ‘Student’ class and ‘Teacher’ class will then inherit those common properties from the ‘Person’ class while defining fields and methods that are unique to them.

    public class Person {
       protected String firstName;
       protected String lastName;
       protected String address;
       protected Integer age;
    }
    class Teacher extends Person {
       private Long employeeId;
       private Long salary;
       private String grade;
       // ... more properties and methods
    }
    class Student extends Person {
       private Integer rollNumber;
       private Integer englishMarks;
       private Integer scienceMarks;
       private Integer mathMarks;
       // ... more properties and methods
    
       public String getFullName() {
           return firstName + lastName;
       }
    }
    

    We can see that now Student and Teacher classes only need to define their unique properties, and the common properties can be defined in the parent class ‘Person’. The getFullName() method in Student class is just for demonstration purposes to show that Student objects have actually inherited properties from the parent class.

  2. Encapsulation: Encapsulation is the wrapping up of data members and methods operating on that data in a single unit. What this means is that, every individual object has its own set of data members and member methods which are independent of fields or methods of other objects. With this capability, each object (with its fields and methods) has complete control over its state and behavior and we can’t modify its state from outside. This helps prevent inconsistencies in the object state. For instance, assume we have a class ‘Number’ with two data members ‘int odd’ and ‘int even’ that is always supposed to be odd and even. Now the methods in this class ensure that the value for these members always remains odd and even respectively. However, if it was possible to modify these members from outside then it could have led to inconsistencies in the code (as other programmers using the class might not be aware of the constraints that the original programmer might have considered important).
    Encapsulation also enables objects to control access to their properties by using appropriate access specifiers.
  3. Abstraction: Abstraction is the concept of hiding implementation details from the user and providing them with just an interface to use any particular functionality. A typical example of abstraction is the way we drive a car. While driving a car, we just use the steering (an interface) to change direction but we are not aware (neither we need to) of how the rotation of steering causes a change in the direction of motion. Similarly, we know to apply the brakes we need to press the brake pedal but we don’t (need to) know the internal functioning of the same.

    Similarly, we might have a class ‘Student’ with a method ‘getGPA()’. Now we can use objects of the ‘Student’ class and call the method ‘getGPA()’ to get the GPA of any student. Thus, the only thing we need to know is the signature of the method that we need to call. Its implementation might even change with time but to the end-user, it doesn’t matter as they are only concerned with the output returned by the method.

  4. Polymorphism: Polymorphism means having multiple forms. In terms of programming, it means using one interface to perform different operations based on the context. A typical example of this is ‘+’ operator. If we do 5 + 5, we get 10 as the result. However, if we do “5” + 5, where the first operand is a string, we get “55” as the result, as all the operands are first converted to a string and then concatenated. In this example, we used the same operator ‘+’ but provided different types of arguments (operands), resulting in different operations actually being performed on those arguments. Polymorphism is usually implemented by method overloading and method overriding in Java, which will be covered in detail in a different post.

    Now that we have covered the basic concepts of OOPS, it would be easier to understand the advantages of the same. Some advantages of using object-oriented programming are:

    • It prevents redundant code as discussed in inheritance above.
    • It helps us map real-world entities to objects that we can use in code.
    • It makes our code more modular and thus, reusable. It also makes the code extensible as adding a new feature to a class requires modification only in that class and not in other parts of the program.
    • It makes our code easy to maintain as it’s structured in logical components.
    • It facilitates data hiding via encapsulation. So if we make our data members private, we can restrict access to them and make them accessible only from the methods inside the class. This helps in enforcing a set of rules (like restricting access to certain data members), which might otherwise prove complicated to implement while also requiring extra coding efforts.
    • It offers abstraction and thus, the users don’t need to worry about the implementation details.
    • Polymorphism helps us to use the same method name for different operations (by using different method signatures). This makes it easier for other programmers to use our code as they don’t need to use different method names for the same operation.

Enjoy learning, Enjoy OOPS!

We welcome your comments

Subscribe Our Newsletter

Get well-designed application and interview centirc content on ds-algorithms, machine learning, system design and oops. Content will be delivered weekly.