# Java language fundamentals

Source: https://codewiki.com/java/fundamentals/

> - **what**: A Java program is made of types, methods, statements, and expressions. The compiler checks names, types, scopes, and control flow before the JVM executes the resulting bytecode.
> - **trap**: Intermediate expression types, short-circuit rules, and loop bounds are semantics, not style. Code that compiles may still mishandle overflow, `null`, or boundary inputs.
> - **fix**: Keep variable scopes narrow, reject invalid input with guard clauses, make branches complete, and verify real execution paths with boundary values and side effects.

## What it is and why it exists

Java language fundamentals are the minimum rules for organizing an executable program: how names bind to declarations, how expressions calculate values, and how statements select what runs next. Java is statically typed, so the compiler exposes most name and type errors before execution. After compilation, the same rules determine runtime evaluation order, branch selection, and loop termination.

A source file usually declares one or more classes, interfaces, enums, or records. Methods put operations behind named boundaries, statements change local state or control execution, and expressions produce values. Beginner programs often enter through `public static void main(String[] args)`, but the same fundamentals apply in web controllers, test methods, batch jobs, and library code.

A variable is not an untyped container. Every variable has a compile-time type, scope, and lifetime; a local variable must also be definitely assigned before it is read. Expressions combine variables, literals, method calls, and operators. Statements decide whether to use the results, repeat the calculation, or leave the current control structure early.

These rules exist so that the compiler and the reader agree about what a program means. Indentation helps people read code, but it does not define a Java block; braces do. Likewise, a method name can suggest intent, but it cannot change the short-circuit semantics of `&&` or the rules of integer division.

This topic concentrates on program structure, variables, expressions, and control flow. The complete rules for primitive and reference types and numeric conversions belong to `java/data-types`; arrays, collections, strings, exceptions, and object-oriented design have their own topics. They appear here only where they are needed to explain control flow, without repeating their API catalogs.

## How it works

### From source file to entry method

`javac` reads source files, performs lexical, syntax, name, and type checks, and produces class files. `java` starts the JVM, loads the entry class, and invokes the `main` method that matches the launch protocol. Class-file names and runtime class names are case-sensitive, and the name of a public top-level type must match its source file.

In `main`, `public` lets the launcher access the method, `static` means no class instance is needed for the call, and `void` means the method returns no result to its caller. `String[] args` receives command-line arguments. The launcher may supply an array of length zero, but it does not pass `null`.

A method body is a block, and a block can contain local-variable declarations and statements. Each pair of braces creates a nested boundary, but it does not necessarily create an object or thread. A local name declared inside the block is no longer visible after execution leaves that block.

### Method calls and pass-by-value

Java always passes arguments by value. For a primitive type, the parameter receives a copy of the primitive value; for a reference type, it receives a copy of the reference value. Reassigning a parameter inside the called method cannot redirect the caller's variable to another object.

The copied reference still identifies the same object, so a method can use it to change mutable object state. Whether the caller observes that change follows from the shared object's contract, not from Java suddenly passing by reference. Saying “a reference is passed by value” explains both why reassignment is ineffective and why object mutation is visible.

Arguments are evaluated from left to right before their results are passed to the matched method. The overload target depends on compile-time types and available conversions; the overridden implementation of an instance method also depends on the receiver's runtime type. The complete overload and override rules belong to `java/oop`, but a fundamentals review should distinguish these two stages.

Every normally completing path in a non-`void` method must return a compatible value. `return` can end a method early, and a `void` method can use a return with no value. When an exit represents failure rather than a normal result, use an explicit exception or result model instead of an undocumented magic value.

### Choose statement forms from semantics

The syntax should match the control intent. This table is not a style ranking; it is a short mapping from a requirement to a language construct.

| Requirement | Usual construct | Key check |
| --- | --- | --- |
| Perform steps in order | Expression statements and calls | Side-effect and exception order |
| Select between two actions | `if` / `else` | Whether the condition really is `boolean` |
| Produce a value from finite categories | `switch` expression | Completeness and a result on every path |
| Repeat while a condition holds | `while` or ordinary `for` | Initial state, exit condition, and update |
| Visit every element | Enhanced `for` | Whether an index or structural change is needed |

More than one syntax can express the same result. Prefer the form that makes valid inputs, exit points, and the result type visible; nesting conditional operators merely to save lines usually hides that information.

### Declarations, scope, and definite assignment

A declaration associates a name with a type and an optional initializer. A local variable's scope starts at its declaration and continues to the end of its block; an inner block cannot redeclare a local name that is still in scope. Fields follow different member rules, so do not infer the local-variable rule from field shadowing or vice versa.

The Java compiler performs definite assignment analysis for local variables. A read is legal only when the compiler can prove that every path reaching it has assigned the variable. This is a compile-time control-flow judgment; the runtime does not silently fill an uninitialized local with zero or `null`.

A `final` local can be assigned only once, but an object it references does not become immutable. `final List names` prevents `names` from being redirected to another list, yet it does not prevent `names.add(...)`. If the program requires immutability, the type and construction mechanism must provide that contract too.

`var` is available only in supported local-variable positions with an initializer. It asks the compiler to infer one static type; it does not make Java dynamically typed, and `var result;` cannot defer inference until later. Spell out the type when the initializer leaves the intended abstraction unclear.

### Expressions and evaluation order

Java evaluates operator operands from left to right, and it evaluates method arguments from left to right too. That guarantee matters when an expression contains method calls, increments, or exceptions. Even so, splitting state-changing steps into named statements usually costs less to review than packing several side effects into one expression.

Arithmetic depends on the operand types. Dividing two `int` values performs integer division, and multiplying two `int` values first produces an `int`, even when the result is later assigned to `long`. See `java/data-types` for the full promotion, narrowing, and overflow rules; fundamentals code should at least inspect intermediate expressions, not only the variable receiving the result.

`&&` and `||` implement short-circuit evaluation. `left && right` evaluates the right side only when the left side is `true`; `left || right` evaluates it only when the left side is `false`. Single `&` and `|` also accept Boolean operands, but both sides are evaluated, so they cannot replace short-circuit operators that guard against `null` or division by zero.

Assignment is an expression too, but embedding it in a condition is usually easy to misread. Equality comparison uses `==`; assignment uses `=`. With references, `==` asks whether the references are identical, while business-value equality usually calls the `equals` method defined by the type.

### Conditional branches

`if` selects a path from a `boolean` expression. Java does not implicitly treat numbers, empty strings, or objects as truthy or falsy, so the condition must directly produce `boolean`. Braces are worth retaining around even one statement: when logging or validation is added later, the new statement cannot accidentally land outside the branch.

An `else` belongs to the nearest syntactically available `if`. Clear braces and shallow nesting make that ownership visible. Invalid input is often best handled with a guard clause that returns early and leaves the main path at a shallower indentation level.

Modern `switch` can be either a statement or an expression that produces a value. Arrow rules do not fall through to the next branch, and several labels can be written as `case 1, 2 ->`. Every normally completing expression path must produce a compatible value; an expression rule supplies one directly, while a block rule uses `yield`.

A `switch` expression must cover every possible selector value. An open-ended selector type usually needs `default`; for an enum or sealed hierarchy, the compiler may prove completeness from the known constants or permitted subtypes. Completeness is a compiler guarantee, but future binary evolution still needs tests.

### Boundaries of Boolean conditions

A condition should express a domain decision directly, such as `hasStock && paymentAccepted`. Hiding assignment, increment, or a remote call inside it tangles the Boolean result with side effects. When diagnostics matter, compute named results first and then combine the final condition.

Java does not support mathematical chained comparisons. Write a range check as `0 <= index && index < length`, not `0 <= index < length`. Short-circuit ordering should put a safe, cheap guard before the access that depends on it.

When negating a complex condition, apply Boolean algebra and keep the grouping explicit. Instead of making a reader expand `!(active && authorized)`, use a named predicate when the domain supports one. Test both boundaries after the rewrite, because improved readability does not itself prove logical equivalence.

### Loops and early exits

`for` fits loops whose initialization, condition, and update can be expressed together. Enhanced `for` fits sequential reads from an array or `Iterable`; an ordinary `for` is usually clearer when the code needs an index, reverse order, or in-place replacement. `while` tests before each iteration, while `do-while` guarantees at least one execution of its body.

Whether a loop terminates depends on state moving toward its exit condition. Review the initial value, condition, update, and integer boundary together. For array indices, the common range is `0 <= index && index < array.length`; the upper bound cannot be `<= array.length`.

`continue` moves to the next iteration, `break` leaves the current loop or `switch`, and `return` ends the current method. A labeled `break` or `continue` can target an outer statement, but it often signals that nested logic should become a method. Exceptions also complete control flow abruptly; see `java/exceptions` for propagation rules.

### Scope is not object lifetime

A local name's scope is its compile-time visibility, while an object's lifetime follows runtime reachability. A local name disappears after a method returns, but its object can remain alive if it was returned, stored in a field, or passed to another long-lived object.

Conversely, a local that remains in scope does not promise that an object is retained until the block ends; an implementation may reclaim an object once doing so cannot change program semantics. Business code must not depend on a collection time, and resources should use explicit mechanisms such as `try`-with-resources.

The main benefit of a narrow scope is less mutable state and fewer available names, not a direct memory-performance promise. Declaring a variable when it first becomes necessary helps a reader identify which statements can affect it and exposes state accidentally reused between loop iterations.

## Examples

These four programs progress from sequential execution to branching, looping, and short-circuit evaluation. Each source file compiles and runs independently, and the output shown is from an actual local JDK run.

### Variables, expressions, and output

The first program declares order data, computes a subtotal, and selects a delivery label with the conditional operator. Variables stay in the method that uses them, and their names expose the unit.

<!-- quick -->

```java
// file: OrderSummary.java
public class OrderSummary {
    public static void main(String[] args) {
        String customer = "Amina";
        int unitPriceCents = 240;
        int quantity = 3;

        int subtotalCents = Math.multiplyExact(unitPriceCents, quantity);
        boolean freeShipping = subtotalCents >= 500;
        String delivery = freeShipping ? "standard-free" : "standard-paid";

        System.out.printf("customer=%s subtotal=%d%n", customer, subtotalCents);
        System.out.println("delivery=" + delivery);
    }
}
```

```text
customer=Amina subtotal=720
delivery=standard-free
```

<!-- /quick -->

`Math.multiplyExact` states that an order subtotal may not overflow silently. The conditional operator fits this choice between two values; if each branch also changed several pieces of state, a regular `if` would be clearer.

### Guard clauses and a `switch` expression

The second program rejects a negative count, handles an empty order, and then classifies the item count. Early returns move invalid and special cases out of the main decision path.

```java
// file: ShippingDecision.java
public class ShippingDecision {
    static String classify(int itemCount, boolean expedited) {
        if (itemCount < 0) {
            return "invalid";
        }
        if (itemCount == 0) {
            return "empty";
        }

        String band = switch (itemCount) {
            case 1, 2 -> "small";
            case 3, 4, 5 -> "medium";
            default -> "large";
        };

        return expedited && itemCount <= 5
                ? band + "-express"
                : band + "-standard";
    }

    public static void main(String[] args) {
        for (int itemCount : new int[] {-1, 0, 2, 7}) {
            System.out.println(itemCount + " -> " + classify(itemCount, true));
        }
    }
}
```

```text
-1 -> invalid
0 -> empty
2 -> small-express
7 -> large-standard
```

The arrow form cannot fall through from `small` into `medium`. `default` covers every integer above `5`, while the guard clauses already handle negative numbers and zero, so each path has a visible meaning.

### Skipping and stopping in a loop

The third program scans inventory changes. A zero change carries no information and is skipped with `continue`; after stock drops below the threshold, later data is irrelevant to this alert, so `break` stops the scan.

```java
// file: StockScan.java
public class StockScan {
    public static void main(String[] args) {
        int[] changes = {3, 0, -2, 5, -8, 4};
        int balance = 10;

        for (int change : changes) {
            if (change == 0) {
                continue;
            }

            balance = Math.addExact(balance, change);
            System.out.println("change=" + change + " balance=" + balance);

            if (balance < 9) {
                System.out.println("reorder");
                break;
            }
        }
    }
}
```

```text
change=3 balance=13
change=-2 balance=11
change=5 balance=16
change=-8 balance=8
reorder
```

Enhanced `for` hides the index because this decision needs only each element's value. Once `break` runs, the final `4` is not processed; a test expectation must include that fact.

### A visible short-circuit path

The final program records the name of every Boolean check. After the second one fails, `&&` does not execute the third; the same rule protects the later division.

```java
// file: EvaluationOrder.java
import java.util.ArrayList;
import java.util.List;

public class EvaluationOrder {
    static boolean check(List<String> events, String name, boolean result) {
        events.add(name);
        return result;
    }

    public static void main(String[] args) {
        List<String> events = new ArrayList<>();
        boolean accepted = check(events, "stock", true)
                && check(events, "credit", false)
                && check(events, "fraud", true);

        int denominator = 0;
        boolean safe = denominator != 0 && 100 / denominator > 2;

        System.out.println("events=" + events);
        System.out.println("accepted=" + accepted + " safe=" + safe);
    }
}
```

```text
events=[stock, credit]
accepted=false safe=false
```

The list preserves the actual evaluation order: `fraud` never appears. Replacing `&&` with `&` in the second expression would still execute the right side and throw `ArithmeticException`, so the two operators are not stylistic alternatives.

## Pitfalls

> **Pitfall:** Applying field and array-element defaults to local variables. `int count; System.out.println(count);` does not print `0`; it fails to compile.

**Fix:** Give the variable a meaningful initial value at its declaration, or ensure every branch assigns it. Do not insert a fake zero merely to satisfy the compiler; if “not calculated yet” is valid, model that state explicitly.

> **Pitfall:** Comparing the business values of two strings or wrapper objects with `==`. Constants in a test may happen to share an object, making reference comparison look correct until runtime input arrives.

**Fix:** Use `equals` according to the type contract, or `Objects.equals` when `null` is possible. Use reference `==` only when the actual question is whether both references identify the same object.

> **Pitfall:** Looking only at the receiving variable's type, not the intermediate expression. `long total = unitPrice * quantity` can first overflow as `int` when both operands are `int`.

**Fix:** Promote an operand before the operation, or reject overflow with exact methods such as `Math.addExact` and `multiplyExact`. See `java/data-types` for the complete basis for choosing numeric boundaries.

> **Pitfall:** Writing an array loop bound as `index <= values.length`, or changing the index in several branches of the loop body. These errors often appear only with an empty array or on the final iteration.

**Fix:** Use the half-open range `0 <= index && index < values.length`, and keep the update in the loop header. Switch to enhanced `for` when only element values are needed, and test empty, one-element, and termination boundaries.

> **Pitfall:** Mixing traditional colon `switch` groups with arrow rules and assuming a colon branch stops automatically. A missing `break` continues into later statement groups, a bug generated code often introduces when adding a case.

**Fix:** Prefer arrow rules in new code and use a `switch` expression when a branch should produce a value. In a traditional switch, document and test intentional fall-through; otherwise end each statement group explicitly with `break`.

<!-- deep -->

## Definite assignment and reachability

Definite-assignment analysis tracks a variable's state at each program point, not the value observed in one test run. When both branches of an `if` assign a local, a later read can be legal. When only one branch assigns it, the compiler rejects the read unless the other path returns or throws before reaching that point.

The analysis is deliberately conservative. The compiler proves state from language-defined expression and statement structure; it does not execute arbitrary business methods to guess that they always return a particular result. Hiding a crucial condition behind an ordinary Boolean method may convince a person that the path is safe without changing the local variable's definite-assignment state.

A blank `final` local tightens the rule: it must be definitely assigned before a read and definitely unassigned before an assignment. Two mutually exclusive branches may each assign it once because any actual path performs one assignment. Assigning the same blank `final` local inside a loop usually cannot be proven to happen only once.

Reachability analysis answers a different question: whether a statement can possibly execute. An ordinary statement immediately after an unconditional `return` is unreachable and causes a compile error. This check catches some dead code, but it does not prove that business conditions are meaningful or replace coverage and boundary tests.

Pattern variables use control-flow scope too. On the right of `value instanceof String text && !text.isBlank()`, `text` is available because that side runs only after a successful match. Replacing `&&` casually with `&` changes evaluation behavior and can also destroy the flow structure the compiler uses to establish that a pattern variable is available.

## Expression semantics beneath the syntax

Left-to-right evaluation means an earlier operand's side effect or exception occurs before a later operand. A method call evaluates the target reference first, then its arguments in order, and only then enters the method body. If any step completes abruptly, later operands and the method body do not run.

Short-circuit operators add conditional skipping to that general order. `a != null && a.isReady()` reads `a` and completes the null comparison first, then calls the method only when the result is `true`. Swapping the sides or using `&` changes the contract, not merely the formatting.

Compound assignment is more specific than it looks. `target += value` evaluates the left side once, performs the operation, and includes an implicit conversion back to the left-side type. It is not always mechanically interchangeable with `target = target + value`: the latter may require an explicit conversion, and a complex left side could be evaluated twice.

The conditional operator `condition ? left : right` evaluates only one selected result expression. It is suited to selecting a value, not hiding several state changes. Its result type also depends on both branches and the target context, so apparently similar branches can still trigger boxing or numeric conversion.

Increment and decrement expressions both produce a value and change a variable. Postfix `index++` produces the value before the change; prefix `++index` produces the changed value. The distinction rarely affects the next iteration when used alone as a loop update, but its timing directly affects results when embedded in an array access or method argument.

## Loop invariants and boundaries

A loop invariant is a condition that should hold at the start or end of every iteration. When scanning `values[0..index)`, for example, the half-open region can mean “already processed,” while `index..length` remains unprocessed. Putting that condition in a test or comment gives a reviewer more than the vague statement “iterate the array.”

Termination also needs a measure that moves monotonically toward a boundary. An index loop commonly decreases `length - index` each time; a retry loop needs a maximum attempt count, deadline, or external cancellation. “It breaks on success” does not prove that the failure path terminates.

The half-open interval `[start, end)` makes its length naturally equal to `end - start`, and `start == end` represents an empty interval. It matches the legal upper bound of an array and removes special arithmetic around the last element. A reverse loop needs its bounds derived again rather than a mechanical reversal of increment signs.

| Input shape | Behavior to prove |
| --- | --- |
| Empty input | The body runs zero times and the result is defined |
| One element | The sole element is processed exactly once |
| Exactly at the threshold | The comparison matches the inclusive or exclusive contract |
| Early match | No extra side effect occurs after `break` |
| No match | The loop still terminates and returns the specified result |

The loop variable in enhanced `for` receives the current element value. For a primitive array, reassigning that variable does not update the array element; for reference elements, mutating an object through the reference may still be visible. Use an explicit index when replacing array slots so the write location is clear.

The guaranteed first execution of `do-while` fits a contract that must show a menu or attempt an operation once before checking. When zero executions are valid, `while` is usually more direct. Answering how many times the body must run at minimum before choosing the loop form avoids duplicated initialization written merely to fit the syntax.

## `switch` completeness and abrupt completion

A traditional `switch` statement permits fall-through between colon-labeled statement groups; that behavior is part of the original syntax. Arrow rules do not fall through, and their right side can be an expression, a block, or a `throw` statement. The forms serve different compatibility needs; choosing one consistently makes new code easier to review.

A `switch` expression is intended to produce a value, so it must be complete. A block rule cannot carry its result with an ordinary `break`; it uses `yield`. A rule that throws completes abruptly and does not need to provide a value on that path.

When every current enum constant is listed, the compiler can accept an expression without a source-level `default`. Recompiling after a new enum constant is added then exposes the missing branch. Running a newer enum class with older caller bytecode is still a binary-evolution scenario, however, so deployment compatibility tests remain necessary.

`break`, `continue`, `return`, and `throw` all make a statement or expression complete abruptly, but they have different targets. `break` leaves a loop or its target statement, `continue` advances a loop, `return` leaves a method, and `throw` searches the call stack for a handler. Naming the target precisely is more useful in nested control-flow review than calling all four operations “jumps.”

The maintenance cost of complex branching usually comes from path combinations, not the number of keywords. Rejecting invalid states with guard clauses before a main `switch` handles valid domain values reduces those combinations. If labeled jumps or shared mutable flags are still needed across several levels, extracting a named method often makes inputs, results, and early exits testable.

<!-- /deep -->

[Checkpoint: java/fundamentals](https://codewiki.com/java/fundamentals/#checkpoint)

## Further reading

- [Java Language Specification: names](https://docs.oracle.com/javase/specs/jls/se25/html/jls-6.html)
- [Java Language Specification: blocks, statements, and patterns](https://docs.oracle.com/javase/specs/jls/se25/html/jls-14.html)
- [Java Language Specification: expressions](https://docs.oracle.com/javase/specs/jls/se25/html/jls-15.html)
- [Java SE 25 API: `java.lang` package](https://docs.oracle.com/en/java/javase/25/docs/api/java.base/java/lang/package-summary.html)
