Java Module3
Java Module3
Module – 3
Polymorphism
The term polymorphism comes from Greek, meaning “many forms.”
It allows one interface to be used for a general class of actions.
The specific action executed depends on the situation or the type of data.
Example (Stack)
In non-OOP languages, you must write three different sets of functions with different
names. In Java (OOP), polymorphism allows one common set of method names, reused
with different data types.
Polymorphism is often described as “one interface, multiple methods.”
You can design a generic interface for related operations. This reduces complexity because:
You use the same method name. The compiler decides which version of the method to call.
Eg: Dog Analogy
A dog’s sense of smell behaves polymorphically. If it smells a cat, it barks and chases. If it
smells food, it salivates and runs to the bowl. Same “interface” (smell), different actions
depending on the “data” (what it smells).
Method Overloading
When a class has multiple methods with the same name but different parameter lists, the
methods are overloaded.
Method overloading is one way Java implements compile-time polymorphism.
How It Works
When an overloaded method is called, Java decides which method to invoke based on:
o Number of parameters
o Type of parameters.
Rules for Overloading
o Methods must differ in type and/or number of parameters.
o Methods may have different return types.
o But return type alone cannot differentiate overloaded methods.
Why It Is Useful
o Makes code more readable and user-friendly.
o Allows methods that perform similar tasks to reuse the same name.
Eg: A method sum() can be overloaded as:
sum(int a, int b)
sum(double a, double b)
sum(int a, int b, int c)
You use one method name to perform similar operations on different data types.
Java avoids this complexity by allowing all versions to share the same name (e.g.,
[Link]() works for int, long, float, double).
The compiler automatically selects the correct version based on the argument’s data
type.
Advantages of Method Overloading
Allows related methods to be accessed using one common name.
Reduces the number of different function names a programmer must remember.
Improves readability, maintainability, and conceptual clarity.
Helps manage complexity in large programs by grouping similar operations.
4. Design Guidelines for Overloading
All overloaded versions can perform any activity, but:
o They should be conceptually related.
Overloading unrelated methods using the same name is not recommended.
o Example of poor use: Using the name sqr for:
square of an integer, and
square root of a float
(These operations are different and should not share a name.)
Overloading should be used only when methods perform similar or related tasks.
Overloading Constructors
In addition to overloading normal methods, you can also overload constructor
methods. In fact, for most real-world classes that you create, overloaded constructors will be
the norm, not the exception. Following is the latest version of Box
Box() constructor requires three parameters. This means that all declarations of Box objects
must pass three arguments to the Box() constructor. For example, the following statement is
currently invalid:
Box ob = new Box();
Since Box() requires three arguments, it’s an error to call it without them.
Overload the Box constructor so that it handles 3 arguments. Here is a program that contains
an improved version of Box
As you can see, the proper overloaded constructor is called based upon the arguments
specified when new is executed.
Using Objects as Parameters
So far, we have only been using simple types as parameters to methods. However, it is both
correct and common to pass objects to methods. For example, consider the following short
program
As you can see, the equalTo( ) method inside Test compares two objects for equality and
returns the result. That is, it compares the invoking object with the one that it is passed. If they
contain the same values, then the method returns true. Otherwise, it returns false. Notice that
the parameter o in equalTo( ) specifies Test as its type. Although Test is a class type created
by the program, it is used in just the same way as Java’s built-in types.
One of the most common uses of object parameters involves constructors.
Frequently, you will want to construct a new object so that it is initially the same as some
existing object. To do this, you must define a constructor that takes an object of its class as a
parameter. For example, the following version of Box allows one object to initialize another:
As you will see when you begin to create your own classes, providing many forms of
constructors is usually required to allow objects to be constructed in a convenient and efficient
manner.
Argument Passing
Two General Ways to Pass Arguments to a method
1. Call-by-Value
The value of the argument is copied into the method’s parameter.
The method works on the copy, not the original value.
Changes inside the method do NOT affect the original argument.
2. Call-by-Reference
Instead of copying the value, a reference (address) to the original argument is passed.
The method can directly access and modify the actual argument.
Changes inside the method DO affect the original variable.
How Java Passes Arguments
Java always uses call-by-value.
o The behavior depends on whether you pass a primitive type or a reference type.
Passing Primitive Types in Java
When a primitive (int, float, double, etc.) is passed:
o A copy of its value is passed to the method.
o The method works only on this local copy.
o The original value remains unchanged.
How Java Passes Reference Types (Objects & Arrays)
When an object or array is passed, Java passes the value of the reference.
This means the method receives a copy of the reference, but it still points to the same
object.
So: Changing the object’s data → affects the original [Link] the reference
inside the method → does NOT affect the original reference.
Note:
Object contents can be changed.
Reference itself cannot be changed from the caller’s side.
As you can see, the operations that occur inside meth( ) have no effect on the values of a and
b used in the call; their values here did not change to 30 and 10.
Note: Primitive types → copy of value
Objects → copy of reference (so object contents can change)
As you can see, in this case, the actions inside meth( ) have affected the object used as an
argument.
REMEMBER When an object reference is passed to a method, the reference itself is passed
by use of ca l-by value. However, since the value being passed refers to an object, the copy of
that value will still refer to the same object that its corresponding argument does.
Returning Objects
A method can return any type of data, including class types that you create. For example, in the
following program, the incrByTen( ) method returns an object in which the value of a is ten
greater than it is in the invoking object:
As you can see, each time incrByTen( ) is invoked, a new object is created, and a reference to
it is returned to the calling routine.
The preceding program makes another important point: Since all objects are
dynamically allocated using new, you don’t need to worry about an object going out of scope
because the method in which it was created terminates. The object will continue to exist as long
as there is a reference to it somewhere in your program. When there are no references to it, the
object will be reclaimed the next time garbage collection takes place.
Recursion
Java supports recursion. Recursion is the process of defining something in terms of itself. As
it relates to Java programming, recursion is the attribute that allows a method to call itself. A
method that calls itself is said to be recursive.
The classic example of recursion is the computation of the factorial of a number. The
factorial of a number N is the product of all the whole numbers between 1 and N. For example,
3 factorial is 1 × 2 × 3, or 6. Here is how a factorial can be computed by use of a recursive
method:
Recursive code is often more elegant and easier to understand compared to complicated
loops.
How QuickSort Works
1. Choose a pivot element.
(In this program, the last element is chosen as pivot.)
2. Rearrange (partition) the array so that:
o Elements smaller than pivot → left side
o Elements greater than pivot → right side
3. Return the position of the pivot after partitioning.
4. Recursively apply QuickSort on:
o Left sub-array (before pivot)
o Right sub-array (after pivot)
5. Recursion continues until the entire array is sorted.
}
int temp = a[i+1];
a[i+1] = a[high];
a[high] = temp;
return i+1;
}
/* The main function that implements QuickSort()
a[] --> Array to be sorted,
l --> Starting index,
h --> Ending index */
void sort(int a[], int l, int h)
{
if (l < h)
{
int pi = partition(a, l, h);
// Recursively sort elements before partition and after partition
sort(a, l, pi-1);
sort(a, pi+1, h);
}
}
// Driver program
public static void main(String args[])
{
int a[] = {10, 7, 8, 9, 1, 5};
int n = [Link];
QuickSort ob = new QuickSort();
[Link](a, 0, n-1);
for (int i=0; i<n; ++i)
[Link](a[i]+" ");
}
public int i;
private double j;
private int myMethod(int a, char b) { //……}
To understand the effects of public and private access, consider the following program:
As you can see, inside the Test class, a uses default access, which for this example is the same
as specifying public. b is explicitly specified as public. Member c is given private access. This
means that it cannot be accessed by code outside of its class. So, inside the AccessTest class, c
cannot be used directly. It must be accessed through its public methods: setc( ) and getc( ). If
you were to remove the comment symbol from the beginning of the following line,
ob.c = 100; // Error!
then you would not be able to compile this program because of the access violation.
To see how access control can be applied to a more practical example, consider the
following improved version of the Stack class.
As you can see, now both stck, which holds the stack, and tos, which is the index of
the top of the stack, are specified as private. This means that they cannot be accessed or altered
except through push( ) and pop( ). Making tos private, for example, prevents other parts of
your program from inadvertently setting it to a value that is beyond the end of the stck array.
The following program demonstrates the improved Stack class. Try removing the
commented-out lines to prove to yourself that the stck and tos members are, indeed,
inaccessible.
Understanding static
Purpose of static members
Sometimes you need a class member that works independently of any object.
Normally, class members are accessed through objects, but static members are
accessible without creating an object.
Declaring static members
Use the keyword static before a variable or method.
Static members can be accessed:
o Before any object is created
o Without using an object reference
The most common static method is main().
main() must be static because Java calls it before objects are created.
As soon as the UseStatic class is loaded, all of the static statements are run. First, a is set to 3,
then the static block executes, which prints a message and then initializes b to a*4 or 12. Then
main( ) is called, which calls meth( ), passing 42 to x. The three println( ) statements refer to
the two static variables a and b, as well as to the parameter x.
Here is the output of the program:
Outside of the class in which they are defined, static methods and variables can be used
independently of any object. To do so, you need only specify the name of their class followed
by the dot operator.
For example, if you wish to call a static method from outside its class, you can do so
using the following general form:
[Link]( )
Here, classname is the name of the class in which the static method is declared. As you
can see, this format is similar to that used to call non-static methods through object-reference
variables. A static variable can be accessed in the same way—by use of the dot operator on the
name of the class. This is how Java implements a controlled version of global methods and
global variables.
Here is an example. Inside main( ), the static method callme( ) and the static variable
b are accessed through their class name StaticDemo.
Introducing final
A final field cannot be modified after it is assigned.
It behaves like a constant in Java.
Ways to initialize a final field
There are two valid methods:
As you can see, the size of each array is displayed. Keep in mind that the value of length
has nothing to do with the number of elements that are actually in use. It only reflects the
number of elements that the array is designed to hold.
You can put the length member to good use in many situations. For example, here is
an improved version of the Stack class. As you might recall, the earlier versions of this class
always created a ten-element stack. The following version lets you create stacks of any size.
The value of [Link] is used to prevent the stack from overflowing.
In the program, an inner class named Inner is defined within the scope of class Outer.
Therefore, any code in class Inner can directly access the variable outer_x. An instance
method named display( ) is defined inside Inner. This method displays outer_x on the
standard output stream. The main( ) method of InnerClassDemo creates an instance of class
Outer and invokes its test( ) method. That method creates an instance of class Inner, and the
display( ) method is called.
It is important to realize that an instance of Inner can be created only in the context of
class Outer. The Java compiler generates an error message otherwise. In general, an inner
class instance is often created by code within its enclosing scope, as the example does.
As explained, an inner class has access to all of the members of its enclosing class, but
the reverse is not true. Members of the inner class are known only within the scope of the inner
class and may not be used by the outer class. For example:
Inheritance
Inheritance is a cornerstone of object-oriented programming (OOP).
Inheritance is an object-oriented programming feature that allows a class (subclass) to
acquire the properties and behaviours (fields and methods) of another class (superclass),
enabling code reuse, hierarchical classification, and creation of specialized versions of
general classes
It allows creating hierarchical relationships between classes.
Promotes code reuse and logical classification.
How Inheritance Works
You can define a general class (superclass/base/parent) that contains traits common to
a group of related items.
Specific classes (subclasses/derived/child) can inherit from the general class and add
their unique features.
4. Features of Subclasses
A subclass inherits all members (fields and methods) of its superclass.
Can add new members unique to itself.
Represents a specialized version of the superclass.
Inheritance Basics
In order to have your class inherit from a superclass, you simply incorporate the definition of
the superclass into your class using the extends keyword. To see how, let’s begin with a short
example. The following program creates a superclass called A and a subclass called B. Notice
how the keyword extends is used to create a subclass of A.
As you can see, the subclass B includes all of the members of its superclass, A. This is why
subOb can access i and j and call showij( ). Also, inside sum( ), i and j can be referred to
directly, as if they were part of B.
Even though A is a superclass for B, it is also a completely independent, stand-alone
class. Being a superclass for a subclass does not mean that the superclass cannot be used by
itself. Further, a subclass can be a superclass for another subclass.
The general form/syntax of a class declaration that inherits a superclass is shown here:
class subclass-name extends superclass-name{
//body of class
}
You can only specify one superclass for any subclass that you create. Java does not support the
inheritance of multiple superclasses into a single subclass. You can, as stated, create a
hierarchy of inheritance in which a subclass becomes a superclass of another subclass.
However, no class can be a superclass of itself.
Member Access and Inheritance
Inheritance and Member Access
A subclass inherits all members (fields and methods) of its superclass.
However, private members of the superclass are not accessible directly in the
subclass.
Why Private Members Are Not Accessible
Private members are hidden from other classes, including subclasses.
This preserves encapsulation, preventing subclasses from modifying internal details of
the superclass directly.
This program will not compile because the use of j inside the sum( ) method of B causes an
access violation. Since j is declared as private, it is only accessible by other members of its
own class. Subclasses have no access to it.
A class member that has been declared as private will remain private to its class. It is not
accessible by any code outside its class, including subclasses.
A More Practical Example
The new class will contain a box’s width, height, depth, and weight.
BoxWeight inherits all of the characteristics of Box and adds to them the weight component.
It is not necessary for BoxWeight to re-create all of the features found in Box. It can simply
extend Box to meet its own purposes.
A major advantage of inheritance is that once you have created a superclass that defines
the attributes common to a set of objects, it can be used to create any number of more specific
subclasses. Each subclass can precisely tailor its own classification. For example, the
following class inherits Box and adds a color attribute:
Remember, once you have created a superclass that defines the general aspects of an object,
that superclass can be inherited to form specialized classes. Each subclass simply adds its own
unique attributes. This is the essence of inheritance
Using super
Problem with Direct Access in Subclasses
In previous examples, subclasses like BoxWeight directly initialized fields (width,
height, depth) from the superclass Box.
Issues with this approach:
1. Code duplication – same initialization code exists in both superclass and
subclass.
2. Access violation – requires the superclass fields to be non-private, which
breaks encapsulation.
If the superclass keeps its members private, the subclass cannot access them directly.
Solution: The super Keyword
Java provides super to allow a subclass to refer to its immediate superclass safely.
super has two main uses:
a) Calling the superclass constructor
b) Accessing a hidden member of the superclass
Key Points
super() must be the first statement in a subclass constructor if used.
super helps maintain encapsulation while allowing proper initialization and member
access.
Avoids code duplication and direct field access, making inheritance more robust.
Using super to Call Superclass Constructors
A subclass can call a constructor defined by its superclass by use of the following form of
super:
super(arg-list);
Here, arg-list specifies any arguments needed by the constructor in the superclass. super( )
must always be the first statement executed inside a subclass’s constructor.
To see how super( ) is used, consider this improved version of the BoxWeight class:
Here, BoxWeight( ) calls super( ) with the arguments w, h, and d. This causes the Box
constructor to be called, which initializes width, height, and depth using these values.
BoxWeight no longer initializes these values itself. It only needs to initialize the value unique
to it: weight. This leaves Box free to make these values private if desired.
In the preceding example, super( ) was called with three arguments. Since constructors
can be overloaded, super( ) can be called using any form defined by the superclass. The
constructor executed will be the one that matches the arguments. For example, here is a
complete implementation of BoxWeight that provides constructors for the various ways that a
box can be constructed. In each case, super( ) is called using the appropriate arguments. Notice
that width, height, and depth have been made private within Box.
Example:
o C inherits all traits of B and A.
Practical Example
Superclass: Box → defines dimensions (width, height, depth)
Subclass: BoxWeight → inherits from Box and adds weight
Subclass of BoxWeight: Shipment → inherits from BoxWeight and Box
o Adds a new field: cost (shipping cost)
Result:
o Shipment objects have all members of Box and BoxWeight, plus its own cost.
Benefits of Multilevel Inheritance
Code reuse: Reuse fields and methods across multiple layers.
Logical classification: Build hierarchies that reflect real-world relationships.
Extensibility: Subclasses can extend functionality without modifying existing classes.
Method Overriding
Method Overriding in Java — Explanation
In a class hierarchy, if a subclass defines a method with the same name, return type, and
parameters as a method in its superclass, the subclass overrides the superclass method.
When you call the method using an object of the subclass, Java executes the subclass
version of the method.
The superclass method is hidden (but can still be accessed using [Link]()
inside the subclass).
This is runtime polymorphism.
When show( ) is invoked on an object of type B, the version of show( ) defined within B is
used. That is, the version of show( ) inside B overrides the version declared in A. If you wish
to access the superclass version of an overridden method, you can do so by using super. For
example, in this version of B, the superclass version of show( ) is invoked within the subclass’s
version. This allows all instance variables to be displayed
The version of show( ) in B takes a string parameter. This makes its type signature different
from the one in A, which takes no parameters. Therefore, no overriding (or name hiding) takes
place. Instead, the version of show( ) in B simply overloads the version of show( ) in A.
This program creates one superclass called A and two subclasses of it, called B and C.
Subclasses B and C override callme( ) declared in A. Inside the main( ) method, objects of type
A, B, and C are declared. Also, a reference of type A, called r, is declared. The program then
in turn assigns a reference to each type of object to r and uses that reference to invoke callme(
). As the output shows, the version of callme( ) executed is determined by the type of object
being referred to at the time of the call. Had it been determined by the type of the reference
variable, r, you would see three calls to A’s callme( ) method.
NOTE Readers familiar with C++ or C# wil recognize that overridden methods in Java are
similar to virtual functions in those languages.
Why Overridden Methods?
Polymorphism through Overridden Methods
Overridden methods allow Java to achieve run-time polymorphism. This means the
same method name can behave differently depending on the object that calls it. The superclass
defines the general method, and each subclass provides its own implementation. This is why
polymorphism is often described as:
“One interface, multiple methods.”
So, the method signature is the same in all classes, but the actual behavior depends on the
subclass object.
Why Polymorphism Is Essential
Polymorphism is important in OOP because it allows:
A general superclass to define a set of methods that all subclasses must share.
Each subclass to implement these methods in a specialized way based on their needs.
This creates a structure where the superclass provides what is common, while subclasses
provide what is specific.
Hierarchy and Specialization
Classes in OOP form a hierarchy:
Superclass → Subclass → More specialized subclass
The superclass contains all the general features.
Subclasses become progressively more specialized.
Subclasses can:
o directly use what the superclass provides
o override methods to add their own specific behavior
This allows subclasses to be flexible while still following a consistent interface
defined by the superclass.
Why Polymorphism Makes Code Powerful
Dynamic run-time polymorphism is powerful because:
You can reuse existing code without modification.
You can create new subclasses and existing programs will still work with them.
Libraries can call methods on new objects without being recompiled.
}
You cannot create an object of an abstract class.
Purpose of Abstract Classes
Define the template or general structure
Force subclasses to provide specific implementations
Support polymorphism with partially implemented superclasses
Points (Exam-Friendly)
Abstract method → declared, not implemented.
Abstract class → cannot be instantiated.
Subclasses → must override all abstract methods, unless they are also abstract.
Used when the superclass cannot fully implement a method.
Enables run-time polymorphism with compulsory overriding.
abstract type name(parameter-list);
As you can see, no method body is present.
Any class that contains one or more abstract methods must also be declared abstract. To declare
a class abstract, you simply use the abstract keyword in front of the class keyword at the
beginning of the class declaration. There can be no objects of an abstract class. That is, an
abstract class cannot be directly instantiated with the new operator. Such objects would be
useless, because an abstract class is not fully defined. Also, you cannot declare abstract
constructors or abstract static methods. Any subclass of an abstract class must either
implement all of the abstract methods in the superclass or be declared abstract itself.
Here is a simple example of a class with an abstract method, followed by a class which
implements that method:
Notice that no objects of class A are declared in the program. As mentioned, it is not
possible to instantiate an abstract class. One other point: class A implements a concrete method
called callmetoo( ). This is perfectly acceptable. Abstract classes can include as much
implementation as they see fit.
Although abstract classes cannot be used to instantiate objects, they can be used to
create object references, because Java’s approach to run-time polymorphism is implemented
through the use of superclass references. Thus, it must be possible to create a reference to an
abstract class so that it can be used to point to a subclass object. You will see this feature put to
use in the next example:
Using an abstract class, you can improve the Figure class shown earlier. Since there is
no meaningful concept of area for an undefined two-dimensional figure, the following version
of the program declares area( ) as abstract inside Figure. This, of course, means that all classes
derived from Figure must override area( ).
o Result:
Faster execution
No overhead of calling a separate method
Why only final methods can be inlined
Java normally uses late binding (run-time method resolution) for overridden methods.
Since most methods can be overridden, the JVM cannot decide at compile time which
version will run.
But with final methods, overriding is impossible → so the exact method is known
earlier.
Late binding (run-time binding)
Default behavior in Java.
The JVM decides at run time which method to call.
Necessary because subclass methods may override superclass methods.
Early binding (compile-time binding)
Possible only when the method is final, static, or private.
The compiler resolves the method call at compile time.
Faster than late binding.
Summary
final methods cannot be overridden, so the JVM knows exactly which method to call.
This enables early binding, meaning the compiler can sometimes inline the method.
Inlining removes call overhead → performance improvement.
Normal methods use late binding because they may be overridden.
Using final to Prevent Inheritance
Purpose of a final class
A final class cannot be inherited.
This means no subclass can extend it
final class
To prevent modification of critical classes.
To maintain security, stability, or design integrity.
Common example: The Java String class is final.
Cannot be both abstract and final
An abstract class requires subclasses (because it is incomplete).
}
In the program, a hierarchy is created that consists of three classes, at the top of which is
MyClass. FirstDerivedClass is a subclass of MyClass, and SecondDerivedClass is a
subclass of FirstDerivedClass. The program then uses type inference to create three variables,
called mc, mc2, and mc3, by calling getObj( ). The getObj( ) method has a return type of
MyClass (the superclass) but returns objects of type MyClass, FirstDerivedClass, or
SecondDerivedClass, depending on the argument that it is passed. As the output shows, the
inferred type is determined by the return type of getObj( ), not by the actual type of the object
obtained. Thus, all three variables will be of type MyClass.