Concepts / References, Pointers, and Null in Java

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.

  • Programming

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?

  • Yes, the language reports the mistake immediately

  • No, the program runs and does something other than what you intended

Reveal answer
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.

SituationLanguage without a declaration requirementJava
A variable name is typed wronglyThe language accepts the new name and creates storage for itThe name was never declared, so it has nowhere to hide and the mistake can be reported where it occurs
The error is noticedPossibly only after a long run or in hard-to-reproduce conditionsAt the point where the mistake happens
Cost to the programmerSaves a line of codeRequires 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.

Key point

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.

A[0]valid slotA[1]valid slotA[2]valid slotA[3]not part of Anextnextaccess attempt
When code uses index 3 on an array of length 3, which slots exist, and at what point does Java stop the access instead of reading memory beyond the array?

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.

java
Notes on the code
  • The array length is fixed at creation
  • Index 2 is the last legal position
  • Index 3 is outside the allocated range and is detected
Worked example

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?

  1. 1
    Writes at i = 0, 1, 2

    These indices belong to the array, so each store succeeds.

  2. 2
    Attempts a write at i = 3

    Index 3 is outside the allocated range, so Java detects the attempt.

  3. 3
    Stops the access

    By default the program is terminated, so the write never reaches memory beyond the array.

Result

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.

ref_aholds a pointerobjectreal objectref_bnullpoints to
How does a reference variable point to an object, and what does it look like when it holds null and points to nothing, so that using it through a method call fails?

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.

Referenced objectat least one pointerUnreachable objectno pointers leftMemory reclaimedavailable for reuselast pointer removedgarbage collected
What happens to an object after the last pointer to it is removed, and when does its memory get reclaimed?

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 like

    A language without a declaration requirement accepts a misspelled name as a new variable.

    Why it fails

    Such a language has no list of legitimate names to check against, so the wrong behavior may only appear much later.

    Do this instead

    In 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 like

    Believing that an out-of-range array access is rejected before the program runs.

    Why it fails

    Some errors are detected only when the faulty line executes. An out-of-range index is detected at the moment of the access.

    Do this instead

    Distinguish errors Java makes impossible from errors it detects during execution.

  • Common mistake

    Believing null is just a harmless empty pointer

    What it looks like

    Calling a method through a reference that holds null and expecting it to work.

    Why it fails

    Null 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 instead

    Treat 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

MEDIUM

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.