Java Programming

Chapter 4
Methods, Arrays, Strings, and Modular Programming

Certificate in Java-Python-Programming Languages
Java Programming   •   Certificate

Methods, Arrays, Strings, and Modular Programming

Introduction

A Java program becomes useful beyond a single demonstration when it can be divided into understandable, reusable, and testable units. Methods provide those units. Arrays organize collections of values, strings represent textual information, and modular programming combines these features into coherent program components. Together, they support abstraction: a programmer can use a well-designed operation without needing to know every implementation detail inside it.

This chapter develops these ideas using standard Java syntax and conventions. Examples assume a modern Java Development Kit and a class-based program structure.

Learning Objectives

By the end of this chapter, you should be able to:

  • declare, call, and document Java methods;
  • use parameters, return values, local variables, and scope correctly;
  • distinguish method overloading from overriding;
  • explain Java's pass-by-value behavior;
  • use recursion appropriately for simple problems;
  • create, traverse, copy, and modify one- and multidimensional arrays;
  • manipulate strings safely and efficiently;
  • use selected utility classes such as Arrays, Math, and StringBuilder;
  • design a small program as cooperating modules;
  • identify limitations, boundary cases, and maintainability risks.

1. Methods as Units of Abstraction

A method is a named block of statements that performs an operation. A method may accept input through parameters and may produce a result through a return value.

public static double calculateArea(double radius) {
    return Math.PI * radius * radius;
}

This declaration contains several elements:

  • public: an access modifier;
  • static: the method belongs to the class rather than to an object instance;
  • double: the return type;
  • calculateArea: the method name;
  • double radius: a formal parameter;
  • return: the statement that supplies the result.

A call supplies an argument:

double area = calculateArea(2.5);

The parameter is a variable inside the method. The argument is the value supplied by the caller. The method's calculation uses the geometric formula for a circle's area, A = πr2, where A is area and r is radius. This formula applies when the radius is expressed in a consistent unit; the resulting area is in the square of that unit.

A method that produces no result uses void:

public static void printWelcome(String name) {
    System.out.println("Welcome, " + name);
}

A void method may still receive parameters and cause an observable effect, such as printing or modifying an object.

Method Design Principles

A good method generally has one clear responsibility. Its name should describe an action or a result, such as findMaximum, validatePassword, or calculateTotal. Methods should be short enough to understand, but not arbitrarily fragmented. A useful method also documents assumptions: for example, whether a negative quantity is valid or whether an empty array is permitted.

Methods can be called from other methods, including main:

public static void main(String[] args) {
    double total = addTax(100.0, 0.15);
    System.out.println(total);
}

public static double addTax(double price, double rate) {
    return price + price * rate;
}

Here rate is represented as a decimal fraction. If rate = 0.15, the calculation assumes a 15% rate and that price and the resulting tax are expressed in the same currency unit.

2. Parameters, Return Values, and Scope

Java passes every argument by value. For primitive types, the value itself is copied. For reference types, the reference value is copied; both the caller and method can therefore refer to the same object, but assigning a new object to the parameter does not change the caller's reference.

public static void changeNumber(int value) {
    value = 99;
}

public static void changeFirst(int[] values) {
    values[0] = 99;
}

After changeNumber(number), the caller's primitive variable is unchanged. After changeFirst(numbers), the first array element is changed because the method and caller refer to the same array object.

This distinction is important. Java is not pass-by-reference, although reference-type objects can be mutated through a copied reference.

Scope and Lifetime

Scope is the region of source code in which a variable can be used. A local variable declared inside a method or block is available only within that method or block. A parameter has method scope. An instance variable belongs to an object, while a static field belongs to a class.

public static int example(int input) {
    int doubled = input * 2;
    if (doubled > 10) {
        int messageCode = 1;
        System.out.println(messageCode);
    }
    return doubled;
}

messageCode cannot be used outside the if block. Restricting scope reduces accidental changes and makes programs easier to reason about. Variables should normally be declared as close as possible to their first use.

A method can return early:

public static boolean isPositive(int number) {
    if (number <= 0) {
        return false;
    }
    return true;
}

Every execution path in a non-void method must return a compatible value.

3. Method Overloading

Overloading allows several methods in the same class to use the same name when their parameter lists differ in number, type, or order.

public static int maximum(int a, int b) {
    return a > b ? a : b;
}

public static double maximum(double a, double b) {
    return a > b ? a : b;
}

public static int maximum(int a, int b, int c) {
    return maximum(maximum(a, b), c);
}

The compiler selects an overload using the method name and argument types. Return type alone cannot distinguish overloaded methods:

// Invalid: differs only by return type
// public static double maximum(int a, int b) { ... }

Overloading improves usability when operations are conceptually the same. However, excessive overloads or ambiguous conversions can make an API difficult to understand. Use consistent parameter meaning and document behavior.

Overloading is resolved mainly at compile time. It should not be confused with overriding, in which a subclass supplies a new implementation of an inherited instance method.

4. Recursion Fundamentals

Recursion occurs when a method calls itself. A correct recursive method requires:

  1. a base case that stops recursion;
  2. a recursive case that moves toward the base case.
public static int factorial(int n) {
    if (n < 0) {
        throw new IllegalArgumentException("n must not be negative");
    }
    if (n == 0) {
        return 1;
    }
    return n * factorial(n - 1);
}

For a non-negative integer n, factorial is defined by n! = n × (n - 1) × ... × 1 and 0! = 1. The method assumes an integer input and does not prevent integer overflow for sufficiently large values. For example, Java int cannot represent arbitrarily large factorials.

Each recursive call consumes stack space. A missing or unreachable base case can produce StackOverflowError. Many recursive operations have simpler iterative forms:

public static int factorialIterative(int n) {
    if (n < 0) {
        throw new IllegalArgumentException("n must not be negative");
    }
    int result = 1;
    for (int i = 2; i <= n; i++) {
        result *= i;
    }
    return result;
}

Recursion is valuable for naturally hierarchical problems, such as traversing a tree, but iteration is often more memory-efficient for simple counting tasks.

5. One-Dimensional Arrays

An array is a fixed-size, indexed collection whose elements have one declared type. Indexing begins at zero.

int[] scores = new int[4];
int[] temperatures = {18, 21, 23, 20};

The valid indices of temperatures are 0 through 3. Its size is available through the length field, not a method:

for (int i = 0; i < temperatures.length; i++) {
    System.out.println(temperatures[i]);
}

A for-each loop is convenient when the index is unnecessary:

int sum = 0;
for (int temperature : temperatures) {
    sum += temperature;
}

The array's length is fixed after creation. Elements of a numeric array receive default value 0; boolean elements receive false; reference elements receive null.

Array Algorithms

A method can receive an array and compute a result:

public static double average(int[] values) {
    if (values == null || values.length == 0) {
        throw new IllegalArgumentException("At least one value is required");
    }
    int sum = 0;
    for (int value : values) {
        sum += value;
    }
    return (double) sum / values.length;
}

The arithmetic mean is mean = (x1 + x2 + ... + xn) ÷ n, where n is the number of values and all values are measured on a compatible scale. Casting to double prevents integer division. For [4, 7, 9], the mean is 20 ÷ 3 ≈ 6.67.

A method that searches for a value can return an index:

public static int find(int[] values, int target) {
    for (int i = 0; i < values.length; i++) {
        if (values[i] == target) {
            return i;
        }
    }
    return -1;
}

Returning -1 communicates that no valid index was found. The caller must check this result before using it as an index.

Common errors include using i <= array.length, which accesses one position beyond the end, and confusing an array's length field with a string's length() method.

6. Multidimensional Arrays

Java multidimensional arrays are arrays of arrays. A rectangular two-dimensional array can be declared as follows:

int[][] table = {
    {1, 2, 3},
    {4, 5, 6}
};

for (int row = 0; row < table.length; row++) {
    for (int column = 0; column < table[row].length; column++) {
        System.out.print(table[row][column] + " ");
    }
    System.out.println();
}

table.length is the number of rows, whereas table[row].length is the number of columns in that row. Because rows are separate arrays, Java also supports ragged arrays:

int[][] ragged = new int[3][];
ragged[0] = new int[2];
ragged[1] = new int[4];
ragged[2] = new int[1];

Code should not assume equal row lengths unless the program guarantees a rectangular structure. A two-dimensional array is not automatically a mathematical matrix; it is a nested object structure.

7. Strings and Text Processing

String is a reference type representing an immutable sequence of characters. Immutability means that a string object is not changed after creation. Operations such as toUpperCase() produce another string.

String original = "Java";
String upper = original.toUpperCase();
System.out.println(original); // Java
System.out.println(upper);    // JAVA

Use equals to compare string content:

if (username.equals("admin")) {
    System.out.println("Access granted");
}

Using == compares references rather than textual content and is generally incorrect for content comparison. A null-safe alternative is:

if ("admin".equals(username)) {
    System.out.println("Access granted");
}

Important methods include:

MethodPurpose
length()returns the number of UTF-16 code units
charAt(i)returns the character at index i
substring(a, b)returns a portion from a inclusive to b exclusive
indexOf(text)locates a substring, or returns -1
contains(text)tests whether a sequence occurs
toLowerCase() / toUpperCase()changes case according to locale-sensitive rules
trim()removes selected leading and trailing whitespace
split(regex)divides text using a regular expression

Java strings use UTF-16 internally. length() counts UTF-16 code units, not necessarily human-perceived characters; some Unicode characters occupy two code units. Programs handling international text should avoid assuming that one char always equals one complete character.

String Concatenation and StringBuilder

Repeated concatenation in a loop may create many temporary immutable strings. StringBuilder is appropriate for constructing a mutable sequence:

StringBuilder report = new StringBuilder();
for (int i = 1; i <= 3; i++) {
    report.append("Item ").append(i).append("\n");
}
String result = report.toString();

Use String for ordinary values and StringBuilder for repeated construction. StringBuffer provides synchronized methods and is relevant only when that synchronization is required.

Never build SQL, HTML, or shell commands by blindly concatenating untrusted input. Use parameterized database queries, context-appropriate output escaping, and validated inputs.

8. Utility Classes

Java's standard library provides reusable operations.

java.util.Arrays

import java.util.Arrays;

int[] values = {4, 1, 3};
Arrays.sort(values);
System.out.println(Arrays.toString(values));
int position = Arrays.binarySearch(values, 3);

binarySearch requires the array to be sorted according to the relevant ordering. If the value is absent, the result is negative and should not be treated as a valid index. Arrays.copyOf creates a new array rather than an alias.

Math

Math.max, Math.min, Math.abs, Math.sqrt, and Math.pow support common numerical operations. Floating-point results are approximations, so equality comparisons may require a tolerance appropriate to the application.

Other Useful Classes

  • Integer.parseInt converts valid integer text and throws NumberFormatException for invalid text.
  • StringJoiner creates delimiter-separated text.
  • Objects.requireNonNull documents and checks required references.
  • Random generates pseudorandom values for simulations; it is not a substitute for a cryptographic generator.
  • java.security.SecureRandom is designed for security-sensitive random values.

9. Modular Program Design

Modular programming divides a system into components with defined responsibilities and interfaces. A module may be a method, class, package, or service. Effective modules exhibit high cohesion—closely related responsibilities—and low coupling—few unnecessary dependencies.

A small grade-processing program might separate responsibilities as follows:

public class GradeReport {
    public static double average(int[] marks) {
        if (marks == null || marks.length == 0) {
            throw new IllegalArgumentException("Marks are required");
        }
        int total = 0;
        for (int mark : marks) {
            if (mark < 0 || mark > 100) {
                throw new IllegalArgumentException("Mark must be 0 through 100");
            }
            total += mark;
        }
        return (double) total / marks.length;
    }

    public static char letterGrade(double average) {
        if (average >= 70) return 'A';
        if (average >= 60) return 'B';
        if (average >= 50) return 'C';
        if (average >= 40) return 'D';
        return 'F';
    }
}

Validation, calculation, and classification are distinct responsibilities. The methods are easier to test than one large main method. In production software, these operations could be placed in separate classes with private state and clearly defined public methods.

Contracts and Defensive Programming

A method contract states what inputs are accepted, what result is produced, and what exceptional conditions may occur. For example, average above requires a non-null, non-empty array containing marks from 0 through 100. Defensive checks prevent invalid data from silently producing misleading results.

Exceptions should communicate violations clearly. Do not catch an exception merely to suppress it. Handle it at the layer that can recover, report, or translate the error meaningfully.

Applied Case: A Small Inventory Analyzer

This is an illustrative case.

Background

A retail trainee needs a console program that records daily unit sales for three products and reports totals, average sales, and the best-performing product.

Problem

A monolithic program would mix input, validation, calculations, and display. Such a design is difficult to test and easy to break when product numbers change.

Analysis and Approach

The solution uses an array for each product's daily sales, methods for total and average calculations, and a two-dimensional array to hold all products. The program validates that sales are not negative.

public class InventoryAnalyzer {
    public static int total(int[] sales) {
        int result = 0;
        for (int sale : sales) {
            if (sale < 0) {
                throw new IllegalArgumentException("Sales cannot be negative");
            }
            result += sale;
        }
        return result;
    }

    public static int bestProduct(int[][] sales) {
        if (sales == null || sales.length == 0) {
            throw new IllegalArgumentException("Products are required");
        }
        int best = 0;
        for (int product = 1; product < sales.length; product++) {
            if (total(sales[product]) > total(sales[best])) {
                best = product;
            }
        }
        return best;
    }

    public static void main(String[] args) {
        int[][] sales = {
            {12, 15, 11},
            {8, 14, 18},
            {10, 9, 13}
        };
        int product = bestProduct(sales);
        System.out.println("Best product: " + product);
        System.out.println("Units sold: " + total(sales[product]));
    }
}

The totals are 38, 40, and 32, so product index 1 is selected. In a user-facing system, products would normally have names, and input would be separated from the analysis methods. A further improvement would calculate totals once rather than repeatedly inside bestProduct, especially for large datasets.

Learning Points

The case demonstrates arrays as data structures, methods as reusable operations, validation as part of a contract, and modular decomposition as a practical maintenance technique. It also illustrates a boundary decision: whether an empty dataset is an error or a meaningful state must be specified by the application.

Challenges, Limitations, and Best Practices

Arrays are efficient and simple, but their fixed size makes insertion and deletion inconvenient. For dynamic collections, ArrayList is often more suitable. Arrays also provide no automatic domain validation; methods must enforce rules.

Strings are immutable and safe to share, but repeated transformations can allocate many objects. Locale-sensitive case conversion should specify a locale when the result is used for machine logic. Never use case conversion as a replacement for proper Unicode-aware comparison in internationalized systems.

Recursion can express a solution elegantly, but stack depth and repeated computation limit its use. Memoization or iteration may improve performance. For example, naive recursive Fibonacci recalculates the same values many times; it is unsuitable for large inputs without optimization.

Professional best practices include meaningful names, small cohesive methods, input validation, unit tests for boundary cases, comments explaining why rather than what, and avoiding mutable global state. Test empty arrays, single-element arrays, duplicate values, null references where permitted, invalid indices, Unicode text, and maximum or minimum numeric values.

Security is also relevant. Validate data at trust boundaries, avoid exposing sensitive information in exception messages, and use secure random generation for tokens or passwords. AI-assisted code generation can help produce alternatives, but every generated method must be reviewed for correctness, input validation, licensing constraints, and security weaknesses.

Chapter Summary

Methods provide reusable units of behavior and may accept parameters and return values. Java passes arguments by value, including copied references for objects. Scope controls where variables can be accessed, while overloading provides several signatures for conceptually related operations. Recursion requires a base case and progress toward that case.

Arrays are fixed-size, zero-indexed collections; multidimensional arrays are arrays of arrays and may be ragged. Strings are immutable, should generally be compared with equals, and require care when processing Unicode text. StringBuilder supports efficient repeated construction. Utility classes such as Arrays, Math, and Objects reduce duplicated code.

Modular programming separates responsibilities, improves testing, and reduces coupling. Reliable modules define contracts, validate inputs, handle exceptions appropriately, and consider performance, security, and maintainability.

Key Terms

Method: A named block of executable statements.

Parameter: A variable declared in a method signature.

Argument: A value supplied when a method is called.

Return value: Data sent from a method to its caller.

Scope: The region in which a variable or name is accessible.

Overloading: Defining methods with the same name but different parameter lists.

Recursion: A method calling itself.

Base case: The condition that terminates recursion.

Array: A fixed-size, indexed collection of one declared type.

Index: The integer position used to access an array or string element.

Ragged array: A multidimensional array whose rows have different lengths.

String: An immutable sequence of characters.

Immutability: The property that an object cannot be changed after creation.

StringBuilder: A mutable class for constructing character sequences.

Reference: A value that identifies an object.

Pass-by-value: Java's argument-passing model in which a value is copied into a parameter.

Cohesion: The degree to which a module's responsibilities belong together.

Coupling: The degree of dependency between modules.

Method contract: A statement of valid inputs, outputs, and exceptional behavior.

Review Questions

Short Answer Questions

  1. What is the difference between a parameter and an argument?
  2. Why does Java use equals rather than == for ordinary string-content comparison?
  3. What two elements are required for correct recursion?
  4. How does a ragged two-dimensional array differ from a rectangular one?
  5. Why is a cast to double useful when calculating an average of integers?

Long Answer Questions

  1. Explain Java's pass-by-value model for both primitive and reference types, using examples.
  2. Describe method overloading and contrast it with method overriding.
  3. Explain array indexing, traversal, boundary errors, and the role of the length field.
  4. Discuss string immutability and compare String, StringBuilder, and StringBuffer.
  5. Explain how cohesion, coupling, validation, and contracts contribute to modular program design.

Analytical / Application Questions

  1. Design methods that accept an integer array and return its minimum, maximum, and average. State how your design handles a null or empty array.
  2. A program compares user-entered passwords with == and sometimes rejects correct passwords. Diagnose the defect and propose a secure, null-safe correction.
  3. Refactor a single method that reads sales, validates values, calculates totals, determines the best product, and prints a report into at least four cohesive modules. Justify the responsibility of each module.

Multiple Choice Questions with Answers

Question 1

Which statement correctly describes Java argument passing?

A. All arguments are passed by reference.

B. Primitive values are copied, and object references are also copied by value.

C. Only arrays are passed by reference.

D. Arguments are never copied.

Correct Answer: B. Primitive values are copied, and object references are also copied by value.

Question 2

Which declaration is a valid overloaded method?

A. A method with the same parameters but only a different return type.

B. A method with the same name and a different parameter list.

C. A method with the same name and identical parameters in the same class.

D. A method with no return type declaration.

Correct Answer: B. A method with the same name and a different parameter list.

Question 3

What is the first valid index of an array named items?

A. -1

B. 0

C. 1

D. items.length

Correct Answer: B. 0

Question 4

What does Arrays.binarySearch require for reliable ordinary searching?

A. The array must contain strings.

B. The array must have an even length.

C. The array must be sorted according to the relevant ordering.

D. The array must be two-dimensional.

Correct Answer: C. The array must be sorted according to the relevant ordering.

Question 5

What does a recursive method require to stop calling itself?

A. A constructor

B. A base case

C. An overloaded parameter

D. A StringBuilder

Correct Answer: B. A base case

Question 6

Which expression compares the contents of two strings?

A. first == second

B. first.equals(second)

C. first = second

D. first.length(second)

Correct Answer: B. first.equals(second)

Question 7

What is the average of the integer array {4, 7, 9} as a double?

A. 5.00

B. 6.00

C. 6.67 approximately

D. 20.00

Correct Answer: C. 6.67 approximately

Question 8

Which class is most appropriate for repeated string construction in a loop?

A. StringBuilder

B. Math

C. Arrays

D. Integer

Correct Answer: A. StringBuilder

Question 9

For int[][] data, which expression gives the number of columns in row r?

A. data.length

B. data[r].length

C. data.length()

D. data[r].length()

Correct Answer: B. data[r].length

Question 10

Which design generally improves modularity?

A. One very large method handling every responsibility.

B. Many methods sharing undocumented global state.

C. Small cohesive methods with clearly defined contracts.

D. Suppressing all exceptions inside utility methods.

Correct Answer: C. Small cohesive methods with clearly defined contracts.

Java Programming
Chapter 4 of 15