Note 12.23.1. Test After Refining!
Always re-run your full test suite after applying any refinement. The goal is to improve the code *without breaking its correctness*. Tests are your safety net!
ArrayList<T> are passing (✅), you have successfully completed the core implementation task. This is a significant achievement.
ArrayList implementation.
System.arraycopy()
growIfNeeded, shiftRight, and shiftLeft currently use for loops to copy array elements. While logically correct, copying potentially large blocks of memory element-by-element in a Java loop can be much slower than using optimized, low-level system routines.
System.arraycopy() Java provides a static method specifically for fast bulk copying of array elements: System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length). It’s designed to be highly efficient. Let’s break down its parameters:
Object src: The source array you are copying from.
int srcPos: The starting index in the source array from where copying begins.
Object dest: The destination array you are copying to.
int destPos: The starting index in the destination array where copied elements will be placed.
int length: The number of elements to copy.
System.arraycopy correctly handles copying elements within the *same* array (as needed for shifting), even if the source and destination ranges overlap.
growIfNeeded
// Inside growIfNeeded, if resizing needed...
Object[] newbuffer = new Object[newCapacity];
for (int i = 0; i < this.size; i++) { // Copying 'size' elements
newbuffer[i] = this.buffer[i];
}
this.buffer = (T[]) newbuffer;
System.arraycopy):
// Inside growIfNeeded, if resizing needed...
Object[] newbuffer = new Object[newCapacity];
// Copy 'size' elements from 'this.buffer' starting at index 0
// to 'newbuffer' starting at index 0.
System.arraycopy(this.buffer, 0, newbuffer, 0, this.size); // Replaces the loop
this.buffer = (T[]) newbuffer;
shiftRight
private void shiftRight(int index) {
for (int i = this.size - 1; i >= index; i--) {
this.buffer[i + 1] = this.buffer[i];
}
}
System.arraycopy):
private void shiftRight(int index) {
// Need to move elements from index..size-1 to index+1..size
int numToMove = this.size - index; // Number of elements from index onwards
if (numToMove > 0) {
// Copy 'numToMove' elements:
// from: 'buffer' starting at 'index'
// to: 'buffer' starting at 'index + 1'
System.arraycopy(this.buffer, index, this.buffer, index + 1, numToMove);
}
}
shiftLeft
private void shiftLeft(int index) {
for (int i = index; i < this.size - 1; i++) {
this.buffer[i] = this.buffer[i + 1];
}
}
System.arraycopy):
private void shiftLeft(int index) {
// Need to move elements from index+1..size-1 to index..size-2
int numToMove = this.size - 1 - index; // Num elements after the removed one
if (numToMove > 0) {
// Copy 'numToMove' elements:
// from: 'buffer' starting at 'index + 1' (element after removed one)
// to: 'buffer' starting at 'index' (the gap)
System.arraycopy(this.buffer, index + 1, this.buffer, index, numToMove);
}
}
clear with Arrays.fill()
Arrays.fill() The java.util.Arrays class provides many static utility methods for working with arrays. One is Arrays.fill(Object[] a, int fromIndexInclusive, int toIndexExclusive, Object val). It efficiently assigns the specified value (val) to each element of the array within the specified range. Note that the toIndex is exclusive (the element at `toIndex` itself is *not* included).
@Override
public void clear() {
for (int i = 0; i < this.size; i++) { // Null out indices 0 to size-1
this.buffer[i] = null;
}
this.size = 0;
}
Arrays.fill):
@Override
public void clear() {
// Fill the range currently holding elements (0 up to size) with null.
// The 'toIndex' parameter is exclusive, so 'this.size' is correct here.
java.util.Arrays.fill(this.buffer, 0, this.size, null);
// Reset size *after* nulling out the old range
this.size = 0;
}
addFirst, removeFirst, and removeLast using their direct logic sequences (calling private helpers). This provided good practice but resulted in some repetition of checking logic and helper call patterns already present in add(index) and remove(index).
removeFirst Before (Direct Logic with Helpers):
@Override
public T removeFirst() {
if (isEmpty()) { throw new NoSuchElementException("List is empty."); }
T removedItem = this.buffer[0];
if (0 < this.size - 1) { shiftLeft(0); } // Check if shifting needed
this.size--;
this.buffer[this.size] = null;
return removedItem;
}
removeFirst After (Delegation):
@Override
public T removeFirst() {
if (isEmpty()) { // Still need specific empty check for NoSuchElementException
throw new NoSuchElementException("List is empty.");
}
// Delegate core logic (index check, get item, shift, size--, null out)
return remove(0); // remove(0) handles the rest
}
removeLast can be refined to check for empty and then call remove(size - 1), and addFirst can be refined to just call add(0, item).
Objects.equals()
// Inside the loop...
T currentElement = this.buffer[i];
if (item == null) {
if (currentElement == null) { return i; }
} else {
if (item.equals(currentElement)) { return i; }
}
Objects.equals() The utility class java.util.Objects (available since Java 7) provides a static method Objects.equals(Object a, Object b) designed specifically for null-safe equality checks. It returns true if both arguments are null, false if exactly one is null, and calls a.equals(b) if both are non-null.
Objects.equals):
// Inside the loop...
if (java.util.Objects.equals(item, this.buffer[i])) {
return i;
}
Objects.equals is the standard, idiomatic way to check for equality when either operand might be null in modern Java. It’s more concise and less prone to logic errors than manual null checks. This is a knowledge-based refinement that improves code clarity and robustness.
System.arraycopy significantly improves the efficiency of internal data copying.Arrays.fill offers a concise alternative for nulling out elements in clear.Objects.equals makes null-safe comparisons cleaner and more robust.dr-step-5.5-final-refined branch.
ArrayList<T>, gaining valuable insight into data structures, generics, and the Design Recipe. The final section will conclude the chapter.