Java Methods - Complete Guide
Master Java methods with detailed explanations of parameters, return types, overloading, recursion, and best practices for modular, reusable code.
Code Reusability
Write Once, Use Many Times
Modular Design
Break Complex Problems
Method Overloading
Multiple Versions
1. What are Methods in Java?
Methods in Java are blocks of code that perform specific tasks. They are the building blocks of Java programs, enabling code reuse, modularity, and organization.
Method Characteristics
- Encapsulate functionality into reusable units
- Can accept parameters (inputs)
- Can return values (outputs)
- Improve code readability and maintainability
- Enable code reuse across program
- Support recursion (calling themselves)
Types of Methods
- Instance Methods: Operate on object instances
- Static Methods: Belong to class, not instances
- Abstract Methods: Declared without implementation
- Final Methods: Cannot be overridden
- Synchronized Methods: Thread-safe execution
- Native Methods: Implemented in other languages
Quick Reference: Method Types
Different types of methods available in Java:
2. Method Anatomy and Syntax
Understanding the structure of a Java method is essential for writing effective code. Here's the complete anatomy:
Method Structure Breakdown
public static int calculateSum(int a, int b) {
int sum = a + b;
return sum;
}
| Component | Description | Required | Examples |
|---|---|---|---|
| Access Modifier | Controls visibility of method | Optional (default: package-private) | public, private, protected |
| Return Type | Data type of returned value | Required | int, String, void, double |
| Method Name | Identifier for method | Required | calculateSum, printMessage |
| Parameters | Input values for method | Optional | (int x, int y) |
| Method Body | Implementation code | Required (except abstract) | { return x + y; } |
| Exception List | Exceptions method can throw | Optional | throws IOException |
public class MethodAnatomyExamples {
public static void main(String[] args) {
System.out.println("=== METHOD ANATOMY EXAMPLES ===\n");
// Call various method examples
simpleMethod();
String greeting = getGreeting("Alice");
System.out.println(greeting);
int result = addNumbers(5, 3);
System.out.println("5 + 3 = " + result);
double area = calculateCircleArea(7.0);
System.out.println("Area of circle with radius 7: " + area);
boolean isEven = checkEven(42);
System.out.println("Is 42 even? " + isEven);
String fullName = concatenateNames("John", "Doe");
System.out.println("Full name: " + fullName);
}
// 1. Simple void method (no parameters, no return)
public static void simpleMethod() {
System.out.println("This is a simple method with no parameters and no return value.");
}
// 2. Method with parameters and return value
public static String getGreeting(String name) {
return "Hello, " + name + "! Welcome to Java methods.";
}
// 3. Method with multiple parameters
public static int addNumbers(int a, int b) {
return a + b;
}
// 4. Method with different return type
public static double calculateCircleArea(double radius) {
return Math.PI * radius * radius;
}
// 5. Method with boolean return type
public static boolean checkEven(int number) {
return number % 2 == 0;
}
// 6. Method that calls other methods
public static String concatenateNames(String firstName, String lastName) {
String fullName = formatName(firstName) + " " + formatName(lastName);
return fullName.trim();
}
// 7. Helper method (private, used internally)
private static String formatName(String name) {
if (name == null || name.isEmpty()) {
return "";
}
return name.substring(0, 1).toUpperCase() + name.substring(1).toLowerCase();
}
// 8. Method with default (package-private) access
static void packagePrivateMethod() {
System.out.println("This method is accessible only within the package.");
}
// 9. Protected method
protected static void protectedMethod() {
System.out.println("This method is accessible within package and subclasses.");
}
// 10. Private method (internal use only)
private static void privateMethod() {
System.out.println("This method is accessible only within this class.");
}
}
3. Method Types and Categories
Java methods can be categorized based on their behavior, access level, and relationship with objects.
Method Classification Chart
By Access Level
- Public: Accessible from anywhere
- Private: Accessible only within class
- Protected: Accessible within package + subclasses
- Package-private: Accessible only within package
By Return Type
- Void Methods: No return value
- Primitive Return: int, double, boolean, etc.
- Object Return: String, Object, custom classes
- Array Return: Returns arrays
| Method Type | Description | When to Use | Example |
|---|---|---|---|
| Instance Methods | Operate on instance variables, require object | When method needs object state | person.getName() |
| Static Methods | Belong to class, can be called without object | Utility functions, helper methods | Math.sqrt() |
| Abstract Methods | Declared without implementation (in abstract class/interface) | When implementation varies in subclasses | abstract void draw(); |
| Final Methods | Cannot be overridden by subclasses | When method implementation must not change | public final void secureMethod() |
| Synchronized | Thread-safe, only one thread can execute at a time | Multi-threaded environments | synchronized void update() |
| Native Methods | Implemented in other languages (C/C++) | Platform-specific operations | public native void nativeOp(); |
| Varargs Methods | Accept variable number of arguments | When number of parameters is unknown | void printAll(String... args) |
public class MethodTypesExamples {
// Instance variable
private int counter = 0;
public static void main(String[] args) {
System.out.println("=== METHOD TYPES DEMONSTRATION ===\n");
// Static method call (no object needed)
staticMethodExample();
// Instance method call (requires object)
MethodTypesExamples obj = new MethodTypesExamples();
obj.instanceMethodExample();
// Varargs method call
printNumbers(1, 2, 3, 4, 5);
printNumbers(); // No arguments
// Method with array parameter
int[] scores = {85, 92, 78, 90, 88};
printArray(scores);
// Method with object return
Person person = createPerson("John", 30);
System.out.println("Created: " + person);
// Method with exception declaration
try {
riskyMethod();
} catch (Exception e) {
System.out.println("Caught exception: " + e.getMessage());
}
}
// ========== STATIC METHODS ==========
// Static method - belongs to class
public static void staticMethodExample() {
System.out.println("This is a static method.");
System.out.println("Can be called without creating an object.");
System.out.println("Cannot access instance variables directly.");
}
// Static utility method
public static double calculateAverage(int[] numbers) {
if (numbers == null || numbers.length == 0) {
return 0.0;
}
int sum = 0;
for (int num : numbers) {
sum += num;
}
return (double) sum / numbers.length;
}
// ========== INSTANCE METHODS ==========
// Instance method - operates on object state
public void instanceMethodExample() {
System.out.println("\nThis is an instance method.");
System.out.println("Requires an object to be called.");
System.out.println("Can access instance variables.");
counter++;
System.out.println("Counter incremented to: " + counter);
}
// Getter method (instance)
public int getCounter() {
return counter;
}
// Setter method (instance)
public void setCounter(int value) {
this.counter = value;
}
// ========== VARARGS METHODS ==========
// Varargs method - variable number of arguments
public static void printNumbers(int... numbers) {
System.out.println("\nVarargs method called with " + numbers.length + " numbers:");
for (int num : numbers) {
System.out.print(num + " ");
}
System.out.println();
}
// ========== ARRAY PARAMETER METHODS ==========
public static void printArray(int[] array) {
System.out.println("\nArray contents:");
for (int i = 0; i < array.length; i++) {
System.out.println("array[" + i + "] = " + array[i]);
}
}
// ========== OBJECT RETURN METHODS ==========
public static Person createPerson(String name, int age) {
return new Person(name, age);
}
// ========== EXCEPTION DECLARATION METHODS ==========
public static void riskyMethod() throws Exception {
System.out.println("\nThis method declares it might throw an exception.");
// Simulating an exceptional condition
if (Math.random() > 0.5) {
throw new Exception("Something went wrong!");
}
System.out.println("Method completed successfully.");
}
// ========== FINAL METHOD ==========
// Final method - cannot be overridden
public final void finalMethodExample() {
System.out.println("This is a final method. Cannot be overridden by subclasses.");
}
// ========== PRIVATE HELPER METHOD ==========
private static void helperMethod() {
System.out.println("This is a private helper method. Only accessible within this class.");
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
// Getter methods
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return name + " (" + age + " years)";
}
}
- Use static methods for utility functions that don't need object state
- Use instance methods when method needs to access or modify object data
- Use private methods for internal implementation details
- Use final methods when implementation should not be changed by subclasses
- Use varargs when number of parameters is variable and unknown
- Use synchronized in multi-threaded environments for thread safety
4. Parameters and Arguments
Parameters define what input a method expects, while arguments are the actual values passed when calling the method.
Method Call Stack Visualization
| Parameter Type | Description | Syntax | Example |
|---|---|---|---|
| Value Parameters | Primitive types passed by value (copy) | (int x, double y) |
modifyValue(x) doesn't change original |
| Reference Parameters | Objects passed by reference | (String str, Person p) |
modifyObject(obj) affects original |
| Varargs | Variable number of arguments | (String... names) |
printAll("A", "B", "C") |
| Array Parameters | Arrays as parameters | (int[] numbers) |
processArray(arr) |
| Default Parameters | Not supported in Java (use overloading) | N/A | Use method overloading instead |
| Final Parameters | Parameters that cannot be modified | (final int x) |
Parameter value cannot be changed |
public class ParametersExamples {
public static void main(String[] args) {
System.out.println("=== PARAMETERS AND ARGUMENTS ===\n");
// Value parameters (primitives)
System.out.println("1. Value Parameters (Primitives):");
int originalValue = 10;
System.out.println("Before method call: originalValue = " + originalValue);
modifyPrimitive(originalValue);
System.out.println("After method call: originalValue = " + originalValue);
System.out.println("Note: Primitive parameters are passed by value (copy)\n");
// Reference parameters (objects)
System.out.println("2. Reference Parameters (Objects):");
Person person = new Person("Alice", 25);
System.out.println("Before method call: " + person);
modifyObject(person);
System.out.println("After method call: " + person);
System.out.println("Note: Object parameters are passed by reference\n");
// Array parameters
System.out.println("3. Array Parameters:");
int[] numbers = {1, 2, 3, 4, 5};
System.out.println("Before: " + java.util.Arrays.toString(numbers));
modifyArray(numbers);
System.out.println("After: " + java.util.Arrays.toString(numbers));
System.out.println("Note: Arrays are objects, so passed by reference\n");
// Varargs parameters
System.out.println("4. Varargs Parameters:");
printAll("Apple", "Banana", "Cherry");
printAll(); // Zero arguments
printAll("Single"); // One argument
System.out.println();
// Multiple parameter types
System.out.println("5. Multiple Parameter Types:");
String result = formatMessage("Hello", 3, true, 42.5);
System.out.println(result);
System.out.println();
// Final parameters
System.out.println("6. Final Parameters:");
processWithFinalParam(100);
System.out.println();
// Method overloading for default parameters simulation
System.out.println("7. Simulating Default Parameters with Overloading:");
greet("John");
greet("Jane", "Good evening");
}
// ========== VALUE PARAMETERS (Primitives) ==========
public static void modifyPrimitive(int value) {
System.out.println(" Inside method - received value: " + value);
value = 999; // Modifies local copy only
System.out.println(" Inside method - changed to: " + value);
}
// ========== REFERENCE PARAMETERS (Objects) ==========
public static void modifyObject(Person p) {
System.out.println(" Inside method - received: " + p);
p.setAge(30); // Modifies the original object
System.out.println(" Inside method - modified age to 30");
}
// ========== ARRAY PARAMETERS ==========
public static void modifyArray(int[] arr) {
System.out.println(" Inside method - modifying array");
for (int i = 0; i < arr.length; i++) {
arr[i] = arr[i] * 2; // Modifies original array
}
}
// ========== VARARGS PARAMETERS ==========
public static void printAll(String... items) {
System.out.print(" Received " + items.length + " items: ");
for (String item : items) {
System.out.print(item + " ");
}
System.out.println();
}
// Varargs must be last parameter
public static void printWithPrefix(String prefix, String... items) {
System.out.print(prefix + ": ");
for (String item : items) {
System.out.print(item + " ");
}
System.out.println();
}
// ========== MULTIPLE PARAMETER TYPES ==========
public static String formatMessage(String text, int count, boolean flag, double value) {
return String.format("Text: %s, Count: %d, Flag: %b, Value: %.2f",
text, count, flag, value);
}
// ========== FINAL PARAMETERS ==========
public static void processWithFinalParam(final int value) {
System.out.println(" Received final parameter: " + value);
// value = 200; // COMPILATION ERROR: Cannot assign value to final parameter
int result = value * 2;
System.out.println(" Result: " + result);
}
// ========== METHOD OVERLOADING FOR DEFAULT PARAMETERS ==========
// Simulating default parameters (not available in Java)
public static void greet(String name) {
greet(name, "Hello"); // Call overloaded version with default
}
public static void greet(String name, String greeting) {
System.out.println(greeting + ", " + name + "!");
}
// ========== PARAMETER VALIDATION ==========
public static double calculateBMI(double weight, double height) {
// Validate parameters
if (weight <= 0) {
throw new IllegalArgumentException("Weight must be positive");
}
if (height <= 0) {
throw new IllegalArgumentException("Height must be positive");
}
return weight / (height * height);
}
// ========== RETURNING MULTIPLE VALUES ==========
// Java doesn't support multiple return values directly
// Options: Return array, return object, or use parameter mutation
// Option 1: Return array
public static int[] getMinMax(int[] numbers) {
if (numbers == null || numbers.length == 0) {
return new int[]{0, 0};
}
int min = numbers[0];
int max = numbers[0];
for (int num : numbers) {
if (num < min) min = num;
if (num > max) max = num;
}
return new int[]{min, max};
}
// Option 2: Return custom object
public static MinMaxResult findMinMax(int[] numbers) {
if (numbers == null || numbers.length == 0) {
return new MinMaxResult(0, 0);
}
int min = numbers[0];
int max = numbers[0];
for (int num : numbers) {
if (num < min) min = num;
if (num > max) max = num;
}
return new MinMaxResult(min, max);
}
// Option 3: Modify parameters (pass by reference)
public static void findMinMax(int[] numbers, int[] result) {
if (numbers == null || numbers.length == 0) {
result[0] = 0;
result[1] = 0;
return;
}
int min = numbers[0];
int max = numbers[0];
for (int num : numbers) {
if (num < min) min = num;
if (num > max) max = num;
}
result[0] = min;
result[1] = max;
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() { return name; }
public int getAge() { return age; }
public void setAge(int age) { this.age = age; }
@Override
public String toString() {
return name + " (" + age + " years)";
}
}
// Helper class for returning multiple values
class MinMaxResult {
private int min;
private int max;
public MinMaxResult(int min, int max) {
this.min = min;
this.max = max;
}
public int getMin() { return min; }
public int getMax() { return max; }
@Override
public String toString() {
return "Min: " + min + ", Max: " + max;
}
}
5. Return Types and Values
The return type specifies what type of value a method returns. Methods can return primitive types, objects, arrays, or nothing (void).
| Return Type | Description | Example Method | Usage |
|---|---|---|---|
void |
No return value (performs action only) | public void printMessage() |
When method only performs side effects |
Primitive Types |
int, double, boolean, char, etc. | public int calculateSum() |
When method computes a simple value |
Object Types |
String, custom classes, Object | public String getName() |
When method returns complex data |
Arrays |
Array of any type | public int[] getNumbers() |
When method returns collection of values |
null |
Can be returned for object types | return null; |
When no valid object to return |
this |
Returns current object instance | return this; |
For method chaining (builder pattern) |
public class ReturnTypesExamples {
public static void main(String[] args) {
System.out.println("=== RETURN TYPES AND VALUES ===\n");
// 1. Void methods (no return)
System.out.println("1. Void Methods:");
printWelcomeMessage();
System.out.println();
// 2. Primitive return types
System.out.println("2. Primitive Return Types:");
int sum = add(10, 20);
System.out.println("Sum: " + sum);
double average = calculateAverage(85, 92, 78);
System.out.println("Average: " + average);
boolean isAdult = checkAge(25);
System.out.println("Is adult? " + isAdult);
System.out.println();
// 3. Object return types
System.out.println("3. Object Return Types:");
String fullName = getFullName("John", "Doe");
System.out.println("Full Name: " + fullName);
Person person = createPerson("Alice", 30);
System.out.println("Created: " + person);
System.out.println();
// 4. Array return types
System.out.println("4. Array Return Types:");
int[] squares = generateSquares(5);
System.out.println("Squares: " + java.util.Arrays.toString(squares));
String[] colors = getColors();
System.out.println("Colors: " + java.util.Arrays.toString(colors));
System.out.println();
// 5. Returning null
System.out.println("5. Returning null:");
String result = findByName("Unknown");
System.out.println("Search result: " + (result == null ? "Not found" : result));
System.out.println();
// 6. Method chaining with 'this' return
System.out.println("6. Method Chaining:");
Calculator calc = new Calculator(10);
calc.add(5).multiply(2).subtract(3).printResult();
System.out.println();
// 7. Early return
System.out.println("7. Early Return:");
int number = -5;
String validation = validateNumber(number);
System.out.println("Validation for " + number + ": " + validation);
number = 42;
validation = validateNumber(number);
System.out.println("Validation for " + number + ": " + validation);
System.out.println();
// 8. Returning from void methods
System.out.println("8. Returning from void methods:");
processNumber(0);
processNumber(10);
System.out.println();
// 9. Return in try-catch-finally
System.out.println("9. Return in try-catch-finally:");
int value = riskyOperation();
System.out.println("Result: " + value);
}
// ========== VOID METHODS ==========
public static void printWelcomeMessage() {
System.out.println("Welcome to Java Methods Tutorial!");
System.out.println("This method performs an action but returns nothing.");
// No return statement needed (or can use empty return)
// return; // Optional
}
// ========== PRIMITIVE RETURN TYPES ==========
public static int add(int a, int b) {
return a + b;
}
public static double calculateAverage(int a, int b, int c) {
return (a + b + c) / 3.0;
}
public static boolean checkAge(int age) {
return age >= 18;
}
// ========== OBJECT RETURN TYPES ==========
public static String getFullName(String firstName, String lastName) {
return firstName + " " + lastName;
}
public static Person createPerson(String name, int age) {
return new Person(name, age);
}
// ========== ARRAY RETURN TYPES ==========
public static int[] generateSquares(int n) {
int[] squares = new int[n];
for (int i = 0; i < n; i++) {
squares[i] = (i + 1) * (i + 1);
}
return squares;
}
public static String[] getColors() {
return new String[]{"Red", "Green", "Blue", "Yellow"};
}
// ========== RETURNING NULL ==========
public static String findByName(String name) {
String[] database = {"John", "Alice", "Bob"};
for (String entry : database) {
if (entry.equals(name)) {
return entry;
}
}
return null; // Not found
}
// ========== EARLY RETURN ==========
public static String validateNumber(int number) {
// Early return for invalid cases
if (number < 0) {
return "Error: Number cannot be negative";
}
if (number > 100) {
return "Error: Number cannot exceed 100";
}
// Main logic for valid cases
if (number % 2 == 0) {
return "Valid: Even number";
} else {
return "Valid: Odd number";
}
}
// ========== RETURNING FROM VOID METHODS ==========
public static void processNumber(int num) {
if (num == 0) {
System.out.println("Number is zero, exiting early");
return; // Exit method early
}
System.out.println("Processing number: " + num);
// More processing...
}
// ========== RETURN IN TRY-CATCH-FINALLY ==========
public static int riskyOperation() {
try {
System.out.println("Trying risky operation...");
// Simulate risky code
if (Math.random() > 0.5) {
throw new RuntimeException("Something went wrong!");
}
return 42; // Return from try block
} catch (RuntimeException e) {
System.out.println("Caught exception: " + e.getMessage());
return -1; // Return from catch block
} finally {
System.out.println("Finally block always executes");
// Note: If finally has return, it overrides try/catch returns
// return 0; // This would override previous returns
}
}
// ========== RETURNING MULTIPLE VALUES ==========
// Using array
public static int[] getStatistics(int[] numbers) {
if (numbers == null || numbers.length == 0) {
return new int[]{0, 0, 0};
}
int sum = 0;
int min = numbers[0];
int max = numbers[0];
for (int num : numbers) {
sum += num;
if (num < min) min = num;
if (num > max) max = num;
}
return new int[]{sum, min, max};
}
// Using custom class (better approach)
public static Statistics calculateStats(int[] numbers) {
if (numbers == null || numbers.length == 0) {
return new Statistics(0, 0, 0, 0);
}
int sum = 0;
int min = numbers[0];
int max = numbers[0];
for (int num : numbers) {
sum += num;
if (num < min) min = num;
if (num > max) max = num;
}
double average = (double) sum / numbers.length;
return new Statistics(sum, min, max, average);
}
}
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
@Override
public String toString() {
return name + " (" + age + " years)";
}
}
// For method chaining example
class Calculator {
private int result;
public Calculator(int initial) {
this.result = initial;
}
public Calculator add(int value) {
result += value;
return this; // Return this for chaining
}
public Calculator subtract(int value) {
result -= value;
return this;
}
public Calculator multiply(int value) {
result *= value;
return this;
}
public void printResult() {
System.out.println("Result: " + result);
}
}
// For returning multiple values
class Statistics {
private int sum;
private int min;
private int max;
private double average;
public Statistics(int sum, int min, int max, double average) {
this.sum = sum;
this.min = min;
this.max = max;
this.average = average;
}
@Override
public String toString() {
return String.format("Sum: %d, Min: %d, Max: %d, Avg: %.2f",
sum, min, max, average);
}
}
6. Method Overloading
Method overloading allows multiple methods with the same name but different parameters. It enables polymorphism at compile-time.
Overloading Rules
What CAN Change
- Number of parameters
- Type of parameters
- Order of parameters
What CANNOT Change
- Method name only
- Return type only
- Access modifier only
- Exception list only
public class MethodOverloadingExamples {
public static void main(String[] args) {
System.out.println("=== METHOD OVERLOADING ===\n");
// Example 1: Different number of parameters
System.out.println("1. Different Number of Parameters:");
System.out.println("add(10, 20) = " + add(10, 20));
System.out.println("add(10, 20, 30) = " + add(10, 20, 30));
System.out.println("add(10, 20, 30, 40) = " + add(10, 20, 30, 40));
System.out.println();
// Example 2: Different types of parameters
System.out.println("2. Different Types of Parameters:");
System.out.println("multiply(5, 3) = " + multiply(5, 3));
System.out.println("multiply(2.5, 4.2) = " + multiply(2.5, 4.2));
System.out.println("multiply(\"Hello\", 3) = " + multiply("Hello", 3));
System.out.println();
// Example 3: Different order of parameters
System.out.println("3. Different Order of Parameters:");
printInfo("John", 30);
printInfo(25, "Alice");
System.out.println();
// Example 4: Overloading with type promotion
System.out.println("4. Type Promotion in Overloading:");
testPromotion(10); // int -> long
testPromotion(10.0f); // float -> double
testPromotion('A'); // char -> int
System.out.println();
// Example 5: Overloading with varargs
System.out.println("5. Overloading with Varargs:");
display("Hello");
display("Hello", "World");
display("Hello", "World", "Java");
System.out.println();
// Example 6: Ambiguity in overloading
System.out.println("6. Ambiguity Examples (commented out):");
// ambiguousCall(10, 20); // COMPILATION ERROR: Ambiguous method call
System.out.println();
// Example 7: Real-world example - Calculator
System.out.println("7. Real-world Example - Calculator:");
Calculator calc = new Calculator();
System.out.println("Int addition: " + calc.add(5, 3));
System.out.println("Double addition: " + calc.add(5.5, 3.2));
System.out.println("Three numbers: " + calc.add(1, 2, 3));
System.out.println("Array sum: " + calc.add(new int[]{1, 2, 3, 4, 5}));
System.out.println();
// Example 8: Constructor overloading
System.out.println("8. Constructor Overloading:");
Student student1 = new Student();
Student student2 = new Student("John Doe");
Student student3 = new Student("Alice Smith", "CS101");
Student student4 = new Student("Bob Johnson", "MA202", 3.8);
System.out.println(student1);
System.out.println(student2);
System.out.println(student3);
System.out.println(student4);
}
// ========== OVERLOADING BY PARAMETER COUNT ==========
// Two parameters
public static int add(int a, int b) {
System.out.println(" Called: add(int, int)");
return a + b;
}
// Three parameters
public static int add(int a, int b, int c) {
System.out.println(" Called: add(int, int, int)");
return a + b + c;
}
// Four parameters
public static int add(int a, int b, int c, int d) {
System.out.println(" Called: add(int, int, int, int)");
return a + b + c + d;
}
// Varargs version (handles any number)
public static int add(int... numbers) {
System.out.println(" Called: add(int...) with " + numbers.length + " numbers");
int sum = 0;
for (int num : numbers) {
sum += num;
}
return sum;
}
// ========== OVERLOADING BY PARAMETER TYPE ==========
// Integer multiplication
public static int multiply(int a, int b) {
System.out.println(" Called: multiply(int, int)");
return a * b;
}
// Double multiplication
public static double multiply(double a, double b) {
System.out.println(" Called: multiply(double, double)");
return a * b;
}
// String repetition
public static String multiply(String str, int times) {
System.out.println(" Called: multiply(String, int)");
return str.repeat(times);
}
// ========== OVERLOADING BY PARAMETER ORDER ==========
public static void printInfo(String name, int age) {
System.out.println(" Called: printInfo(String, int)");
System.out.println(" Name: " + name + ", Age: " + age);
}
public static void printInfo(int age, String name) {
System.out.println(" Called: printInfo(int, String)");
System.out.println(" Age: " + age + ", Name: " + name);
}
// ========== TYPE PROMOTION IN OVERLOADING ==========
public static void testPromotion(long num) {
System.out.println(" Called: testPromotion(long) with value: " + num);
}
public static void testPromotion(double num) {
System.out.println(" Called: testPromotion(double) with value: " + num);
}
// Note: No testPromotion(int) method exists
// int will be promoted to long
// float will be promoted to double
// ========== VARARGS OVERLOADING ==========
public static void display(String message) {
System.out.println(" Called: display(String)");
System.out.println(" Message: " + message);
}
public static void display(String message1, String message2) {
System.out.println(" Called: display(String, String)");
System.out.println(" Messages: " + message1 + ", " + message2);
}
public static void display(String... messages) {
System.out.println(" Called: display(String...) with " + messages.length + " messages");
for (String msg : messages) {
System.out.println(" - " + msg);
}
}
// ========== AMBIGUITY IN OVERLOADING ==========
public static void ambiguousCall(int a, double b) {
System.out.println("int, double version");
}
public static void ambiguousCall(double a, int b) {
System.out.println("double, int version");
}
// Calling ambiguousCall(10, 20) causes compilation error
// Both methods are equally applicable
}
// ========== REAL-WORLD CALCULATOR EXAMPLE ==========
class Calculator {
// Overloaded add methods
public int add(int a, int b) {
return a + b;
}
public double add(double a, double b) {
return a + b;
}
public int add(int a, int b, int c) {
return a + b + c;
}
public int add(int[] numbers) {
int sum = 0;
for (int num : numbers) {
sum += num;
}
return sum;
}
// Overloaded max methods
public int max(int a, int b) {
return (a > b) ? a : b;
}
public int max(int a, int b, int c) {
return max(max(a, b), c);
}
public double max(double a, double b) {
return (a > b) ? a : b;
}
// Overloaded print methods
public void print(int value) {
System.out.println("Integer: " + value);
}
public void print(double value) {
System.out.println("Double: " + value);
}
public void print(String value) {
System.out.println("String: " + value);
}
public void print(int[] array) {
System.out.print("Array: ");
for (int num : array) {
System.out.print(num + " ");
}
System.out.println();
}
}
// ========== CONSTRUCTOR OVERLOADING EXAMPLE ==========
class Student {
private String name;
private String course;
private double gpa;
// Default constructor
public Student() {
this.name = "Unknown";
this.course = "Not enrolled";
this.gpa = 0.0;
}
// Constructor with name only
public Student(String name) {
this.name = name;
this.course = "Not enrolled";
this.gpa = 0.0;
}
// Constructor with name and course
public Student(String name, String course) {
this.name = name;
this.course = course;
this.gpa = 0.0;
}
// Full constructor
public Student(String name, String course, double gpa) {
this.name = name;
this.course = course;
this.gpa = gpa;
}
// Copy constructor
public Student(Student other) {
this.name = other.name;
this.course = other.course;
this.gpa = other.gpa;
}
@Override
public String toString() {
return String.format("%s - %s (GPA: %.2f)", name, course, gpa);
}
}
- Overloaded methods MUST have different parameter lists
- Return type alone is NOT sufficient for overloading
- Access modifiers alone are NOT sufficient for overloading
- Exception lists alone are NOT sufficient for overloading
- Java compiler uses method signature (name + parameters) to resolve calls
- Type promotion happens when exact match not found (int → long → float → double)
- Avoid ambiguous overloading that causes compilation errors
7. Recursion
Recursion occurs when a method calls itself. It's a powerful technique for solving problems that can be broken down into smaller, similar subproblems.
Recursive Call Stack - factorial(4)
| Recursion Type | Description | When to Use | Example |
|---|---|---|---|
| Direct Recursion | Method calls itself directly | Simple recursive problems | factorial(n) |
| Indirect Recursion | Method A calls B, B calls A | Mutually recursive algorithms | isEven() ↔ isOdd() |
| Tail Recursion | Recursive call is last operation | Can be optimized by compiler | factorialTail(n, acc) |
| Tree Recursion | Multiple recursive calls | Tree traversal, Fibonacci | fibonacci(n) |
| Nested Recursion | Recursive call has recursive parameter | Complex mathematical functions | Ackermann(m, n) |
public class RecursionExamples {
public static void main(String[] args) {
System.out.println("=== RECURSION EXAMPLES ===\n");
// 1. Factorial (classic recursion)
System.out.println("1. Factorial:");
for (int i = 0; i <= 5; i++) {
System.out.println(i + "! = " + factorial(i));
}
System.out.println();
// 2. Fibonacci sequence
System.out.println("2. Fibonacci Sequence:");
for (int i = 0; i <= 6; i++) {
System.out.println("fib(" + i + ") = " + fibonacci(i));
}
System.out.println();
// 3. Tail recursion factorial
System.out.println("3. Tail Recursion Factorial:");
System.out.println("5! = " + factorialTail(5, 1));
System.out.println();
// 4. Indirect recursion
System.out.println("4. Indirect Recursion:");
System.out.println("isEven(4): " + isEven(4));
System.out.println("isOdd(4): " + isOdd(4));
System.out.println("isEven(5): " + isEven(5));
System.out.println("isOdd(5): " + isOdd(5));
System.out.println();
// 5. Power calculation
System.out.println("5. Power Calculation:");
System.out.println("2^5 = " + power(2, 5));
System.out.println("3^4 = " + power(3, 4));
System.out.println("5^0 = " + power(5, 0));
System.out.println();
// 6. Sum of digits
System.out.println("6. Sum of Digits:");
System.out.println("sumDigits(12345) = " + sumDigits(12345));
System.out.println("sumDigits(987) = " + sumDigits(987));
System.out.println();
// 7. GCD calculation
System.out.println("7. GCD (Euclidean Algorithm):");
System.out.println("gcd(48, 18) = " + gcd(48, 18));
System.out.println("gcd(56, 98) = " + gcd(56, 98));
System.out.println();
// 8. Palindrome check
System.out.println("8. Palindrome Check:");
System.out.println("isPalindrome(\"racecar\"): " + isPalindrome("racecar"));
System.out.println("isPalindrome(\"hello\"): " + isPalindrome("hello"));
System.out.println("isPalindrome(\"madam\"): " + isPalindrome("madam"));
System.out.println();
// 9. Binary search (recursive)
System.out.println("9. Recursive Binary Search:");
int[] sortedArray = {2, 5, 8, 12, 16, 23, 38, 45, 56, 72};
int key = 23;
int index = binarySearch(sortedArray, key, 0, sortedArray.length - 1);
System.out.println("Search for " + key + ": index = " + index);
System.out.println();
// 10. Tower of Hanoi
System.out.println("10. Tower of Hanoi:");
towerOfHanoi(3, 'A', 'C', 'B');
System.out.println();
// 11. Tree traversal simulation
System.out.println("11. Binary Tree Traversal:");
TreeNode root = createSampleTree();
System.out.print("Pre-order: ");
preOrder(root);
System.out.println();
System.out.print("In-order: ");
inOrder(root);
System.out.println();
System.out.print("Post-order: ");
postOrder(root);
System.out.println();
}
// ========== FACTORIAL (Classic Recursion) ==========
public static int factorial(int n) {
// Base case
if (n <= 1) {
return 1;
}
// Recursive case
return n * factorial(n - 1);
}
// ========== FIBONACCI ==========
public static int fibonacci(int n) {
// Base cases
if (n == 0) return 0;
if (n == 1) return 1;
// Recursive case (tree recursion)
return fibonacci(n - 1) + fibonacci(n - 2);
}
// ========== TAIL RECURSION FACTORIAL ==========
public static int factorialTail(int n, int accumulator) {
// Base case
if (n <= 1) {
return accumulator;
}
// Tail recursive call
return factorialTail(n - 1, n * accumulator);
}
// ========== INDIRECT RECURSION ==========
public static boolean isEven(int n) {
if (n == 0) return true;
return isOdd(n - 1);
}
public static boolean isOdd(int n) {
if (n == 0) return false;
return isEven(n - 1);
}
// ========== POWER CALCULATION ==========
public static int power(int base, int exponent) {
// Base case
if (exponent == 0) return 1;
// Recursive case
return base * power(base, exponent - 1);
}
// Optimized power (logarithmic time)
public static int powerFast(int base, int exponent) {
if (exponent == 0) return 1;
int halfPower = powerFast(base, exponent / 2);
if (exponent % 2 == 0) {
return halfPower * halfPower;
} else {
return base * halfPower * halfPower;
}
}
// ========== SUM OF DIGITS ==========
public static int sumDigits(int number) {
// Base case
if (number == 0) return 0;
// Recursive case
return (number % 10) + sumDigits(number / 10);
}
// ========== GCD (EUCLIDEAN ALGORITHM) ==========
public static int gcd(int a, int b) {
// Base case
if (b == 0) return a;
// Recursive case
return gcd(b, a % b);
}
// ========== PALINDROME CHECK ==========
public static boolean isPalindrome(String str) {
return isPalindromeHelper(str, 0, str.length() - 1);
}
private static boolean isPalindromeHelper(String str, int left, int right) {
// Base cases
if (left >= right) return true;
if (str.charAt(left) != str.charAt(right)) return false;
// Recursive case
return isPalindromeHelper(str, left + 1, right - 1);
}
// ========== BINARY SEARCH ==========
public static int binarySearch(int[] array, int key, int low, int high) {
// Base case: element not found
if (low > high) return -1;
int mid = (low + high) / 2;
// Base case: element found
if (array[mid] == key) return mid;
// Recursive cases
if (array[mid] > key) {
return binarySearch(array, key, low, mid - 1);
} else {
return binarySearch(array, key, mid + 1, high);
}
}
// ========== TOWER OF HANOI ==========
public static void towerOfHanoi(int disks, char source, char destination, char auxiliary) {
// Base case
if (disks == 1) {
System.out.println("Move disk 1 from " + source + " to " + destination);
return;
}
// Move n-1 disks from source to auxiliary
towerOfHanoi(disks - 1, source, auxiliary, destination);
// Move the nth disk from source to destination
System.out.println("Move disk " + disks + " from " + source + " to " + destination);
// Move n-1 disks from auxiliary to destination
towerOfHanoi(disks - 1, auxiliary, destination, source);
}
// ========== BINARY TREE TRAVERSAL ==========
static class TreeNode {
int value;
TreeNode left;
TreeNode right;
TreeNode(int value) {
this.value = value;
}
}
public static TreeNode createSampleTree() {
TreeNode root = new TreeNode(1);
root.left = new TreeNode(2);
root.right = new TreeNode(3);
root.left.left = new TreeNode(4);
root.left.right = new TreeNode(5);
root.right.left = new TreeNode(6);
root.right.right = new TreeNode(7);
return root;
}
public static void preOrder(TreeNode node) {
if (node == null) return;
System.out.print(node.value + " ");
preOrder(node.left);
preOrder(node.right);
}
public static void inOrder(TreeNode node) {
if (node == null) return;
inOrder(node.left);
System.out.print(node.value + " ");
inOrder(node.right);
}
public static void postOrder(TreeNode node) {
if (node == null) return;
postOrder(node.left);
postOrder(node.right);
System.out.print(node.value + " ");
}
// ========== RECURSION VS ITERATION ==========
// Iterative factorial
public static int factorialIterative(int n) {
int result = 1;
for (int i = 1; i <= n; i++) {
result *= i;
}
return result;
}
// Iterative Fibonacci
public static int fibonacciIterative(int n) {
if (n <= 1) return n;
int prev = 0;
int current = 1;
for (int i = 2; i <= n; i++) {
int next = prev + current;
prev = current;
current = next;
}
return current;
}
// ========== COMMON RECURSION MISTAKES ==========
// Missing base case (infinite recursion)
public static void infiniteRecursion() {
infiniteRecursion(); // StackOverflowError!
}
// Wrong base case
public static int wrongFactorial(int n) {
if (n == 0) {
return 0; // WRONG! Should return 1
}
return n * wrongFactorial(n - 1);
}
// Not progressing toward base case
public static void noProgress(int n) {
if (n <= 0) return;
noProgress(n); // Never changes n - infinite recursion!
}
}
- Always define a base case to prevent infinite recursion
- Ensure each recursive call progresses toward the base case
- Consider stack overflow for deep recursion (use iteration or tail recursion)
- Use recursion for problems with natural recursive structure (trees, divide-and-conquer)
- Consider memoization for overlapping subproblems (like Fibonacci)
- Use tail recursion when possible for potential compiler optimization
- Always test with edge cases (0, 1, negative numbers)
8. Method Design Principles and Best Practices
- Methods too long: Should do one thing well (Single Responsibility)
- Too many parameters: Consider using objects or builder pattern
- Side effects: Methods should have predictable behavior
- Poor naming: Method names should indicate purpose
- No parameter validation: Always validate input parameters
- Deep nesting: Makes code hard to read and maintain
- Ignoring exceptions: Handle or declare checked exceptions
Method Design Principles
- Single Responsibility: One method, one purpose
- Command-Query Separation: Methods should either do something or answer something, not both
- Fail Fast: Validate parameters early
- Defensive Programming: Handle nulls and edge cases
- Law of Demeter: Don't talk to strangers (minimize dependencies)
- Keep It Simple: Simple, clear, maintainable code
- Documentation: Javadoc for public methods
Performance Tips
- Avoid unnecessary object creation in loops
- Use primitive types when possible
- Consider method inlining for small, frequently called methods
- Use final for methods that shouldn't be overridden
- Cache results of expensive computations
- Use StringBuilder for string concatenation in loops
- Profile before optimizing
import java.util.Objects;
/**
* Demonstration of method design best practices.
* This class shows how to write clean, maintainable methods.
*/
public class MethodBestPractices {
/**
* Calculates the area of a rectangle.
*
* @param width the width of the rectangle (must be positive)
* @param height the height of the rectangle (must be positive)
* @return the area of the rectangle
* @throws IllegalArgumentException if width or height is not positive
*/
public static double calculateRectangleArea(double width, double height) {
// 1. Validate parameters early (Fail Fast)
validateDimensions(width, height);
// 2. Single responsibility - just calculates area
return width * height;
}
/**
* Validates rectangle dimensions.
* Private helper method for parameter validation.
*/
private static void validateDimensions(double width, double height) {
if (width <= 0) {
throw new IllegalArgumentException("Width must be positive: " + width);
}
if (height <= 0) {
throw new IllegalArgumentException("Height must be positive: " + height);
}
}
/**
* Formats a user's full name.
* Example of Command-Query Separation: returns formatted name without side effects.
*/
public static String formatFullName(String firstName, String lastName) {
// Defensive programming: handle nulls
firstName = Objects.toString(firstName, "").trim();
lastName = Objects.toString(lastName, "").trim();
if (firstName.isEmpty() && lastName.isEmpty()) {
return "Unknown";
}
return String.format("%s %s", capitalize(firstName), capitalize(lastName)).trim();
}
/**
* Capitalizes the first letter of a string.
* Small, focused helper method.
*/
private static String capitalize(String str) {
if (str == null || str.isEmpty()) {
return "";
}
return str.substring(0, 1).toUpperCase() + str.substring(1).toLowerCase();
}
/**
* Processes a list of items.
* Example of method with reasonable parameter count.
*/
public static void processItems(String[] items, boolean shouldSort, int maxItems) {
Objects.requireNonNull(items, "Items array cannot be null");
// Copy array to avoid modifying input (defensive copy)
String[] workingCopy = items.clone();
if (shouldSort) {
java.util.Arrays.sort(workingCopy);
}
// Process limited number of items
int limit = Math.min(workingCopy.length, maxItems);
for (int i = 0; i < limit; i++) {
processItem(workingCopy[i]);
}
}
/**
* Processes a single item.
* Extracted from loop for clarity and reusability.
*/
private static void processItem(String item) {
System.out.println("Processing: " + item);
// Actual processing logic here
}
/**
* Builder pattern for handling many parameters.
*/
public static class UserProfileBuilder {
private String username;
private String email;
private int age;
private String country;
public UserProfileBuilder setUsername(String username) {
this.username = Objects.requireNonNull(username, "Username cannot be null");
return this;
}
public UserProfileBuilder setEmail(String email) {
this.email = Objects.requireNonNull(email, "Email cannot be null");
return this;
}
public UserProfileBuilder setAge(int age) {
if (age < 0 || age > 150) {
throw new IllegalArgumentException("Invalid age: " + age);
}
this.age = age;
return this;
}
public UserProfileBuilder setCountry(String country) {
this.country = country; // Optional parameter
return this;
}
public UserProfile build() {
return new UserProfile(username, email, age, country);
}
}
/**
* Immutable data class.
*/
public static class UserProfile {
private final String username;
private final String email;
private final int age;
private final String country;
public UserProfile(String username, String email, int age, String country) {
this.username = Objects.requireNonNull(username);
this.email = Objects.requireNonNull(email);
this.age = age;
this.country = country; // Can be null
}
// Getter methods only (no setters - immutable)
public String getUsername() { return username; }
public String getEmail() { return email; }
public int getAge() { return age; }
public String getCountry() { return country; }
}
/**
* Example of method that avoids deep nesting.
*/
public static void processTransaction(Transaction tx) {
// Early returns to avoid deep nesting
if (tx == null) {
System.out.println("Transaction is null");
return;
}
if (!tx.isValid()) {
System.out.println("Invalid transaction");
return;
}
if (tx.getAmount() <= 0) {
System.out.println("Amount must be positive");
return;
}
// Main processing logic (no nesting)
executeTransaction(tx);
logTransaction(tx);
sendNotification(tx);
}
// Mock classes for demonstration
static class Transaction {
boolean isValid() { return true; }
double getAmount() { return 100.0; }
}
static void executeTransaction(Transaction tx) {}
static void logTransaction(Transaction tx) {}
static void sendNotification(Transaction tx) {}
/**
* Example of good method naming.
*/
public static class GoodNamingExamples {
// GOOD: Clear what the method does
public double calculateTax(double income, String countryCode) {
// Implementation
return 0.0;
}
// GOOD: Boolean methods should read like questions
public boolean isValidEmail(String email) {
return email != null && email.contains("@");
}
// GOOD: Command methods should be verbs
public void saveToDatabase(UserProfile user) {
// Implementation
}
// GOOD: Getter methods start with "get"
public String getUserName() {
return "John";
}
// GOOD: Setter methods start with "set"
public void setUserName(String name) {
// Implementation
}
}
/**
* Example of method documentation (Javadoc).
*/
public static class DocumentedMethods {
/**
* Calculates the body mass index (BMI) for a person.
*
* BMI is calculated as weight in kilograms divided by
* the square of height in meters.
*
* @param weightKg the weight in kilograms (must be positive)
* @param heightM the height in meters (must be positive)
* @return the calculated BMI value
* @throws IllegalArgumentException if weight or height is not positive
* @see BMI on Wikipedia
*/
public static double calculateBMI(double weightKg, double heightM) {
if (weightKg <= 0) {
throw new IllegalArgumentException("Weight must be positive: " + weightKg);
}
if (heightM <= 0) {
throw new IllegalArgumentException("Height must be positive: " + heightM);
}
return weightKg / (heightM * heightM);
}
/**
* Determines the BMI category based on the calculated BMI value.
*
* @param bmi the BMI value to categorize
* @return the BMI category as a string
*/
public static String getBMICategory(double bmi) {
if (bmi < 18.5) return "Underweight";
if (bmi < 25) return "Normal weight";
if (bmi < 30) return "Overweight";
return "Obese";
}
}
public static void main(String[] args) {
System.out.println("=== METHOD DESIGN BEST PRACTICES ===\n");
// Example 1: Using the builder pattern
System.out.println("1. Builder Pattern Example:");
UserProfile user = new UserProfileBuilder()
.setUsername("johndoe")
.setEmail("john@example.com")
.setAge(30)
.setCountry("USA")
.build();
System.out.println("Created user: " + user.getUsername());
System.out.println();
// Example 2: Good method naming
System.out.println("2. Method Naming Examples:");
GoodNamingExamples examples = new GoodNamingExamples();
System.out.println("Is valid email? " + examples.isValidEmail("test@example.com"));
System.out.println();
// Example 3: Documented methods
System.out.println("3. Documented Methods:");
double bmi = DocumentedMethods.calculateBMI(70, 1.75);
String category = DocumentedMethods.getBMICategory(bmi);
System.out.printf("BMI: %.2f (%s)%n", bmi, category);
System.out.println();
// Example 4: Error handling
System.out.println("4. Proper Error Handling:");
try {
calculateRectangleArea(-5, 10);
} catch (IllegalArgumentException e) {
System.out.println("Caught expected error: " + e.getMessage());
}
System.out.println();
// Example 5: Formatting names
System.out.println("5. Name Formatting:");
System.out.println("Formatted: " + formatFullName("john", "doe"));
System.out.println("Formatted: " + formatFullName(null, "smith"));
System.out.println("Formatted: " + formatFullName("", ""));
}
}