Java #12 - Class and Object (Part -2)

Method & constructor overloading | Argument Passing | Access Control | static keyword | final keyword

Method & Constructor Overloading

What is polymorphism in OOP?

In Object-Oriented Programming (OOP), polymorphism is the ability of an object to take on multiple forms. Polymorphism allows a single object or function to be used in multiple ways, depending on the context in which it is used.

There are two types of polymorphism in OOP:

  1. Compile-time polymorphism, also known as "overloading" or "static polymorphism," is achieved through method overloading or operator overloading.

  2. Run-time polymorphism, also known as "overriding" or "dynamic polymorphism," which is achieved through method overriding and virtual function calls.

In OOP, polymorphism allows for more flexible and extensible code, as the same action can be performed by a single object or function, but with different inputs or types. It allows for more natural modelling of real-world concepts and relationships.

Method Overloading

Method overloading in Java is a feature that allows a class to have multiple methods with the same name but with different parameters. This is also known as "compile-time polymorphism" or "static polymorphism".

  • Using this feature, we can define two or more methods within the same class that share the same name, as long as their parameter declarations are different and it is referred to as method overloading.

  • Return types do not play a role in overload resolution.

  • When an overloaded method is invoked, Java uses the type and/or number of arguments as its guide to determine which version of the overloaded method to actually call.

  • The value of overloading is that it allows related methods to be accessed by use of a common name which helps several names to reduce to one.

class Overload {
    void test(){
        System.out.println("Called test() method without argument.");
    }
    void test(int a){
        System.out.println("Called test() method with argument: "+ a);
    }
}
public class OverloadDemo {
    public static void main(String[] args) {
        Overload overload = new Overload();
        overload.test();
        overload. Test(10);
    }
}

Overloading Constructor

Constructor overloading is a powerful feature that allows you to create objects with different initial values and handle different types of input in a more flexible and efficient way.

Why overload the constructor?

  1. To provide multiple ways to initialize an object: By overloading the constructor, you can provide different sets of parameters that allow the user to create an object with different initial values.

  2. To handle different types of input: Overloading the constructor allows you to handle different types of input, such as strings, integers, or other objects, and convert them into the appropriate format for the object.

  3. To create a default constructor: If you don't provide any constructors for a class, the compiler will automatically generate a default constructor with no arguments. However, if you want to provide a default constructor with a specific set of default values, you can do this by overloading the constructor.

  4. To create a copy constructor: A copy constructor is a constructor that creates a new object and initializes it with the data from an existing object. Overloading the constructor allows you to create a copy constructor that can be used to create a new object that is a copy of an existing object.

Example - Below are examples of constructor overloading in Student classes. In this example, the class Student has three constructors. The first constructor initializes the id, name, course, and fee variables with default values. The second constructor takes one int and one String parameter and sets the id and name variables accordingly. The third constructor takes three ints, one String and one double parameter and sets all the variables accordingly.

class Student {
    private int id;
    private String name;
    private String course;
    private double fee;

    // First constructor with no parameters
    public Student() {
        // default values
        id = 0;
        name = "Unknown";
        course = "Unknown";
        fee = 0.0;
    }

    // Second constructor with one int and one string parameter
    public Student(int i, String n) {
        id = i;
        name = n;
        course = "Unknown";
        fee = 0.0;
    }

    // Third constructor with three parameters
    public Student(int i, String n, String c, double f) {
        id = i;
        name = n;
        course = c;
        fee = f;
    }

    public void display() {
        System.out.println("ID: " + id + " Name: " + name + " Course: " + course + " Fee: " + fee);
    }
}
public class StudentDemo {
    public static void main(String[] args) {
        // Creating objects using the first constructor
        Student s1 = new Student();
        s1.display(); // ID: 0 Name: Unknown Course: Unknown Fee: 0.0

        // Creating objects using the second constructor
        Student s2 = new Student(101, "John Doe");
        s2.display(); // ID: 101 Name: John Doe Course: Unknown Fee: 0.0

        // Creating objects using the third constructor
        Student s3 = new Student(102, "Jane Smith", "Computer Science", 15000.0);
        s3.display(); // ID: 102 Name: Jane Smith Course: Computer Science Fee: 15000.0
    }
}

As you can see, constructor overloading allows you to create different objects with different initial values, depending on the parameters passed to the constructor, this flexibility allows the user to initialize the object in the way that suits them.

Argument Passing

There are two ways that a computer language can pass an argument to a subroutine.

  • call-by-value - It copies the value of an argument into the formal parameter of the subroutine i.e. changes made to the parameter of the subroutine have no effect on the argument.

  • call-by-reference - A reference to the argument (not the value of the argument) is passed to the parameter. Inside the subroutine, this reference is used to access the actual argument specified in the call i.e. change made to the parameter will affect the argument used to call the subroutine.

In Java, call-by-value is used to pass all arguments, the precise effect differs between whether a primitive type or a reference type is passed.

When primitive type is passed to a method, it is passed by value i.e. A copy of the argument is made and what occurs to the parameter that receives the argument has no effect outside the method.

When object reference is passed to the method, the argument and parameter refer to the same object, that's why changes to the object inside a called method do affect the object used as an argument.

Access Control

Encapsulation is a fundamental principle of object-oriented programming (OOP) that is used to hide the implementation details of a class and expose only the necessary information to other classes.

The main idea behind encapsulation is to ensure that the internal state of an object cannot be accessed or modified directly by external objects, but only through a set of public methods or properties.

Encapsulation provides below benefits -

  • Data hiding: Encapsulation allows developers to hide the internal state of an object from external objects, which helps to protect the object from unwanted changes and ensure that the object's state remains consistent.

  • Security: Encapsulation allows developers to limit access to sensitive information and methods, which can help to prevent unauthorized access and enhance security.

Encapsulation is achieved by using access modifiers. Java's access modifiers are -

  • private - A class member that is declared as private can only be accessed from within the same class.

  • public - A class member that is declared as public can be accessed from any class, regardless of the package the class is in.

  • protected - A class member that is declared as protected can be accessed from any class within the same package or any subclasses of the class, regardless of the package the subclass is in.

  • default (or package-private) - A class member that does not have an explicit access modifier is considered to have a default access. This means that it can only be accessed from classes within the same package.

Example

class AccessControl{
    int a;
    private int b;
    void print(){
        System.out.println("a= "+ a +", b = "+b);
    }
}

public class AccessControlDemo {
    public static void main(String[] args) {
        AccessControl accessControl = new AccessControl();
        accessControl.a = 10;
        //accessControl.b =20; // this will give compile time error as 'b' is declared as private in class and will not be accessible outside
        System.out.println(accessControl.a);
        accessControl.print(); // print() method is accessible as it default i.e. accessible inside package
    }
}

When using access modifiers in Java, it is generally recommended to follow these guidelines:

  1. Make fields private: Fields should generally be declared private to promote encapsulation and data hiding. Public fields can be accessed and modified from anywhere, which can lead to unpredictable behaviour and introduce bugs.

  2. Use getters and setters for private fields: If a private field needs to be accessed or modified from outside the class, you should create public getter and setter methods for that field. This allows you to control access to the field and to add validation or other logic to the setter method.

  3. Use package-private access with care: Package-private access should be used only when necessary to allow access to members of a class within the same package. In general, it is better to use private or protected access instead, since package-private access can make it difficult to manage the visibility of class members.

  4. Avoid using public fields and methods, if possible: Public fields and methods can be accessed and modified from anywhere, which can lead to unpredictable behaviour and introduce bugs. Instead, use private fields and methods, and provide public getters and setters for fields that need to be accessed from outside the class.

By following these guidelines, you can improve the quality of your code by making it more robust, maintainable, and secure. You also can improve the organization of the codebase by making it more modular and reusable.

static keyword

The static keyword in Java is used to declare a class member (field or method) as being associated with the class itself, rather than with instances of the class. When a member is declared static, it can be accessed using the class name and without reference to any object.

Static fields and methods are shared among all instances of a class and can be accessed without creating an instance of the class. They are typically used for utility methods or fields that are common to all instances of a class, or for methods or fields that don't depend on the state of an instance.

A method declared as static has to follow below rules:

  • They can only directly call other static methods and static variables of their class.

  • They cannot refer to this or super.

You can declare the block as static to initialize static variables and this block executes only once when the class is first loaded.

Example

class StaticExample{
    static int a;
    int b;
    static{
        System.out.println("This will be called only once");
        a = 10;
    }
    static void display(){
        System.out.println("a="+a);
        a += 10;
        //System.out.println("b="+b);// compile time error that static method cannot access non-static member
    }
}
public class StaticDemo {
    public static void main(String[] args) {
        StaticExample.display();
        StaticExample.display(); // when this method is called it display incremented value of a 
    }
}

final keyword

The final keyword in Java is used to declare a variable, method, or class as final. Here we will discuss only variables as the use of final for method and class is applicable with inheritance, which will be discussed in future articles.

When a final keyword is used with a variable, then its content cannot be modified i.e. it becomes a constant value.

We must initialize the final variable when it is declared, we can do this in two ways:

  • We can initialize the variable when it is declared.

  • Or its value can be assigned within a constructor.

Did you find this article valuable?

Support subodh's blog by becoming a sponsor. Any amount is appreciated!