L
LLLOS.ai
Learn
L

Chapter 9 — Methods and Constructors 30

Class 11 · Computer Science

Overview

This unit explains methods (functions) and constructors in object-oriented programming, focusing on their purpose, syntax, behaviour and best practices. Students learn how methods allow code reuse, modular design, and abstraction by packaging operations that act on data. Constructors are special methods used to initialise objects; the unit covers default and parameterised constructors, constructor overloading, and the role of copy constructors in object lifecycle. The unit also treats method calling mechanisms (call by value and call by reference), return types including void and object returns, method signatures, scope and lifetime of variables, access modifiers, static methods, and recursion. Practical examples demonstrate how methods and constructors are used to design classes, enforce invariants, and implement simple algorithms. Emphasis is placed on writing clear, well-documented methods, choosing appropriate parameter lists, and understanding the flow of execution during object creation and method invocation. Mastery of these topics prepares students to design classes for larger programs, debug object interactions, and follow coding practices important for board-level programming questions and practical exams.

Learning Objectives

  • Define methods and constructors and distinguish between them.
  • Write methods with correct syntax including return type, name, parameters, and body.
  • Create classes that use default and parameterised constructors to initialise objects.
  • Demonstrate method calling using call by value and call by reference where applicable.
  • Explain method overloading and constructor overloading with examples.
  • Use static methods and variables appropriately and explain their behaviour.
  • Apply recursion in methods for simple problems and trace execution.
  • Design small programs that use methods and constructors to model real-world objects.

Topics in this chapter

17 topics · tap a topic title to jump straight to it.

💻1

What is a Method (Function) in OOP

Introduction to Methods
A method is a named block of code associated with a class that performs a specific operation. Methods encapsulate behaviour: they define how an object acts or what operations can be performed on its data. In object-oriented programming, methods are the primary way to manipulate object state and provide services to other parts of a program.

Structure and Purpose
Typically a method has a return type, a name, and a parameter list inside parentheses, followed by a body enclosed in braces. The return type specifies the kind of value the method yields, or 'void' if it returns nothing. Parameters let the caller supply data for the method to work on. The method body contains statements that carry out the task. Good methods are cohesive: they focus on a single responsibility, which makes code easier to read, test and reuse.

Methods and Objects
Methods are defined inside classes and operate on the instance variables (fields) of objects. When you call a method on an object, the method has access to that object's fields and can change them (if permitted). This combination of data and the methods that operate on that data is the essence of encapsulation. By hiding internal data behind methods, classes control how their data is accessed and modified.

Visibility and Modifiers
Methods can have visibility modifiers such as public, private and protected. Public methods are accessible from outside the class, while private methods are only usable within the same class. Protected methods are accessible within the class and its subclasses. Other modifiers like static associate a method with the class itself rather than instances; final or equivalent can prevent further overriding. Choose modifiers to enforce encapsulation and a clean API.

Local Variables and Scope
Inside a method you declare local variables; their scope is limited to the method. When the method returns, these locals are destroyed. The method may call other methods and may itself be called multiple times; each call has its own local variables stored in a separate activation record on the call stack.

Benefits
Methods avoid code duplication by centralising functionality, make programs modular and easier to test, and improve readability through descriptive names. Learning to design clear, small methods is a key programming skill for building reliable, maintainable software.

📌 Examples
  • A method sum(a, b) that returns a + b.
  • A method displayDetails() inside a Student class that prints name and grade.
🧮 Formulas
  1. methodSignature = returnType methodName(parameterList)
  2. void methods return no value
📊 Visual ideas
Diagram showing a class box with fields and methods listed; show a method call from main() to Class.method().
🧾2

Method Syntax and Components

Essential Parts of a Method
Every method consists of several components: access modifier, optional modifiers (like static), return type, method name, parameter list, and the method body. The access modifier controls who can call the method; static indicates the method belongs to the class rather than an instance. The return type tells what type of value the method gives back; if there is no value the method uses a special type such as void.

Naming and Conventions
Method names usually start with a lowercase verb and use camelCase for readability, e.g., calculateTotal or displayInfo. Names should clearly describe what the method does. Use meaningful parameter names. Keep method length small: a method should ideally do one thing. Long methods are harder to maintain and test.

Parameters and Types
Parameters appear inside the parentheses and include a type and a name. They specify what information the caller must provide. Parameters may be primitive types, object references, arrays, or even functional types depending on the language. Optional parameters and default values may be supported in some languages; otherwise use overloading or builder patterns.

Return Statement
If the method declares a non-void return type, every possible path through the method must return a value of that type. The return statement terminates the method and optionally supplies the return value. Methods that return values are useful in expressions and for composing complex behaviour from smaller parts.

Access Modifiers and Encapsulation
Choose access modifiers based on intended use: public for API methods, private for internal helpers. Private helper methods break complex tasks into simpler steps without exposing implementation details. Protected methods allow extension in subclasses but hide the method from external usage.

Static Methods and Limitations
Static methods can be called using the class name and cannot directly access instance variables. They are useful for utility functions that do not require object state. Be careful not to overuse static mutable variables because they create global state and complicate testing.

Documentation and Comments
Document method purpose, parameters, return values and exceptions. Short comments explaining complex logic help future readers. Use consistent style and keep method contracts clear, stating preconditions and postconditions when necessary.

📌 Examples
  • public int add(int x, int y) { return x + y; }
  • private void init() { // setup code }
🧮 Formulas
  1. returnType methodName(parameterType parameterName, ...)
  2. AccessModifiers + [static] + returnType + name + (parameters) + { body }
📊 Visual ideas
Flowchart of method execution: call -> parameters passed -> execute body -> return -> resume caller.
💻3

Calling Methods and Call Stack

What Happens During a Method Call
When a program calls a method, control moves from the caller to the method. The runtime environment creates an activation record (also called a stack frame) which stores the method's local variables, parameter values, and the return address. The CPU executes the method body using this frame. When a return statement executes or the method ends, the frame is removed and control returns to the saved return address in the caller.

Structure of the Call Stack
The call stack is a last-in-first-out structure where each method call pushes a frame and each return pops a frame. This stack keeps track of nested calls: for example, main calls A, A calls B, and so on. Each frame isolates the local variables and parameters of its method call, so recursive calls have independent storage.

Tracing and Debugging
Understanding the call stack helps in debugging. If a method causes an error, the stack trace shows the sequence of calls leading to the error. By stepping through calls in a debugger, you can inspect each frame's variables and see how values change. Watch for deep recursion or long chains of calls which can exhaust stack memory and cause a stack overflow error.

Parameter Passing at Call Time
During a call, arguments are evaluated and assigned to the callee's parameters. The mechanism (call by value or call by reference) determines whether the callee receives copies or references. The activation record holds these parameters, and any updates to them are local unless references point to shared objects.

Return and Resuming Execution
When a method returns, the return value (if any) is passed back to the caller, and execution resumes immediately after the call expression. Nested calls return step by step: the deepest call returns first, then the caller resumes, eventually reaching the top-level caller. If an exception is thrown and not handled in the method, the runtime unwinds the stack, popping frames until a matching handler is found or the program terminates.

Best Practices
Keep call chains reasonably short for clarity. Avoid extremely deep recursion; convert to iterative solutions if necessary. Use meaningful stack traces and small methods so debugging and testing are easier. Understand how the language implements parameter passing to predict side effects of calls.

📌 Examples
  • main() calls compute() which calls factorial(n) recursively; trace stack frames for n=3.
  • A method updateBalance(account) is called; show whether account's balance is changed depending on pass mechanism.
🧮 Formulas
  1. Each call -> push frame(parameters + locals + returnAddress); return -> pop frame
  2. Stack depth increases with nesting and recursion
📊 Visual ideas
Stack diagram showing frames for main -> A -> B and contents (parameters, locals, return address).
💻4

Return Types and Returning Objects

Simple Return Types
Methods can return primitive data types like int, float, char or boolean. The declared return type must be respected: if a method is declared to return int, its return statements must provide integer values or values that are implicitly convertible. The return value becomes an expression in the caller and can be assigned or used within other calculations.

Returning Object References
Methods may also return objects. What is returned is usually a reference to an object in memory. Returning an object reference allows the caller to access and possibly modify the object's fields. In languages where references are passed by value, the reference itself is copied, but caller and callee share the same underlying object until a copy is made.

Returning New Instances and Defensive Copy
To prevent callers accidentally modifying internal state, methods sometimes return a new object that is a copy (defensive copy). For example, a class that stores a list might return a new list containing the same elements rather than the internal list reference. This preserves encapsulation and avoids unintended side effects at the cost of extra memory and time.

Immutability and Safe Returns
Another way to protect internal state is to use immutable objects. Returning an immutable object is safe because the caller cannot change it. Immutable designs are common for simple value types and make reasoning about code easier. Document whether returned objects are shared or independent.

Multiple Return Paths and Error Handling
Methods may have multiple return statements for different cases. Make sure every possible execution path returns a value when required. When a method cannot produce a valid result, it may throw an exception or return a special value (e.g., null or an Optional). Prefer clear contracts so callers know how to handle special cases.

Returning Large Objects
Returning very large objects can be expensive in time and memory. Where appropriate, return references, lightweight handles, or use streams and iterators to process data incrementally. In performance-sensitive code consider object pooling or shared immutable structures.

📌 Examples
  • int max(int a, int b) { if (a>b) return a; else return b; }
  • Point getOrigin() { return new Point(0,0); }
🧮 Formulas
  1. DeclaredReturnType methodName(...) { return expression_of_DeclaredReturnType; }
  2. void methodName(...) { // no return value }
📊 Visual ideas
UML method signature box showing +getBalance(): double and +setBalance(d:double): void
💻5

Parameters: Types and Passing Mechanisms

Kinds of Parameters
Parameters let methods accept inputs. They can be of primitive types (like int, double), references to objects, arrays, or function types (depending on language). Choose parameter types that express intent clearly: for related data use objects or small classes rather than many separate parameters. Keep parameter lists short for readability and usability.

Call by Value Explained
Call by value passes a copy of the argument value into the method. For primitive types this means the method works on a copy, and changes do not affect the caller's variable. For object references, the reference value is copied: the method receives a copy of the reference that points to the same object. Thus, the method cannot change which object the caller's reference points to, but it can modify the object's internal fields.

Call by Reference Explained
Some languages support call by reference where the method receives an alias or address to the original variable, so changes inside the method affect the caller. This is useful when a method must update multiple pieces of data without returning them. However, it increases the chance of unintended side effects and makes reasoning about code harder unless documented clearly.

In-Out and Output Parameters
To return more than one result, some languages use out or ref parameters. Another approach is to return a composite object (tuple, struct, or class) containing multiple results. Returning a composite object keeps the method interface clearer and avoids modifying caller variables secretly.

Passing Arrays and Collections
When you pass arrays or collections, the method receives a reference to the same collection object (unless the language copies them). Modifying elements or adding/removing items will affect the original collection unless you create and operate on a copy. Document whether a method mutates or only reads its collection parameters.

Best Practices
Prefer returning values rather than using output parameters when clarity is important. Validate parameters at the start of a method and document expected ranges and behaviours. For methods with many parameters, consider using a parameter object or builder pattern to simplify creation and avoid mistakes in ordering arguments.

📌 Examples
  • void swap(int a, int b) { int t=a; a=b; b=t; } // does not swap caller values in call-by-value
  • void updateName(Person p) { p.name = "Rahul"; } // modifies object's field visible to caller
🧮 Formulas
  1. ParameterList = (type1 name1, type2 name2, ...)
  2. CallByValue: callerValue -> copy -> calleeParameter
  3. CallByReference: callerAddress -> calleeParameter -> modifies caller
📊 Visual ideas
Diagram showing two boxes: caller variable with value copied to callee parameter (call by value) and a pointer arrow for call by reference.
🧾6

Constructor: Purpose, Syntax and Initialization

What a Constructor Does
A constructor is a special method used to create and initialise instances of a class. It has the same name as the class and typically no explicit return type. When an object is created, the constructor runs automatically to set fields to sensible initial values, allocate resources if needed, and ensure the object starts in a valid state.

Constructor Syntax
The constructor header uses the class name and a parameter list. For example, ClassName(Type param) { /* initialization */ }. Inside the body you assign field values and possibly validate inputs. Constructors may call helper methods to perform complex setup. A constructor can have access modifiers like public or private to restrict whom can create objects of that class.

Default vs Parameterised Constructors
A default constructor takes no parameters and sets fields to default values; a parameterised constructor accepts arguments to customise the object's initial state. If you do not supply any constructor, many languages supply a no-argument default constructor that initialises fields to default values (zero, null, false). Once you write any constructor, the automatic default is typically not provided and must be written explicitly if needed.

Validation in Constructors
Constructors should validate arguments and throw appropriate exceptions for invalid input so that no object exists in an inconsistent state. For example, a constructor for BankAccount should reject negative initial balances. If validation fails, throw an exception and ensure any partially-initialised resources are safely released to avoid leaks.

Visibility and Special Uses
Constructors can be private to prevent direct instantiation; this is used in patterns like singleton or factory methods. Protected constructors allow subclass instantiation while preventing external creation. Use these controls to manage object creation appropriately for your design.

Practical Tips
Keep constructors focused on initialisation and avoid heavy work like network calls or long-running operations. For complex setup, provide a separate init method to be called after construction or use dependency injection so constructors remain simple and easy to test.

📌 Examples
  • class Book { String title; Book() { title = "Unknown"; } Book(String t) { title = t; } }
  • class Point { int x,y; Point(int x,int y){ this.x=x; this.y=y; } }
🧮 Formulas
  1. ConstructorName(parameterList) { initialisation statements }
  2. If no constructor is defined, a default constructor may be provided by the language
📊 Visual ideas
Object creation diagram: new Class(args) -> memory allocated -> constructor runs -> initialized object returned
💻7

Constructor Overloading and Chaining

What Constructor Overloading Means
Constructor overloading is the practice of providing more than one constructor in the same class, each with a different parameter list. This allows objects to be created in different ways: sometimes with full information, sometimes with partial information, and sometimes with no information where default values are appropriate. Overloading increases convenience for the user of a class while keeping each constructor focused on initialising the object.

Why Overload Constructors
Different code paths or callers may know different amounts of information at the time of object creation. For example, one part of the program may create a Customer with full details from a database, while another part may only know a name and fill the rest with defaults. Overloaded constructors support both cases without forcing clients to set fields after creation or use complex factory utilities.

Constructor Chaining to Avoid Duplication
Constructor chaining is the technique of making one constructor call another constructor of the same class to reuse initialisation logic. This avoids duplicating code across multiple constructors. Chaining is typically performed with a special call like this(...) and must be the first statement in the constructor in many languages. The chained constructor executes first, and then control returns to the caller constructor which may perform any additional, specific adjustments.

Centralised Validation and Single Source of Truth
A common pattern is to implement full validation and complete initialisation in the most detailed constructor, and have simpler constructors call it with default values. This centralises validation so all creation paths behave consistently; you do not need to repeat checks in every constructor. Centralised initialisation reduces bugs and helps maintain invariants.

Rules and Potential Pitfalls
Constructor chaining must not be circular: constructors cannot call one another in a loop. Because the chained call is often required to be first, you cannot execute code before it; plan initialisation accordingly. Heavy operations in constructors, when combined with chaining, may cause unexpected delays or exceptions during object creation, so avoid expensive work or move it out to separate initialisation methods where possible.

Design Alternatives
When a class has many optional parameters, many overloaded constructors become cumbersome. Use patterns like builder or named factory methods to provide clearer creation options. Builders let the caller set only the options they need and then call build() to create the object, improving readability and maintainability.

Practical Example
Consider a class Email with constructors Email(String to), Email(String to, String subject) and Email(String to, String subject, String body). The simplest constructor can chain to the most detailed one with default subject and body, ensuring all Email objects are created through a single validation path.

📌 Examples
  • Point() { this(0,0); } Point(int x,int y) { this.x=x; this.y=y; }
  • Book(String t) { this(t, "Unknown"); } Book(String t, String a) { title=t; author=a; }
🧮 Formulas
  1. Constructor1(...) { this(...); // call to another constructor }
  2. OverloadedConstructors = sameName + differentParameterList
📊 Visual ideas
Flow showing default constructor calling parameterised constructor then returning initialized object.
💻8

Default Constructor and Compiler-Provided Constructors

What the Compiler May Provide
In many programming languages, if you do not define any constructor in a class, the compiler automatically provides a default no-argument constructor. This implicit constructor initialises instance fields to language default values: numeric types to zero, booleans to false, and object references to null. The automatic default constructor enables simple object creation without explicit setup when no specialised initialisation is necessary.

Why the Default May Disappear
Once you define at least one constructor—usually a parameterised one—the compiler will typically stop providing the implicit no-argument constructor. This prevents ambiguity and enforces that callers supply required information. If existing code expects to call a no-argument constructor but the programmer adds only parameterised constructors, compilation errors occur until a no-argument constructor is explicitly written or client code is updated.

Interaction with Inheritance and super()
In class hierarchies, subclass constructors often implicitly call the parent class's no-argument constructor. If the parent class does not have a no-argument constructor because only parameterised constructors exist, subclass constructors must explicitly call a parent constructor using a special syntax (for example super(args)). Failure to do so causes compilation errors. Therefore, when designing base classes, consider whether subclasses will need a default constructor and provide one if appropriate.

When to Provide an Explicit Default Constructor
Provide an explicit default constructor when frameworks or tools rely on no-argument creation (for instance, certain serialization libraries, GUI frameworks or dependency injection containers). An explicit default constructor also lets you set non-default but safe initial values, log creation, or register the instance with a manager while keeping object creation simple for client code.

Design Considerations
Think deliberately about whether objects should be creatable without arguments. If certain fields are mandatory for correctness, prefer parameterised constructors and prevent default construction. If flexibility is required for testing or frameworks, provide a default constructor that sets safe defaults and performs minimal work. Avoid heavy operations in default constructors to keep object creation predictable and test-friendly.

Practical Examples and Troubleshooting
Common compile-time errors occur when subclasses lack a matching super() call or when reflection-based instantiation fails because no default constructor exists. When encountering such problems, add a simple no-argument constructor or modify subclass constructors to call an appropriate parent constructor explicitly.

📌 Examples
  • class A { A(int x) { } } class B extends A { B() { super(5); } } // must call parent constructor
  • If only parameterised constructors exist and no default, new Class() is invalid
🧮 Formulas
  1. If no constructor defined -> compiler may provide default: ClassName() { /* default init */ }
  2. Once any constructor is defined -> compiler does not add default
📊 Visual ideas
Diagram showing class with no constructors -> compiler adds default; with constructor present -> no automatic default.
💻9

Copy Constructor and Object Cloning

Purpose of a Copy Constructor
A copy constructor creates a new object as a duplicate of an existing object. It takes an object of the same class as a parameter and initialises the new object's fields from the existing object's fields. The copy constructor is useful when you want an independent object with the same initial state, so changes to one do not affect the other unless shared references remain.

Shallow Copy vs Deep Copy
A shallow copy copies primitive fields and copies references for object fields; both the original and the copy reference the same nested objects. A deep copy duplicates nested objects recursively so the new object has separate copies of everything, preventing side-effects from shared sub-objects. Which type you choose depends on whether shared references are acceptable and on performance considerations.

Implementing Copy Constructors
To implement a copy constructor you assign primitive fields directly. For mutable referenced fields you must decide whether to assign the same reference (shallow) or create new instances (deep). For deep copies create new objects for each referenced field and copy their contents; be careful with circular references to avoid infinite recursion—use memoisation or mapping to detect already-copied objects if necessary.

Language Support and Alternatives
Some languages provide built-in cloning methods or interfaces, while others encourage explicit copy constructors. Another approach to cloning is serialization (serialize and deserialize the object), which can produce a deep copy but is slower and requires serialisable classes. Choose the method that balances correctness, performance and simplicity.

Practical Uses
Copy constructors are handy for undo operations, snapshots, or when storing versions of objects. They ensure an operation can work on a safe copy rather than the original, avoiding accidental modifications. Always document whether the copy is shallow or deep so users know the semantics.

Examples and Cautions
For collections and arrays, deep copying usually requires creating a new collection and copying each element (and possibly deep copying elements if they are mutable). Avoid copying ephemeral resources like open file handles; instead, recreate or re-open resources as needed in the new object.

📌 Examples
  • Point original = new Point(2,3); Point copy = new Point(original); // copy constructor duplicates x,y
  • class Person { Address addr; Person(Person p){ this.name=p.name; this.addr=new Address(p.addr); } } // deep copy of address
🧮 Formulas
  1. CopyConstructor: ClassName(ClassName other) { copy fields; create new instances for mutable references if deep copying }
📊 Visual ideas
Diagram showing original object with references and copy object: shallow copy shares reference arrows; deep copy has separate objects.
💻10

Static Methods and Variables

Static Members Defined
Static variables (class variables) and static methods belong to the class itself rather than to any particular instance. A static variable is shared by all instances of the class; changing it through one reference is visible to all. Static methods can be invoked directly using the class name without creating an object, making them convenient for utility functions.

Use Cases for Static Variables
Use static variables for data that should be common across all instances: for example, a counter that tracks how many objects of a class have been created. Constants are often declared static final (or equivalent) to provide a single shared value. Because static state is global, it must be used sparingly to avoid hidden dependencies.

Static Methods and Restrictions
Static methods cannot access instance variables directly because they are not associated with any particular object. To work with instance state, a static method must receive a reference to an instance as a parameter. Static methods are excellent for stateless utilities (like math functions), factories that create objects, or helper functions used across the system.

Lifetime and Initialization
Static variables are typically initialised when the class is first loaded and remain until the class is unloaded or the program ends. This lifetime means static members occupy memory for a long duration and can cause memory retention if they hold large objects. Also, initialisation order matters: static initialisers run in a defined order and may depend on other static members, so keep static initialisation simple and predictable.

Concurrency Considerations
Because static variables are shared, concurrent access from multiple threads requires synchronization to avoid race conditions. Use atomic constructs or synchronized access to static mutable variables. Immutable static data does not pose concurrency issues and is safe to share freely.

Testing and Design Risks
Excessive static mutable state makes unit testing harder because tests can influence each other through shared state. Prefer dependency injection of stateful collaborators when testability is important. Use static for true global constants and pure utilities, not as a shortcut for global variables.

📌 Examples
  • static int count = 0; // shared count across all objects
  • static double max(double a,double b) { return (a>b)?a:b; } // utility method
🧮 Formulas
  1. StaticMember accessed as ClassName.member
  2. static variables are initialised once and shared across instances
📊 Visual ideas
Class diagram showing static variable above instance fields, with notation indicating shared access.
💻11

Method Overloading and Signatures

What is Method Overloading?
Method overloading means defining multiple methods with the same name but different parameter lists (different number, types, or order of parameters). Overloading allows a class to offer the same logical operation in several variants, making the API more convenient. The compiler selects the appropriate method to call based on the argument types provided at the call site.

Method Signature Explained
A method's signature usually comprises its name and the types of its parameters. Return type is not part of the signature for overload resolution in most languages, so you cannot overload methods solely by changing return type. The compiler resolves calls by matching the argument types to available signatures, preferring exact matches and applying conversion rules when needed.

Resolution Rules and Ambiguity
Overloading resolution follows language-specific rules: exact match first, then widening conversions, then boxing or varargs as fallbacks. When multiple conversions make different overloads applicable equally, the call becomes ambiguous and causes a compile-time error. To avoid ambiguity, design overloads with clearly distinct parameter lists and avoid types that the compiler can convert between easily.

Design Use-Cases
Common use-cases include providing convenience methods (e.g., print(int), print(String), print(Object)) and constructors that accept different groups of initial data. Overloading helps simplify client code by allowing it to pass whatever form of data is most natural without needing explicit conversions.

Best Practices
Keep overloads consistent in behaviour: methods with the same name should do similar things. Avoid excessive overloading, which can confuse users and complicate maintenance. When many optional parameters are needed, consider builder patterns or named factory methods rather than a large number of overloaded constructors.

Examples and Pitfalls
Be cautious when combining overloading with default parameters or implicit conversions. Overloads that differ only in parameter types that can be implicitly converted (like int and long) are risky because small changes in call expressions can change which overload is selected, potentially altering behaviour.

📌 Examples
  • void print(int x) { } void print(String s) { }
  • int add(int a,int b) { } int add(int a,int b,int c) { }
🧮 Formulas
  1. OverloadedMethods = sameName + differentParameterList
  2. MethodSignature = methodName + parameterTypes
📊 Visual ideas
Table showing overloaded methods and which call matches given argument lists.
💻12

Method Overriding and Access Control

Overriding: Changing Behaviour in Subclasses
Method overriding occurs when a subclass defines a method with the same signature as a method in its superclass. This allows the subclass to provide a new implementation for that method. Overriding is the basis of runtime polymorphism: a reference typed as the superclass can point to a subclass instance, and calling an overridden method will execute the subclass's version at runtime.

Rules for Overriding
To override a method correctly, the subclass method must have the same name, parameter types and order as the superclass method. The return type should be the same or covariant (a subtype) where the language allows it. The access level of the overriding method must be at least as accessible as the original: for example, you cannot make a public method private in the subclass. Also, the exception specifications (if enforced) must be compatible.

Calling Parent Implementations
Sometimes the subclass wants to extend rather than replace behaviour. In such cases the subclass method can call the parent method explicitly (for example using super.methodName()). This allows a combination of inherited behaviour and subclass-specific additions. Use this pattern when the subclass should retain the contract and invariants of the parent while adding functionality.

Annotations and Compiler Checks
Many languages provide an annotation like @Override which tells the compiler that a method is intended to override a parent method. This helps catch mistakes—for instance, a misspelt name or wrong parameter types—by causing a compile-time error when the method does not actually override anything.

Polymorphism and Dynamic Dispatch
Overriding supports polymorphism so code can work with a general type but rely on specific behaviours at runtime. For example, a list of Shape objects can contain Circle and Rectangle; calling draw() on each shape invokes the correct subclass method thanks to dynamic dispatch. This makes code extensible and flexible.

Design Considerations
Keep method contracts consistent across superclass and subclass: preconditions should not be strengthened and postconditions should not be weakened in overrides. Violating contracts can lead to surprising behaviour and bugs. Use overriding deliberately to provide clear, logical customisations of behaviour.

📌 Examples
  • class Animal { void sound(){} } class Dog extends Animal { void sound(){ System.out.println("Bark"); } }
  • Use super() in constructor to invoke parent initialisation
🧮 Formulas
  1. Overriding requires same signature; access level in subclass >= access level in parent
  2. Polymorphism: ParentRef = new Child(); ParentRef.method() -> Child.method()
📊 Visual ideas
Inheritance diagram showing Parent and Child classes with overridden method; arrow from object instance calling method resolved to Child implementation.
💻13

Recursion in Methods

Understanding Recursion
Recursion is a technique where a method calls itself to solve a smaller instance of the original problem. It is natural for problems that are defined in terms of smaller subproblems, such as computing factorials, traversing trees, or performing divide-and-conquer algorithms. A recursive method reduces the problem size on each call until a base case is reached.

Base Case and Recursive Case
Every recursive method must have at least one base case which stops further recursion and one or more recursive cases that make progress toward the base case. The base case provides direct answers for simple inputs (e.g., factorial(0) = 1). The recursive case breaks the problem into smaller pieces and combines their results.

Tracing and Call Trees
To reason about recursion, draw the call tree showing each recursive call and its parameters. Each node in the tree represents a method call and returns a value used by its parent. Tracing helps verify correctness and ensures that each path reaches the base case. For example, factorial(4) leads to calls factorial(3), factorial(2), factorial(1), revealing how multiplication accumulates.

Performance and Optimisation
Recursion is elegant but can be less efficient than iteration due to call overhead and stack usage. Some recursive algorithms have exponential time (like naive Fibonacci) and should be replaced by dynamic programming or memoisation to avoid repeated work. Tail recursion is a form where the recursive call is the final action; some compilers optimise tail recursion to reuse stack frames and avoid stack growth.

When to Use Recursion
Use recursion when it simplifies code and the expected depth is small, such as tree traversals or when a problem is naturally recursive. Avoid deep recursion for large inputs unless the language guarantees tail-call optimisation or you transform the algorithm to an iterative form.

Examples and Testing
Classic examples include factorial, Fibonacci, and binary search. Test recursive methods with base conditions, typical values, and boundary cases to ensure they terminate correctly and return expected results.

📌 Examples
  • int factorial(int n) { if(n<=1) return 1; else return n*factorial(n-1); }
  • int fib(int n) { if(n<=1) return n; else return fib(n-1)+fib(n-2); }
🧮 Formulas
  1. Recurrence example: factorial(n) = n * factorial(n-1) with base factorial(0)=1
  2. Ensure base case: if (condition) return baseValue; else return recursiveCall;
📊 Visual ideas
Call tree for factorial(4): factorial(4) -> factorial(3) -> factorial(2) -> factorial(1)
💻14

Designing Methods: Cohesion and Single Responsibility

Single Responsibility Principle
A well-designed method should do one thing and do it well. This principle makes methods easier to test, understand and reuse. A method that performs multiple unrelated tasks should be split into smaller helper methods. Small, focused methods also make debugging simpler because you can isolate failures more easily.

Cohesion and Coupling
Cohesion measures how closely related the steps inside a method are; high cohesion means the method's statements all contribute towards a single purpose. Coupling refers to dependencies between methods and classes; low coupling is desirable so changes in one part have minimal effect on others. Aim for high cohesion within methods and low coupling between them.

Parameters and Return Types
Design parameter lists to include only necessary information. If a method requires many parameters, consider grouping them into an object that captures the concept (a parameter object). Prefer returning computed values rather than modifying global or class-level state silently. Clear input-output behaviour improves readability and testability.

Naming and Documentation
Name methods with verbs that state the action (calculateTax, findMax). Document preconditions (what the method expects) and postconditions (what it guarantees). For complex logic explain the overall approach and edge cases in comments. Good names and documentation reduce mistakes and make the codebase easier for others to use.

Refactoring and Reuse
When similar code appears in multiple places, extract it into a method to avoid duplication. Refactor large methods by identifying coherent sub-tasks and creating private helper methods. This improves reuse and reduces the chance of bugs when behaviour must change.

Testing Considerations
Small methods lend themselves to unit testing because each test can focus on one behaviour. Mock dependencies to isolate methods under test. Well-designed method boundaries make mocking straightforward and tests reliable. Overall, design methods to be simple, focused and predictable.

📌 Examples
  • extractFileName(path) performs one task: returns the file name from a path
  • calculateInterest(account) returns computed interest; does not print or modify account directly
📊 Visual ideas
Diagram showing a large method split into smaller helper methods with single responsibilities.
💻15

Error Handling and Validation in Methods and Constructors

Validate Inputs Early
Methods and constructors should check their inputs at the start and handle invalid values promptly. Validating early prevents objects from entering an inconsistent state and helps locate bugs where they originate. For example, a constructor should check that a required string is not null or empty and that numeric parameters are within acceptable ranges.

Exceptions vs Return Codes
Use exceptions for unexpected or exceptional situations where normal operation cannot continue. Return codes are appropriate for expected alternative outcomes but require callers to check results. Follow language and project conventions: many modern codebases prefer exceptions for error signalling as they separate error handling from normal flow and are harder to ignore accidentally.

Constructors and Failed Initialization
If a constructor cannot complete initialization due to invalid input or resource errors, it should throw an exception rather than creating a partially-initialised object. This prevents code from working with defective objects. Ensure that any resources acquired before throwing are released to avoid leaks.

Using Assertions
Assertions document assumptions and catch programming errors during development. They are not a replacement for input validation because assertions may be disabled in production. Use clear error messages and exceptions with informative messages to help diagnose problems quickly.

Graceful Recovery and Fallbacks
When possible, methods should fail gracefully by returning optional results, fallback defaults, or meaningful error objects. For library code, define stable error contracts so callers know what to expect and how to recover. Log errors with sufficient context for debugging while avoiding leakage of sensitive information.

Testing Error Paths
Write unit tests for invalid inputs and exceptional conditions to ensure methods and constructors behave as specified. Test cleanup and resource release paths to avoid leaks. Document which exceptions are thrown and under what circumstances so users can write correct calling code.

📌 Examples
  • Constructor Person(String name) { if(name==null || name.isEmpty()) throw new IllegalArgumentException("name required"); this.name=name; }
  • double divide(int a,int b) { if(b==0) throw new ArithmeticException("divide by zero"); return a/(double)b; }
📊 Visual ideas
Flowchart showing method start -> validate inputs -> normal processing -> return; invalid -> throw exception/return error
💻16

Testing and Debugging Methods and Constructors

Unit Testing Principles
Test methods in isolation using unit tests that verify correctness for normal inputs, edge cases and invalid inputs. Each test should be small and focused, asserting a single behaviour. For constructors, write tests that ensure objects are initialised with correct values and that invalid input leads to the expected exceptions. Automated unit tests make it safe to refactor and extend code.

Choosing Test Cases
Pick representative typical cases, boundary conditions (e.g., zero, maximum expected sizes), and negative cases. For numeric methods consider tests for overflow, underflow and precision issues. For methods manipulating collections, test empty, singleton and large collection cases. For constructors, test default and parameterised forms as well as failure paths where validation should raise exceptions.

Using Debuggers and Stack Traces
Use a debugger to step through method calls, inspect local variables and watch how the call stack evolves. Stack traces from exceptions show the sequence of method calls that led to an error; read them from the top to find the origin. Breakpoints let you pause execution at precise points to investigate state and control flow. Combine logging with debugging for problems that occur intermittently or only in certain environments.

Mocking and Isolation
When methods depend on external systems (databases, web services), use mocks or stubs to isolate the method under test. Mocking frameworks let you replace collaborators with controlled test doubles that return expected responses. This makes tests faster, reliable and deterministic. For constructors that perform expensive setup, provide alternate constructors or factory methods that skip heavy work so tests can create objects quickly.

Test Automation and Coverage
Automate tests to run frequently, ideally on each code change. Aim for meaningful coverage by testing critical logic and edge cases rather than chasing 100% line coverage. Continuous integration systems can run the test suite automatically, catching regressions early. Maintain tests as first-class artefacts alongside code so they evolve with requirements.

Refactoring and Debugging Strategy
Refactor in small steps and run tests after each change. When a bug is reported, write a failing test that reproduces it, fix the code, and then keep the test to prevent regression. Use assertions in code to check invariants during development; they can reveal wrong assumptions early. Clear, descriptive test names and helpful assertion messages make debugging faster.

📌 Examples
  • Write unit test for factorial(5) expecting 120 and for factorial(0) expecting 1
  • Test constructor of Circle with radius -1 should throw IllegalArgumentException
📊 Visual ideas
Test case table mapping input values to expected outputs for a method.
💻17

Practical Examples: Designing Classes with Methods and Constructors

Bringing It All Together
This topic demonstrates how methods and constructors combine to form useful classes. A well-designed class uses constructors to ensure valid initial state and methods to provide the operations clients need. Consider a BankAccount class: a parameterised constructor sets account id and initial balance, deposit and withdraw methods change balance with validation, and getBalance returns the current amount. This keeps related data and behaviour together and enforces rules like preventing negative balances.

Encapsulation and Accessors
Keep fields private and expose needed operations through public methods. Provide accessor (get) and mutator (set) methods when necessary, but avoid exposing internal structures directly. For derived data like area or age, provide methods that compute values on demand instead of storing redundant fields that risk becoming inconsistent.

Static Utilities and Counters
Use static fields for class-wide data like instance counts, and static methods for utilities that don’t need instance state. For example, a static method formatCurrency can be used across classes. Constructors can increment a static counter so you can monitor how many objects were created.

Factory Methods and Builders
Factories and builders offer alternatives to many overloaded constructors. A named factory method like Account.createSavings(initialDeposit) is clearer than several similar constructors. Builder patterns are useful when many optional parameters exist: they provide a readable way to construct complex objects step by step without many constructor overloads.

Error Handling and Testing
Ensure constructors validate their inputs and that methods check preconditions. Write unit tests for constructor behaviour and method operations, including edge cases and invalid inputs. For classes that manage resources, implement clean shutdown methods and test resource handling thoroughly.

Example Walkthrough
Design a Rectangle class with private width and height, parameterised constructor with validation, getArea() method, and a static utility to compare areas. This simple example shows how clear constructors and focused methods form a reliable API that is easy to test and reuse.

📌 Examples
  • BankAccount(String id, double initial) { if(initial<0) throw new IllegalArgumentException(); this.id=id; this.balance=initial; } void deposit(double amt){ balance+=amt; }
  • Rectangle r = new Rectangle(5,4); double area = r.getArea(); // constructor sets width and height; getArea() computes
📊 Visual ideas
Class diagram for BankAccount showing private fields id, balance and public methods deposit, withdraw, getBalance and constructors.

Key Concepts

Method
A named block of code in a class that performs a specific task and may return a value.
Constructor
A special method with the same name as the class that initialises new objects.
Default Constructor
A no-argument constructor provided by the programmer or automatically by the compiler when none is defined.
Parameterised Constructor
A constructor that accepts parameters to set initial object state.
Method Overloading
Defining multiple methods with the same name but different parameter lists.
Method Overriding
A subclass redefining a parent class method with the same signature to change behaviour.
Static
A modifier making a member belong to the class itself rather than to instances.
Call by Value
A parameter passing method where the called method receives a copy of the argument value.
Call by Reference
A passing mechanism where the called method can modify the caller's variable via a reference.
Copy Constructor
A constructor that creates a new object as a copy of an existing object.
Shallow Copy
A copy that duplicates top-level fields but shares references to nested objects.
Deep Copy
A copy that recursively duplicates all nested objects so no shared references remain.
Recursion
A method calling itself to solve smaller instances of a problem, with a base case to stop.
Activation Record
A call stack frame storing a method's parameters, local variables and return address during execution.
Defensive Copy
Returning a new copy of internal data to prevent callers from modifying the original state.

Practice Questions

  1. Define a constructor and explain its purpose. / एक कन्स्ट्रक्टर को परिभाषित करें और इसका उद्देश्य समझाइए।
    Show answer

    A constructor is a special method named the same as the class that initialises new objects; its purpose is to set initial values for fields and prepare the object for use. / कन्स्ट्रक्टर एक विशेष विधि है जिसका नाम क्लास के समान होता है और यह नए ऑब्जेक्ट्स को प्रारम्भ करता है; इसका उद्देश्य फ़ील्ड्स के प्रारम्भिक मान सेट करना और ऑब्जेक्ट को उपयोग के लिए तैयार करना है।

  2. Write a parameterised constructor for a class Student with fields name (String) and age (int). / name (String) और age (int) फ़ील्ड वाली Student क्लास के लिए एक parameterised constructor लिखिए।
    Show answer

    public Student(String name, int age) { this.name = name; this.age = age; } / public Student(String name, int age) { this.name = name; this.age = age; }

  3. Explain method overloading with an example. / एक उदाहरण के साथ method overloading समझाइए।
    Show answer

    Method overloading is defining methods with the same name but different parameter lists, e.g., void print(int x) and void print(String s); the compiler selects the appropriate method based on arguments. / Method overloading का अर्थ है एक ही नाम की वे विधियाँ परिभाषित करना जिनके पैरामीटर सूची भिन्न होते हैं, जैसे void print(int x) और void print(String s); कंपाइलर तर्कों के आधार पर उपयुक्त विधि चुनता है।

  4. What is the difference between call by value and call by reference? / call by value और call by reference में क्या अंतर है?
    Show answer

    Call by value passes a copy of the argument so changes in the method do not affect the caller; call by reference passes an address/reference so the method can modify the caller's variable. / Call by value तर्क की नकल पास करता है इसलिए विधि में परिवर्तन कॉलर को प्रभावित नहीं करते; call by reference पता/संदर्भ पास करता है इसलिए विधि कॉलर के चर को बदल सकती है।

  5. Describe a copy constructor and when you would use it. / एक copy constructor का वर्णन करें और आप इसे कब उपयोग करेंगे।
    Show answer

    A copy constructor creates a new object by copying fields from an existing object; use it when you need an independent object with the same initial state, especially to avoid shared mutable references. / एक copy constructor मौजूदा ऑब्जेक्ट के फ़ील्ड्स की नकल कर नया ऑब्जेक्ट बनाता है; जब आपको समान प्रारम्भिक स्थिति वाला स्वतंत्र ऑब्जेक्ट चाहिए या साझा परिवर्तनशील संदर्भों से बचना हो तो इसका उपयोग करें।

  6. Write a recursive method to compute factorial of n and explain base case. / n का factorial निकालने के लिए एक recursive method लिखिए और base case समझाइए।
    Show answer

    int factorial(int n) { if(n<=1) return 1; else return n * factorial(n-1); } The base case is n<=1 returning 1; it stops further recursion. / int factorial(int n) { if(n<=1) return 1; else return n * factorial(n-1); } Base case n<=1 पर 1 लौटाता है; यह आगे की recursion को रोकता है।

  7. A class has only a parameterised constructor. Will the compiler provide a default constructor? Explain. / यदि एक क्लास में केवल parameterised constructor है तो क्या कंपाइलर default constructor देगा? समझाइए।
    Show answer

    No; if any constructor is defined, the compiler does not provide a default no-argument constructor. You must write it explicitly if needed. / नहीं; यदि कोई भी कन्स्ट्रक्टर परिभाषित है तो कंपाइलर डिफ़ॉल्ट no-argument कन्स्ट्रक्टर प्रदान नहीं करता। आवश्यकता हो तो आपको स्वयं लिखना होगा।

  8. Give two reasons to use constructor chaining. / constructor chaining का उपयोग करने के दो कारण दीजिए।
    Show answer

    1) To avoid repeating initialization code; 2) To centralise validation and setup in one constructor so all creation paths behave consistently. / 1) आरम्भिकरण कोड दोहराने से बचने के लिए; 2) मान्यकरण और सेटअप को एक कन्स्ट्रक्टर में केंद्रीकृत करने के लिए ताकि सभी निर्माण पथ समान रूप से व्यवहार करें।

  9. Explain shallow copy and deep copy with one example each. / shallow copy और deep copy को एक-एक उदाहरण के साथ समझाइए।
    Show answer

    Shallow copy duplicates primitive fields but copies object references so both objects share referenced sub-objects (e.g., new Object(a.field references same array)). Deep copy duplicates nested objects too so the copy is independent (e.g., create new array and copy elements). / Shallow copy प्राथमिक फ़ील्ड्स की नकल करता है पर ऑब्जेक्ट संदर्भों को साझा करता है, जिससे दोनों ऑब्जेक्ट साझा उप-ऑब्जेक्ट्स उपयोग करते हैं (जैसे नया ऑब्जेक्ट बनाते समय वही array संदर्भ)। Deep copy अंतर्निहित ऑब्जेक्ट्स को भी नकल करता है इसलिए कॉपी स्वतंत्र होती है (जैसे नया array बनाकर उसके तत्त्वों की प्रतिलिपि करना)।

  10. Why should constructors avoid heavy operations like network access? / कन्स्ट्रक्टर्स को network access जैसे भारी ऑपरेशन्स से क्यों बचना चाहिए?
    Show answer

    Heavy operations can slow object creation, cause exceptions during construction, and make testing harder; it's better to perform such tasks in separate initialisation methods. / भारी ऑपरेशन्स ऑब्जेक्ट निर्माण को धीमा कर सकते हैं, कंस्ट्रक्शन के दौरान अपवाद उत्पन्न कर सकते हैं और परीक्षण कठिन बना सकते हैं; ऐसे कार्य अलग initialisation विधियों में करना बेहतर है।

  11. Explain why static methods cannot access instance variables directly. / समझाइए कि static methods सीधे instance variables तक क्यों नहीं पहुँच सकतीं।
    Show answer

    Static methods belong to the class, not to any instance, so there is no specific object's instance variables available; instance variables require an object reference to be accessed. / Static methods क्लास से संबंधित होते हैं, किसी विशेष उदाहरण से नहीं, इसलिए कोई विशेष ऑब्जेक्ट का instance variables उपलब्ध नहीं होता; instance variables तक पहुँच के लिए ऑब्जेक्ट संदर्भ आवश्यक है।

Related Laws & Principles

Explore all

Foundational laws & principles connected to this chapter — tap to open in the Laws Explorer.

Loading related laws…
Sourced from 0 content files · LLOS Learn · browse all chapters