References, Pointers, and Null in Java
Some languages do not require variables to be declared, so a misspelled name can silently create a new variable; Java requires declarations so such errors surface early.
Why Skipping a Requirement Feels Free
Programmers often judge a language by how little it asks of them. A language that lets you use a variable without announcing it first, or that lets you move a pointer wherever you like, feels faster to work with. This feeling comes with a belief: that a language which skips a requirement is simply more convenient and carries no real cost. Java's designers took a different view. They held that part of the problem with programming errors can be traced to the languages programmers use, and that a language can be built to protect against certain categories of error. This article examines the belief and asks how a language feature can prevent an error from happening at all.
What do you think happens?
Consider a language in which a variable springs into existence the first time its name appears in a statement. A single misplaced character changes a loop header into an assignment. Will the program tell you that something went wrong?
Reveal answer
No, the program runs and does something other than what you intended.
The source uses a FORTRAN statement to make this point. The spaces in FORTRAN are insignificant, so one form of the line is a counting loop, and the other, with a period in place of a comma, is an assignment. Because the language accepts both as valid, nothing flags the difference.
How a Missing Declaration Hides a Typo
A language without a declaration requirement has no list of legitimate names to check against. When the programmer types a name, the language accepts it and creates storage for it. A spelling slip, a stray character, or a substituted punctuation mark therefore produces a valid program that does something the programmer never intended. Nothing in the language flags the mistake, so the wrong behavior may surface only after the program has run for a long time, or only under conditions that are hard to reproduce.
| Situation | Language without a declaration requirement | Java |
|---|---|---|
| A variable name is typed wrongly | The language accepts the new name and creates storage for it | The name was never declared, so it has nowhere to hide and the mistake can be reported where it occurs |
| The error is noticed | Possibly only after a long run or in hard-to-reproduce conditions | At the point where the mistake happens |
| Cost to the programmer | Saves a line of code | Requires declarations before use |
Java's declaration requirement looks like a burden, but it is the feature that catches the typo early.
The source treats this declaration rule as the foundation of the wider argument. Java variables must be declared before they are used, so a misspelled name that was never declared is reported at the point where it appears, instead of quietly becoming a new variable.
Two Strategies: Impossible Errors and Detected Errors
Java's protective features all follow one of two strategies. The first is to design the language so that a particular error is impossible to write. If the construct that would produce the error does not exist, nothing needs to be checked at all. The second applies when an error cannot be removed entirely. In that case the language is designed so that the error is detected automatically at the moment it happens. Detection has two benefits: it stops the error from causing further harm, and it alerts the programmer that a bug needs fixing.
When you read about any Java safety feature, ask which strategy it uses. An impossible error needs no runtime check, while a detected error is caught only when the faulty line actually runs. The strength of the protection depends on which strategy applies.
Array Bounds: Where Java Stops the Access
An array is created with a fixed number of locations, numbered from zero up to a maximum index. Using a location outside that range is an error. Consider an array A with three locations, A[0], A[1], and A[2]. The positions A[3], A[4], and so on do not belong to the array. In Java, an attempt to store data at A[3] is detected automatically. By default the program is terminated, unless the error is caught by the program, a topic the source defers to a later section.
The diagram shows the boundary. Slots zero through two are allocated to the array, and the access at position three falls outside the allocated range. Java checks the index, so the access stops there rather than continuing into memory that belongs to something else.
- The array length is fixed at creation
- Index 2 is the last legal position
- Index 3 is outside the allocated range and is detected
Tracing a Write Past the End
An array A has three locations. A loop writes a value into A[i] for i from 0 to 3. What happens?
- 1Writes at i = 0, 1, 2
These indices belong to the array, so each store succeeds.
- 2Attempts a write at i = 3
Index 3 is outside the allocated range, so Java detects the attempt.
- 3Stops the access
By default the program is terminated, so the write never reaches memory beyond the array.
Three stores succeed and the fourth is detected and stopped instead of silently writing into neighboring memory.
The source contrasts this with C and C++. There the programmer must make sure the index is legal. If the programmer does not, the computer stores the data in memory that is not part of the array. That memory might be in use for something else, so the result is unpredictable and can be far more serious than a terminated program.
Null References and Pointer Misuse
The source calls pointers a notorious source of programming errors. In Java, a variable of an object type holds one of two things: a pointer to an object, or the special value null. Using null as if it were a pointer to a real object is detected by the system, which is the second strategy at work. The source contrasts this with languages in which avoiding null pointer errors is left entirely to the programmer.
In the diagram, ref_a has a pointer leading to an object, so using it reaches real data. ref_b holds null, which points to nothing. Using ref_b as though it led to an object is the kind of misuse Java detects, rather than letting it proceed as if a valid object were present.
The source gives a historical case to show why this matters. On an early Macintosh computer, a null pointer was implemented as a pointer to memory location zero. A program that used a null pointer could change values stored near that location, and those locations held important system data. Damaging that data could crash the whole system, a far more severe outcome than a single failed program.
A second category of pointer error occurs when a pointer refers to an object of the wrong type, or to a segment of memory that holds no valid object at all. The source states that these errors are impossible in Java, because Java does not let programmers manipulate pointers directly. In other languages a pointer can be set to point essentially anywhere in memory, and doing so incorrectly produces unpredictable results. Here the first strategy applies: the dangerous operation is removed from the language rather than checked after the fact.
Memory Leaks and Garbage Collection
A memory leak occurs when memory that is no longer needed is never released, so it accumulates. In Java, once no pointers refer to an object, that object is garbage collected, and the memory it occupied becomes available for reuse. In other languages the programmer must return unused memory to the system. If that step is forgotten, unused memory builds up and leaves less for programs and data. The source recalls that many common programs for older Windows computers leaked memory so badly that the machine would run out after a few days and need to be restarted.
The key difference is who carries the responsibility. Java moves it from the programmer to the system, so one common category of leak is prevented by design rather than left to discipline. Your code does not need to remember a manual release step for every object it stops using.
Buffer Overflows and Why Java Blocks Them
A buffer is a segment of memory that a program has allocated to hold data it expects to receive, for example data arriving from another computer over a network. A buffer overflow occurs when more data arrives than the buffer can hold. What follows depends on whether the program or networking software detects the overflow. If it is detected, the only result is a failed transmission. The serious case is an undetected overflow, where the software keeps writing past the end of the buffer into memory that belongs to something else. That memory might hold important data, or even part of the program itself.
The source explains why this is a security problem. If an overflow overwrites part of a program with received data, the computer later executes that data as if it were program code. The data could be anything, including a program that crashes the machine or takes it over. This is why buffer overflows so often appear in news about network security.
For software written entirely in Java, the source says buffer overflow errors are impossible. Java provides no way to store data into memory that has not been properly allocated. Doing so would require a pointer to unallocated memory, or an array index outside the allocated range, and both are prevented as described in the earlier sections.
What Java Still Cannot Guarantee
The safety features described in this article protect the code the programmer writes in Java. They do not make every line of code that runs inside the platform immune to every error. Knowing where that boundary lies is part of understanding what the language actually promises.
Mistakes Learners Make About Java Safety
Common mistake Assuming a typo will always cause a visible error
What it looks likeA language without a declaration requirement accepts a misspelled name as a new variable.
Why it failsSuch a language has no list of legitimate names to check against, so the wrong behavior may only appear much later.
Do this insteadIn Java, names must be declared before use, so a misspelled name that was never declared is reported where it occurs.
Common mistake Thinking every Java error is caught when it is written
What it looks likeBelieving that an out-of-range array access is rejected before the program runs.
Why it failsSome errors are detected only when the faulty line executes. An out-of-range index is detected at the moment of the access.
Do this insteadDistinguish errors Java makes impossible from errors it detects during execution.
Common mistake Believing null is just a harmless empty pointer
What it looks likeCalling a method through a reference that holds null and expecting it to work.
Why it failsNull does not point to a real object, and the source says using it as a pointer to an object is detected by the system.
Do this insteadTreat null as a value that signals no object is present, and check for it where needed.
Common mistake Assuming Java prevents memory leaks completely in all code
Practice: Predicting the Safety Outcome
An array B has four locations, B[0] through B[3]. A loop is written to store a value in B[i] for i from 0 to 4. Before you answer, decide which of the two Java strategies applies to the failing write, and whether the error is caught when it is written or when it runs. Then explain what happens to memory beyond the array.
Hints
- Ask whether the faulty index is an impossible construct or an attempt that is detected when it happens.
- Remember that index 4 is outside the allocated range for an array of four locations.
- Consider what the source says about C and C++ when an illegal index is used.
Key Takeaways
- A language that does not require declarations can turn a misspelled name into a valid program that silently does the wrong thing, while Java's declaration requirement reports such mistakes early.
- Java uses two strategies: it makes some errors impossible to write, and it automatically detects others when they occur, stopping further harm and alerting the programmer.
- Java detects out-of-bounds array indices and null misuse, removes direct pointer manipulation, and garbage collects objects no pointers refer to, which prevents a common category of memory leak.
- Software written entirely in Java cannot overflow buffers, but errors in methods written in C within the standard classes remain possible.