Java Essentials – Arrays (Module 2 Segment)

Recap of Previous Module

  • Covered background & history of Java, its key features, and wrote first basic Java programs.

  • Concluded with control statements (selection & iteration). These are foundational for today’s discussion because iteration is critical when working with arrays.

Learning Objectives for This Module

  • Implement single- and multi-dimensional arrays.

  • Understand declaration, definition, and invocation of functions (by value & by reference). (Functions proper will be addressed later in the module; today’s transcript focuses on arrays.)

  • Method overloading, String, and StringBuffer classes will follow after arrays & functions.

Why Arrays?

  • Analogy: John & Daisy must process a large U.S. election data set (votes for Democratic vs. Republican parties across many states).

  • Primitive variables (int, char, etc.) hold single values → not scalable.

  • Arrays act as containers / data structures for sequential, homogeneous data, simplifying storage & traversal.

  • Practical outcome: Correct array use earned John a promotion → illustrates professional value of mastering data structures.

Core Characteristics of Arrays

  • Fixed-size contiguous block of memory holding same data type.

  • Indexing starts at 00 and ends at n1n-1 where nn = total number of elements.

  • Array variable stores a reference (on stack); actual elements reside in heap.

  • Support both primitive and reference (non-primitive) types. Arrays in Java support both primitive and reference (non-primitive) types because although the array variable itself stores a reference on the stack, the actual elements reside in the heap. When an array holds primitive types (like `int` or `char`), the heap memory block allocated for the array directly stores the values of these primitive types. However, when an array holds reference types (like `String` objects or instances of custom classes), the heap memory block for the array stores references (memory addresses) that point to the actual objects, which are themselves stored elsewhere in the heap.

Advantages
  • Eliminates repetitive variable declarations.

  • Simplifies traversal (can use loops instead of one-by-one access).

  • Deterministic memory layout ⇒ predictable access time.

Declaring & Initialising One-Dimensional Arrays

  1. Implicit size specification

   int[] a = new int[5];      // indices: 0-4
  1. Brackets after variable (same effect)

   int a[] = new int[5];
  1. Inline initialisation (implicit size)

   int[] a = {1, 2, 3, 4, 5}; // size inferred as 5
  1. Explicit, separate steps

   int[] a;           // declaration
   a = new int[]{1,2,3}; // explicit creation & assignment
Basic Operations Demo
  • Attempting to store multiple ints in a single int d = {…} raises compile-time error.

  • Printing array variable directly shows reference value (e.g., [I@15db9742), not elements.

  • Correct element access → a[index].

  • Looping to print all elements:

   for(int i = 0; i < a.length; i++) {
       System.out.print(a[i] + " ");
   }

Traversal & Control Statements

  • Used for loop with counter i to iterate sequential indices.

  • Recalled prior module’s control flow concepts (iteration).

Multi-Dimensional Arrays

  • Needed when data has more than one varying dimension (e.g., height × depth, or votes per state per party).

  • Java stores multi-dimensional arrays as arrays of arrays.

Declaration Examples
int[][] m = new int[2][2]; // 2x2 matrix
char[][] c = new char[3][2]; // 3 rows, 2 columns
float[][] f = new float[5][2]; // 5x2 matrix
  • 3×3 literal initialisation:

  int[][] a = {
      {1,2,3},
      {4,5,6},
      {7,8,9}
  };
Traversing 2-D Array (Nested Loops)
for(int i = 0; i < a.length; i++) {          // rows
    for(int j = 0; j < a[i].length; j++) {  // columns
        System.out.print(a[i][j] + " ");
    }
    System.out.println(); // new row
}
  • Outer loop iterates rows; inner loop iterates columns.

  • Output appears in matrix form.

Memory & Index Mapping
  • Element at row ii, column jj accessed via a[i][j].

  • Total elements in r×cr \times c matrix = r×cr \times c.

Stack vs Heap Discussion

  • Stack: stores reference variable (a).

  • Heap: stores actual array objects and their contained values.

  • Printing a without index yields heap address string, not contents.

Both the Stack and the Heap are memory locations. The Stack is used to store the reference variable, which holds the memory address pointing to where the actual data is stored. The Heap is where the actual array objects and their contained values or the objects themselves reside.


In Java, both the Stack and the Heap are memory areas used for data storage, but they serve different purposes:

  • Stack Memory: This is primarily used for storing local variables, method call parameters, and references to objects. It is known for its Last-In, First-Out (LIFO) behavior. When a method is called, a new block (stack frame) is created on the stack for that method's variables, and when the method finishes, the block is popped off. Variables stored on the Stack are typically short-lived.

  • Heap Memory: This is where all actual objects and arrays are stored. When you create an object or an array using the new keyword, the memory for that object/array is allocated in the Heap. References to these Heap-allocated objects are then stored on the Stack (for local variables) or within other objects (for instance variables). The Heap is much larger and objects here can live for the entire duration of the application, as long as there are active references to them; otherwise, they become eligible for garbage collection.

In essence, reference variables (which hold memory addresses) sit on the Stack, pointing to the actual data (objects and arrays, including their primitive attributes) which reside in the Heap.


Java separates memory into Stack and Heap for several key reasons, primarily to optimize memory management, control data lifetime, and enhance flexibility:

  1. Efficient Management of Local Data: The Stack is designed for fast, automatic allocation and deallocation of local variables and method call information. Its Last-In, First-Out (LIFO) nature makes operations extremely quick, as memory is simply added or removed from the top of the stack as methods are called and return.

  2. Dynamic Object Storage: The Heap provides a space for dynamic memory allocation, meaning objects and arrays (whose sizes might not be known at compile time and whose lifetimes are not tied to a specific method's execution) can be created and stored. Objects in the Heap persist as long as there are active references pointing to them, allowing them to be shared across different methods or live for the application's duration.

  3. Garbage Collection: The Heap is where Java's automatic garbage collector operates. By keeping objects separate from the stack's local variables, the JVM can efficiently identify and reclaim memory from unreferenced objects in the Heap, reducing memory leaks and simplifying development.

  4. Flexibility and Predictability: This separation allows for more flexible object management on the Heap while maintaining highly predictable and fast access for local variables and execution flow on the Stack. Each thread typically has its own stack, but they share the heap, which is crucial for concurrency.


For a local variable declaration like int a = 5;, the primitive value 5 is typically stored directly on the Stack. So, in that sense, a (and its value) does reside on the Stack.

However, when we describe Stack variables as "short-lived" it's in contrast to Heap objects. The lifetime of a is indeed limited to the execution of the method in which it's declared. Once that method finishes, the stack frame is popped, and a (along with its value) is deallocated.

So, while the int value itself is on the Stack, its longevity is tied directly to the method's scope, making it short-lived compared to objects on the Heap which can persist as long as there are references to them, even across multiple method calls.


The reference variable on the Stack does not necessarily live as long as the object it points to in the Heap. Their lifetimes are governed by different rules:

  • Lifetime of a Reference on the Stack: A reference variable created on the Stack (e.g., Person person1; inside a method) lives only as long as the method call is active. Once the method finishes executing, its stack frame is popped, and all local variables (including reference variables) within that frame are deallocated.

  • Lifetime of an Object on the Heap: An object created on the Heap lives as long as there is at least one active reference pointing to it. If all references to an object are gone (e.g., the stack reference that pointed to it went out of scope, or was reassigned, and no other references exist), the object becomes eligible for garbage collection. The garbage collector will eventually reclaim the memory occupied by that object.

So, while a stack reference enables you to access an object on the Heap, the object can continue to exist even after that specific stack reference is gone, provided other


How Java access data from the heap for Arrays

When you declare and initialize an array in Java using the new keyword (e.g., int[] a = new int[5];), the array object itself is created and stored in the Heap. This allocation reserves a contiguous block of memory in the Heap:

  • For arrays of primitive types (like int, char, float): The actual primitive values are stored directly within this contiguous memory block in the Heap.

  • For arrays of reference types (like String[], Object[]): The Heap block stores references (memory addresses) to the actual objects. These objects themselves are also stored elsewhere in the Heap.

When you access an array element using an index, for example a[i]:

  1. Reference Retrieval: Java first looks at the array variable a on the Stack. This variable contains a memory address (a reference) that points to the beginning of the array object in the Heap.

  2. Offset Calculation: Using this base memory address from the Heap, Java calculates the exact memory location of the desired element. It does this by taking the base address and adding an offset, which is computed as index * size_of_element. For example, for an int array, if i is 2, and each int takes 4 bytes, it moves 2 * 4 = 8 bytes from the start of the array in the Heap to find the element at index 2.

  3. Direct Access: Because this calculation is a simple arithmetic operation that directly yields the memory address, accessing an array element by its index in Java is an extremely fast, constant-time operation (O(1)O(1)). Java then retrieves the value stored at that specific memory location in the Heap.


How Java access data from the heap for Objects

In Java:

  • Objects themselves are always stored in the Heap.

  • References to objects (variables that point to an object's location in memory) are stored on the Stack (if they are local variables or method parameters) or within other objects in the Heap (if they are instance variables).

Let's say you have a Person object with an int attribute for age:

class Person {
    String name;
    int age;

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

    public int getAge() {
        return age;
    }
}

// In your main method or another method:
Person person1 = new Person("Alice", 30);
int aliceAge = person1.getAge();

Here's how Java retrieves the int attribute (age) from the Heap:

  1. Reference on the Stack: The person1 variable is created on the Stack. It doesn't hold the Person object itself, but rather a memory address (a reference) pointing to where the actual Person object is located in the Heap.

  2. Object in the Heap: When new Person("Alice", 30) is executed, a new Person object is allocated space in the Heap. This Person object's memory block within the Heap contains:

    • The name attribute: This would be a reference to a String object ("Alice") which is also stored elsewhere in the Heap.

    • The age attribute: Since age is a primitive int, its actual value (3030) is stored directly within the Person object's memory block in the Heap. It's not a separate object or reference.

  3. Retrieval Process (e.g., person1.getAge() or person1.age if public):

    • Java first looks at the person1 variable on the Stack to get the memory address of the Person object in the Heap.

    • It then navigates to that memory location in the Heap.

    • Once at the Person object's location in the Heap, Java knows the memory layout of a Person object (i.e., where each of its attributes is stored relative to the object's starting address). It directly accesses the specific offset within that Person object's memory block where the age (integer) value is stored.

    • The int value (30) is then retrieved directly from that location in the Heap.

So, while the person1 variable (the reference) sits on the Stack, the actual Person object and its primitive attributes (like int age) reside together in a contiguous block of memory on the Heap. Java uses the


Theory vs Practice Summary

Single-Dimensional

  • Suitable when data varies along one axis.

  • Indexing sufficient with one counter.

Multi-Dimensional

  • Suitable for tabular or grid data (e.g., x,y,zx, y, z coordinates).

  • Requires nested loops for traversal.

Both

  • Support primitive & reference data types.

  • Provide time-saving, space-efficient alternative to many discrete variables.

Sample Concept-Check Questions & Answers

  1. Q: How many bytes for int x[][] = new int[5][5];?

    • Elements = 5×5=255 \times 5 = 25.

    • Each int = 44 bytes.

    • Total = 25×4=10025 \times 4 = 100 bytes.

  2. Q: Can multi-dimensional operations be mimicked with single-dimensional arrays?

    • Yes, by manual index calculations (e.g., index=row×cols+colindex = row \times cols + col ).

    • But multi-dimensional arrays reduce coding, time, and conceptual overhead.

Key Takeaways

  • Arrays are foundational in Java for managing collections of homogeneous data.

  • Understand declaration syntaxes, memory model, and traversal techniques.

  • Multi-dimensional arrays extend capability to model more complex data structures with minimal additional syntax.

  • Proper use of arrays not only improves code clarity but can significantly affect program performance and developer productivity—just as John’s promotion story illustrates.

(Functions, method overloading, and advanced string handling will be covered in subsequent segments of the module.)