Java Arrays: Traversals and Core Algorithms
Build a practical understanding of Java one-dimensional arrays, from creation and indexing through traversal, searching, accumulation, comparison, modification, tracing, and boundary safety.
Array structure and indexing
A stores multiple values of the same type in a fixed-length ordered sequence. Its length is established when the array is created and cannot be changed afterward.
Declaring, creating, and initializing arrays
A declaration specifies an element type and a variable name, but it does not create the array object. For example, int[] scores, double[] temperatures, and String[] names declare array variables.
Creation with new specifies the length, as in int[] scores = new int[5]. When Java creates an array without supplied values, it assigns default values: 0 for int, 0.0 for double, false for boolean, and null for reference types.
An initializer list creates and fills an array at the same time. For example, int[] scores = {88, 92, 76, 95, 84} creates an array whose length is determined by the number of listed values. String[] names = {"Ava", "Ben", "Chloe"} creates an array of three String references.
Positions and length
An identifies one element's position. Java uses zero-based indexing: an array of length has valid indices , , , , and . In general, the last valid is for an array of length .
Use the length attribute to obtain the number of elements. For example, if int[] values = {4, 8, 15, 16, 23, 42}, then values.length is 6. Use array.length, not array.length(), because length is an attribute rather than a method.
Accessing and changing elements
Square brackets select one element. If int[] values = {12, 7, 19, 4, 10}, then values[0] is 12, and values[3] is 4. The assignment values[1] = 20 replaces the value at 1.
An expression such as values[2] represents one int, not the entire array. It can be read into a variable, used in an expression, or assigned a new value.
Takeaway: Array creation fixes the length, indices identify individual elements, and length reports the number of elements.
Traversing arrays with loops
An visits elements systematically, usually from the first position through the last. The best loop depends on whether the algorithm needs an , changes the array, or can stop early.
Indexed for loops
Use an indexed for loop when the position matters or when elements must be modified. The standard pattern is for (int = 0; < values.length; ++). Inside the loop, values[] refers to the current element.
For example, for (int = 0; < values.length; ++) { System.out.println(values[]); } prints every element. The condition uses < values.length because the position equal to the length is one past the final valid .
Indexed loops can modify the array. The statement values[] = values[] * 2 replaces each current value with twice that value as the traversal proceeds.
while loops
A while loop can perform the same traversal, but the must be initialized and updated manually. A typical pattern is int = 0; while ( < values.length) { System.out.println(values[]); ++; }. Omitting ++ can cause the loop not to terminate.
Enhanced for loops
An visits each element without explicitly exposing its . The pattern for (int value : values) is concise for read-only processing.
The loop variable receives the current value. Therefore, for (int value : values) { value = value * 2; } changes only the local variable value; it does not modify the array. To change the array, use an indexed loop and assign to values[].
Choosing a traversal
Use an enhanced
forloop when every element should be read and the position is unnecessary.Use an indexed
forloop when the algorithm needs an or must modify elements.Use an indexed
fororwhileloop when the algorithm may stop early withreturnorbreak.Use the array being indexed to determine the loop boundary, especially when multiple arrays are involved.
Takeaway: Direct element iteration is convenient for reading; explicit indices are necessary for positions and modifications.
Searching for values and properties
A examines elements from left to right until a condition is satisfied or all elements have been checked. The result should clearly describe what happened when a match is found and when no match exists.
Searching for a value
A method that tests whether a target occurs can use an enhanced loop: public static boolean contains(int[] values, int target) { for (int value : values) { if (value == target) { return true; } } return false; }. The method returns immediately when it finds a match. If the loop finishes, the target is absent and the method returns false.
To return the first matching position, use an indexed loop: public static int indexOf(int[] values, int target) { for (int = 0; < values.length; ++) { if (values[] == target) { return ; } } return -1; }.
Returning -1 communicates that no valid was found because array indices cannot be negative. If a value appears more than once, this left-to-right method returns the first occurrence.
Searching for a property
The condition can describe a property rather than equality with one target. For example, containsNegative can return true as soon as it encounters a value satisfying value < 0, and return false after checking every element without finding one.
This is an at-least-one pattern: one qualifying element is enough for a positive result. It is different from a pattern that requires every element to satisfy a condition or that counts all qualifying elements.
Designing the result
Before writing the loop, identify whether the method should return a Boolean, an , a count, or another value. Then define the no-match result, such as false or -1, and decide whether the search should stop at the first match.
Takeaway: State the matching condition precisely, traverse in order, stop early when the result is already known, and provide a clear no-match result.
Counting, sums, and averages
Counting and accumulation turn a traversal into a computed result. Initialization is part of the algorithm: a counter or running result must begin with a value appropriate to the operation.
Counting matches
A counting method starts at zero and increments once for every element that satisfies its condition. The pattern int count = 0; for (int value : values) { if (value % 2 == 0) { count++; } } counts even integers. For {12, 7, 19, 4, 10}, the result is 3 because 12, 4, and 10 satisfy the condition.
Starting with count = 1 would incorrectly claim that one qualifying element had already been found. A count should increase only when the condition is true.
Sums and other running results
An stores a running result. To sum an integer array, use the pattern int total = 0; for (int value : values) { total += value; }. For {12, 7, 19, 4, 10}, the final total is 52.
The initial value should match the operation:
Sum: start at
0.Count: start at
0.Product: start at
1.Logical AND: start at
true.Logical OR: start at
false.
Averages and
An average is the sum divided by the number of elements. If a fractional answer is possible, avoid by converting one operand to double: return (double) total / values.length.
For a total of 52 and a length of 5, decimal division produces 10.4. The expression 52 / 5 uses and produces 10, because both operands are integers. Assigning that result to a double afterward does not restore the discarded fraction.
Combining a condition with accumulation
A traversal can update an only for selected elements. For example, sumPositive can start total at 0, add value only when value > 0, and return the resulting sum.
Takeaway: Initialize deliberately, update only when the algorithm requires it, and use decimal division when the result may contain a fractional part.
Comparing elements and handling edge cases
Minimum and maximum algorithms maintain the best value found so far. When the array is known to be nonempty, initializing that candidate from the first element works for positive, negative, and mixed values.
Finding a maximum
The pattern int max = values[0]; for (int = 1; < values.length; ++) { if (values[] > max) { max = values[]; } } begins with the first element as the current maximum. Each later element replaces max only when it is larger.
The loop begins at 1 because 0 has already supplied the initial candidate. For {12, 7, 19, 4, 10}, the candidate changes from 12 to 19 and remains 19 afterward.
Finding a minimum
The minimum pattern changes the comparison: int min = values[0]; for (int = 1; < values.length; ++) { if (values[] < min) { min = values[]; } }. Each smaller value becomes the new candidate.
Do not automatically initialize a maximum to 0. If every value is negative, such as in {-8, -3, -12}, returning 0 would be incorrect because 0 is not an element. Initializing from values[0] avoids that problem.
Empty-array preconditions
The expressions values[0] in these algorithms require a nonempty array. If values.length is 0, direct access to values[0] is invalid. A method that accepts empty arrays must define a policy, such as returning a special result, reporting an error, or using a different result type.
Always check the stated precondition before choosing an algorithm. A standard minimum or maximum implementation can rely on values[0] only when nonemptiness is guaranteed.
Takeaway: Use a data-dependent initial candidate instead of an arbitrary constant, and handle the empty-array case explicitly.
Avoiding errors and tracing array code
Most array mistakes involve invalid boundaries, incorrect initialization, unintended non-modification, or overlooked preconditions.
Valid boundaries
For an array of length , the valid indices are , , , and . The indices and are invalid. An invalid access causes an at runtime.
The loop condition <= values.length is an off-by-one error when the loop body accesses values[]. Eventually, becomes equal to values.length, which is already outside the valid range. Use < values.length instead.
An empty array is different from an array with one element. A loop using < empty.length runs zero times safely, but empty[0] is invalid because there is no first element.
Tracing an algorithm
To trace int[] data = {5, 2, 8, 2}; int count = 0; for (int = 0; < data.length; ++) { if (data[] == 2) { count++; } }, record the , current element, condition result, and counter after each iteration. The counter becomes 1 at the first 2 and 2 at the second 2, so the final result is 2.
A useful tracing checklist is:
Identify every variable's initial value.
List the indices the loop can visit.
Check whether the boundary uses
<or<=withlength.Determine exactly when counters and accumulators change.
Look for early
returnorbreakstatements.Determine whether the array itself is modified.
Verify that every indexed access is valid.
Check preconditions, especially whether the array may be empty.
Combining algorithms
Some tasks require more than one pass. To count values above the average, first accumulate the sum and compute the average, then traverse again to count values greater than that average. Each pass is linear in the array length, and the separate passes make the dependency between the calculations clear.
Takeaway: Trace boundaries and state changes separately. When visited indices, updates, early exits, and preconditions are explicit, array algorithms become easier to predict and debug.