Chapter 2: Foundations of the Java Language
Every programming language has its own internal logic; its way of expressing ideas through structure and rules. Java’s logic is grounded in clarity, order, and predictability. It was designed to help programmers think in terms of classes, objects, and strict type boundaries, while still feeling familiar to anyone who has worked with other C-like languages.
At its heart, Java encourages you to write code that is both explicit and self-explanatory. Its syntax may appear formal at first, but that discipline pays off in code that scales gracefully and remains understandable even after years of maintenance. Each program has a clear entry point, each variable a defined type, and each class a distinct purpose within a hierarchy of behavior and inheritance.
Understanding the foundations of Java means understanding how it thinks about structure. The language expects everything to live inside a class, and every piece of code to follow predictable rules about scope, access, and typing. This may feel more constrained than dynamically typed languages, but those constraints provide the consistency that makes large-scale Java systems reliable and maintainable.
This chapter introduces the building blocks that define how Java code works and how programs are interpreted by the Java Virtual Machine. You will learn how statements and expressions form the core of Java’s logic, how identifiers and variables are defined and scoped, and how primitive types, constants, and naming conventions shape the rhythm of the language. Together, these ideas create the framework upon which every Java application is built.
By mastering these fundamentals, you will develop an instinct for how Java organizes data and behavior, and why its design has remained steady for decades. Once these core principles feel natural, the rest of the language (its objects, generics, exceptions, and concurrency) will make far more sense, because they all follow the same consistent foundation introduced here.
Statements, expressions, and blocks
Every Java program is built from statements and expressions, the smallest units of meaning in the language. Understanding how these pieces interact is essential to reading and writing Java fluently. A statement performs an action. An expression produces a value. Together, they form the logical rhythm that drives all program flow.
Expressions
An expression is any piece of code that evaluates to a value. It can be as simple as a literal number or as complex as a nested method call. Expressions appear inside statements, parameters, and control structures. They do not perform actions on their own. Rather they simply compute and return something that can be used elsewhere.
int total = 3 + 4; // An expression
boolean ready = total > 5; // An expression
System.out.println(total); // An expression used as an argument
Java evaluates expressions in a defined order, respecting operator precedence and parentheses. Each expression produces a value whose type is known at compile time. This predictability is part of Java’s static type system, ensuring that incompatible operations (like adding a number to a string) are caught before the program runs.
Statements
A statement is a complete instruction that tells the Java Virtual Machine to do something. Statements usually end with a semicolon (;) and can include expressions as part of their structure. Assignment, method calls, and control flow instructions like if and while are all statements.
int count = 0; // declaration statement
count++; // expression statement
System.out.println(count); // method call statement
In Java, each statement executes in sequence unless altered by control flow. Statements can appear alone or be grouped together into larger structures called blocks.
Blocks
A block is a group of one or more statements enclosed in braces ({…}). Blocks define scope, or regions of code where variables exist and can be accessed. They are used in method bodies, control structures, and class definitions. For example:
if (count > 0) {
System.out.println("Positive count");
count--;
}
Here, the two statements inside the braces form a block that executes only if the condition is true. Variables declared within a block exist only inside that block and are discarded once it ends.
Declarations vs. executable statements
Java distinguishes between declarations (which define variables, methods, or classes) and executable statements (which perform actions when the program runs). This separation makes Java’s flow easy to follow and its structure consistent. Declarations tell the compiler what exists; executable statements tell the runtime what to do.
Comments and documentation
Java code is meant to be read by people as well as compiled by the machine. Comments and documentation are how you explain intent, clarify design choices, and help future readers (including your future self) understand why the code was written the way it was. They never affect program execution but play an essential role in maintainability and teamwork.
Single-line and multi-line comments
Single-line comments begin with // and extend to the end of the line. They are ideal for short notes, explanations, or temporary reminders. Multi-line comments begin with /* and end with */, allowing you to span several lines or temporarily disable blocks of code during testing.
// This is a single-line comment
int count = 5; // You can also place it at the end of a statement
/*
This is a multi-line comment.
It can span several lines and is often used
for larger explanations or code sections.
*/
Good comments explain why something is done, not what the code does. The code itself should make the “what” clear through naming and structure. Over-commenting every line makes code harder to read rather than easier.
Javadoc comments
Java introduced a formal documentation system called Javadoc, which allows you to create rich, searchable API documentation directly from the source code. A Javadoc comment begins with /** and ends with */. It is placed immediately before a class, method, or field declaration.
/**
* Calculates the area of a rectangle.
* @param width The width of the rectangle
* @param height The height of the rectangle
* @return The area as an integer
*/
public int area(int width, int height) {
return width * height;
}
Each tag (such as @param and @return) provides structured information that can be turned into formatted documentation by running the javadoc tool, which generates HTML pages for your classes and methods. This makes Javadoc comments part of the official interface for any library or project.
Best practices for documentation
- Write concise, accurate summaries in the first sentence of each Javadoc block.
- Keep parameter and return descriptions clear and free of repetition.
- Update comments whenever code changes, as outdated documentation can be worse than none.
- Reserve detailed explanations for complex logic or algorithms that cannot be made clearer through naming alone.
Many IDEs can generate Javadoc templates automatically as you type, prompting you to fill in parameter and return descriptions. For public APIs and reusable code, consistent Javadoc use is a sign of professional quality.
Identifiers, keywords, and naming rules
In Java, every variable, class, method, and constant is identified by a name called an identifier. Identifiers give structure and meaning to code, helping both the compiler and the reader understand what each element represents. Alongside these identifiers, Java reserves certain keywords that have predefined meanings and cannot be used as names. Together, they form the basic vocabulary of the language.
Identifiers
An identifier is a name you choose for something you define, such as a class, variable, or method. Identifiers must follow a few simple rules:
- They may contain letters, digits, underscores (
_), or dollar signs ($). - They must begin with a letter, underscore, or dollar sign, not a digit.
- They are case-sensitive:
total,Total, andTOTALare all distinct names. - They cannot be the same as any reserved Java keyword.
Valid examples include:
int totalCount;
String customerName;
double _balance;
boolean $debugMode;
Although the dollar sign is technically allowed, it is rarely used except by tools that generate code automatically. For readability, prefer simple, descriptive names made of letters and digits.
temperatureCelsius is clearer than tempC, and maxItems conveys more meaning than x. Good names reduce the need for comments.
Keywords
Java reserves a set of words for the language itself. These keywords define syntax and structure, and cannot be redefined or used as identifiers. Examples include class, public, static, void, if, else, return, for, switch, and many others.
A full list of keywords (as of Java 25) includes:
abstract assert boolean break byte case catch char class
const continue default do double else enum extends final
finally float for if implements import instanceof int
interface long native new package private protected public
return short static strictfp super switch synchronized this
throw throws transient try void volatile while
Java also reserves true, false, and null as literals that cannot be used as identifiers. In addition, newer versions may introduce contextual keywords, which are words that behave like keywords only in specific situations (for example, var or yield).
Naming conventions
Beyond the basic rules, Java developers follow consistent conventions to make code readable and predictable. These conventions are not enforced by the compiler but are considered standard practice throughout the Java ecosystem:
- Classes and interfaces: Use PascalCase, where each word begins with a capital letter, such as
DataProcessororUserProfile. - Methods and variables: Use camelCase, where the first word is lowercase, and subsequent words are capitalized, such as
calculateTotal()oritemCount. - Constants: Use UPPER_CASE_WITH_UNDERSCORES, such as
MAX_SIZEorDEFAULT_TIMEOUT. - Packages: Use all lowercase, often reversed domain notation, such as
com.example.project.
package com.example.store;
public class Product {
private double price;
public static final double TAX_RATE = 0.2;
public double calculateTotal() {
return price + (price * TAX_RATE);
}
}
These conventions help others immediately recognize the role of each name in your program. When everyone follows the same patterns, codebases become easier to navigate and maintain.
Primitive types and literals
All data in Java belongs to one of two broad categories: primitive types or reference types. Primitive types store simple values directly; numbers, characters, and truth values. Reference types store references to objects in memory. This section focuses on the eight primitive types, which form the foundation of all data handling in Java.
The eight primitive types
Java defines exactly eight primitive data types, each with a fixed size and specific range of values. This consistency ensures that Java behaves the same way across all platforms.
| Type | Size | Example | Description |
byte | 8-bit | byte b = 10; | Small integer, often used for data streams or compact storage. |
short | 16-bit | short s = 2000; | Medium-range integer type. |
int | 32-bit | int n = 123456; | Standard integer type used most often. |
long | 64-bit | long l = 12345678900L; | Large integer values, requires L suffix. |
float | 32-bit | float f = 3.14F; | Single-precision floating-point numbers, requires F suffix. |
double | 64-bit | double d = 3.14159; | Double-precision floating-point, default for decimal values. |
char | 16-bit | char c = 'A'; | Represents a single Unicode character. |
boolean | 1-bit (logical) | boolean ready = true; | Represents truth values: true or false. |
Because Java defines exact sizes for each type, a program that runs on one system behaves identically on another. This predictability is part of what makes Java portable across platforms and architectures.
int for whole numbers and double for decimals. The other types exist for precision control, memory efficiency, or compatibility with specific data formats.
Literals
A literal is a fixed value written directly into your code. Java supports several literal forms for each primitive type.
int count = 10; // integer literal
long distance = 45_000L; // underscores improve readability
double price = 19.99; // decimal literal
float ratio = 0.75F; // float literal requires F
char letter = 'J'; // single character literal
boolean success = true; // boolean literal
Numeric literals can also be written in different bases:
int hex = 0xFF; // hexadecimal (255)
int bin = 0b1010; // binary (10)
int oct = 012; // octal (10)
Underscores within numeric literals make large numbers easier to read, but they have no effect on value:
long population = 7_950_000_000L;
double literal to a float variable without the F suffix, for example, will cause a compilation error because Java does not perform implicit narrowing conversions.
Type ranges and overflow
Each primitive type has a defined minimum and maximum value. When a value goes beyond that range, it overflows or underflows, wrapping around instead of producing an error.
byte b = 127;
b++; // wraps around to -128
This behavior can lead to subtle bugs if not expected. Java does not raise an exception for overflow, so it is up to the programmer to ensure calculations stay within range or to use larger data types when necessary.
Default values
When primitive fields are declared inside a class but not initialized, Java assigns them default values automatically. Local variables inside methods, however, must be explicitly initialized before use.
| Type | Default value |
byte, short, int, long | 0 |
float, double | 0.0 |
char | '\u0000' (null character) |
boolean | false |
This behavior simplifies class construction and ensures all fields start with predictable values.
Variables, constants, and scope
Variables are the named storage locations that hold your program’s data. Each variable in Java has a specific type, a name, and a lifetime. Understanding how variables are declared, how long they exist, and where they can be accessed (known as their scope) is essential to writing reliable and maintainable programs.
Declaring and initializing variables
Every variable in Java must be declared with a type before it can be used. The declaration tells the compiler what kind of data the variable will hold, allowing it to check that only valid operations are performed on it. You can declare and initialize a variable in one statement or separately:
int count; // declaration
count = 10; // initialization
String name = "Java"; // declaration and initialization together
Java also allows multiple variables of the same type to be declared on one line, but this is best reserved for simple cases:
int x = 1, y = 2, z = 3;
userCount or filePath make code easier to understand and maintain.
Variable types and mutability
Variables can represent different kinds of data. Primitive variables store values directly (like numbers or characters), while reference variables store a reference to an object in memory. Both kinds of variables can be reassigned unless declared as constants using the final keyword.
int score = 100;
score = 120; // allowed
final int MAX_SCORE = 200;
MAX_SCORE = 250;
// error: cannot assign a value to final variable
Declaring a variable as final makes it immutable, such that it cannot be reassigned once a value is set. Constants are typically written in uppercase letters with underscores separating words.
Variable scope
The scope of a variable defines where it can be accessed within a program. In Java, scope is determined by the block structure created with braces ({…}). A variable declared inside a block exists only within that block and is destroyed when the block ends.
public class ScopeExample {
public static void main(String[] args) {
int outer = 10;
{
int inner = 5;
System.out.println(outer + inner);
// both visible here
}
// System.out.println(inner);
// error: inner no longer exists
}
}
This block-based scoping helps prevent naming conflicts and keeps variables local to where they are needed. Variables declared at the class level (outside any method) are known as fields, while those declared inside methods or blocks are local variables.
Local, instance, and class variables
Java defines three main categories of variables, based on where they are declared:
- Local variables these are declared inside methods or blocks and exist only during execution of that code.
- Instance variables these are declared within a class but outside any method; each object of that class has its own copy.
- Class variables these are declared with the
statickeyword; shared among all instances of the class.
public class Counter {
static int totalCount = 0; // class variable
int value; // instance variable
public Counter(int start) {
value = start;
totalCount++;
}
public void increment() {
int step = 1; // local variable
value += step;
}
}
Here, totalCount belongs to the class itself, value belongs to each individual object, and step exists only while the increment() method runs.
Variable lifetime
Each variable’s lifetime is tied to its scope. Local variables exist only while their method or block executes. Instance variables persist as long as the object they belong to remains in memory. Class variables persist for as long as the program or class loader keeps the class loaded.
Java’s garbage collector automatically reclaims memory when objects are no longer referenced. You never need to manually free variables, but you should always limit their scope and lifetime to what is necessary. Doing so keeps your programs efficient and reduces the chance of errors.