Classes, methods and blocks
The four things every Java program is made of, why one file can produce three class files, and the block that decides what exists.
Every Java program is the same four things nested inside each other: a class holds methods, a method holds statements, and statements are grouped into blocks. Once you can name those in a file you have never seen, reading unfamiliar Java stops being decoding and starts being reading.
The smallest complete program, named part by part
public class Greet { // a class
public static void main(String[] args) { // a method
String who = "world"; // a statement
System.out.println("hello " + who); // another
}
}class Greet— a named thing that holds code. In Java nothing lives outside one; there are no free-floating functions.main— a method: a named block of statements you can call, with a list of parameters and a return type.String who = "world";— a statement. It ends with a semicolon, which is how the compiler knows where it stops. Newlines mean nothing to Java.{ ... }— a block. Every pair groups statements and, as the next section shows, decides what is visible.
The file is Greet.java because a public class must be in a file of its own name. That is a rule about public specifically; a non-public class can live anywhere.
One file does not mean one class
That last rule is often mis-learned as one file, one class. Compile a file with a nested class and an anonymous one in it:
public class Shape {
static class Inner { }
public static void main(String[] a) {
Runnable r = new Runnable() { public void run() { } };
}
}$ javac Shape.java
$ ls *.class
Shape$1.class Shape$Inner.class Shape.classThree class files from one source file. The compiler's unit is the class, not the file, and the naming is worth recognising: Outer$Inner for a named nested class, Outer$1 for an anonymous one, numbered in the order they appear.
You will meet those exact names again in a stack trace. A frame reading OrderService$1.run is an anonymous class inside OrderService, and now it is a place you can find rather than a puzzle.
A block decides what exists
Braces are not only punctuation. Anything declared inside a block stops existing when the block ends:
{
int insideTheBlock = 1;
System.out.println("inside: " + insideTheBlock); // fine
}
System.out.println(insideTheBlock); // not fineScope.java:4: error: cannot find symbol
System.out.println(insideTheBlock);
^
symbol: variable insideTheBlockThat is why a variable declared in a for loop's header is gone after the loop, and why declaring one inside an if and using it after does not compile. The rule is the same everywhere: a name lives from its declaration to the closing brace of the block it is in, and nowhere else.
It is also a tool. Declaring a variable in the smallest block that needs it is how you stop it being reused by accident twenty lines later — which the local variables lesson takes further.
Expressions and statements are different things
Worth separating early, because error messages assume you know:
- An expression produces a value.
2 + 2,name.length(),age > 18. - A statement does something. An assignment, a method call used for its effect, an
if, areturn.
Some expressions can be statements on their own — a method call is the usual case. Most cannot: 2 + 2; is not a legal statement, and the compiler says not a statement, which reads as nonsense until you know it means this produces a value and does nothing with it.
The distinction is why if (x = 5) fails in Java and quietly works in C: x = 5 is an expression producing 5, an int where a boolean is required. Java's type system catches it; that is a deliberate design decision and not an accident.
Keywords and identifiers
Keywords are the roughly fifty words the language reserves — class, public, static, if, return, new, final. You cannot use one as a name; that is the entire rule, and the compiler enforces it immediately.
Three details worth carrying:
true,falseandnullare not keywords, they are literals. The effect is the same: you cannot name a variablenull.- Some words are keywords only in context.
var,record,sealed,yieldandpermitswere added without breaking code that used them as names, so a variable calledrecordstill compiles. They are called contextual keywords. gotoandconstare reserved and unused. They exist in the keyword list so that a program using them as names fails clearly rather than working and then breaking if the words are ever given meaning.
Identifiers — your names — may contain letters, digits, _ and $, and may not start with a digit. The mechanical rule is uninteresting; the convention is not, and the naming lesson covers what a reviewer actually reads. One piece of it belongs here: $ is legal and reserved by convention for generated code, which is why the compiler used it for Shape$Inner. Do not put it in a name you write.
Comments, and the two kinds worth writing
// to the end of the line
/* across
several lines */
/** a documentation comment, read by javadoc and by your IDE */The compiler discards all three. They exist for the next person, who is usually you.
The useful distinction is not the syntax, it is what the comment says:
// add one to i
i++; // noise: it repeats the code and rots when the code changes
// Retry three times: the payment provider returns 503 during their
// nightly maintenance window and recovers within ~2 seconds.The second cannot be recovered from the code at any price. Comment the why, and the surprising: a workaround for somebody else's bug, a constant that came from a measurement, a decision that looks wrong and is not.
The third form, /** ... */, is different in kind because it is published. It becomes the tooltip in every caller's IDE, so it is written for someone who will never open your file: what the method does, what its parameters mean, what it returns, and what it throws.