Study Notes
Free high-yield revision notes for Java Platform, JVM, JDK, Types, Operators and Control Flow
Java Platform, JVM, JDK, Types, Operators and Control Flow
Why this topic matters
Every later Java topic, from collections to JDBC, assumes you can write a class, compile it, run it and reason precisely about what each statement does. This chapter covers the Java platform (how source code becomes a running program), the tools in the JDK, the eight primitive types, conversions, operators and control statements. It ends with the session lab: a four-operation calculator.
The syllabus heading says "Java 8 basics". Everything in the core language shown here works on Java 8 unless a section says otherwise. The reference version for these notes is Java 17 (LTS), and newer features such as arrow-style switch (Java 14+) are labelled. All examples were compiled with javac --release 17.
Overview and features of Java
Java is a general-purpose, class-based, object-oriented language. Programs are compiled into a portable instruction format called bytecode, which a Java Virtual Machine (JVM) executes. Its commonly listed features are:
| Feature | What it means in practice |
|---|---|
| Simple and familiar | C-like syntax without pointer arithmetic, header files or manual free |
| Object-oriented | Code is organised into classes and objects (see topic 3) |
| Platform independent / architecture-neutral | The same .class file runs on any OS that has a compatible JVM |
| Robust | Strong static typing, mandatory initialisation of locals, array bounds checks, exception handling and garbage collection |
| Secure | No raw pointers; the JVM verifies bytecode before running it |
| Multithreaded | Threads are built into the language and library |
| High performance | The JVM's just-in-time (JIT) compiler turns frequently used bytecode into native machine code |
| Dynamic and distributed | Classes are loaded when first needed; rich networking libraries |
"Write once, run anywhere" means compiled class files target a defined runtime contract. It does not mean one native executable runs on every processor: each platform needs its own JVM, and the JVM itself is platform-specific.
From source code to running program
Hello.java --javac--> Hello.class (bytecode) --java--> JVM loads, verifies and executes it- You write source code in a
.javafile. - The compiler
javacchecks syntax and types and writes one.classfile per class. A class file contains bytecode plus metadata (fields, methods, constant pool). - The launcher
javastarts a JVM, which loads the class, verifies that the bytecode is safe, links it, initialises static state, and executesmain. - The JVM first interprets bytecode and then JIT-compiles hot paths into native code. It also manages memory and performs garbage collection.
Compilation errors (for example a type mismatch) stop javac from producing a valid class file. Runtime exceptions (for example dividing an int by zero) happen while an already compiled program runs.
Compile and run your first program
/**
* First program: prints a greeting.
*/
public class Hello {
public static void main(String[] args) {
System.out.println("Hello, Java");
System.out.println("Arguments received: " + args.length);
}
}javac Hello.java # creates Hello.class
java Hello # class name, no .class extension
java Hello a b # two command-line arguments
java Hello.java # Java 11+: compile in memory and run a single source fileOutput of java Hello and java Hello a b:
Hello, Java
Arguments received: 0
Hello, Java
Arguments received: 2Rules to remember:
- A
publictop-level class must be saved in a file with exactly the same, case-sensitive name (Hello.java). - The launcher looks for
public static void main(String[] args).String... argsis also accepted, and the parameter name can change. Ifstaticis missing, Java 17 printsError: Main method is not static in class .... javatakes a class name, not a file path.
What the JVM does
The JVM is an abstract machine defined by a specification; Oracle HotSpot (used by OpenJDK builds) is the most common implementation. In outline it has a class loader subsystem (load, verify, prepare, resolve, initialise), runtime data areas (a shared heap for objects, class metadata, and per-thread stacks of frames holding local variables and an operand stack, plus a program counter per thread) and an execution engine (interpreter, JIT compiler and garbage collector). Declaring a variable is not the same as creating an object, and loading a class is not the same as creating an instance. JVM internals are studied in detail in topic 14.
JDK, JRE and JVM
| Term | Contains | Who needs it |
|---|---|---|
| JVM | The engine that loads and runs bytecode | Part of every runtime |
| JRE (Java Runtime Environment) | JVM + standard class libraries | Someone who only runs Java programs |
| JDK (Java Development Kit) | JRE + development tools (javac, jdb, javadoc, jar, ...) | Developers |
So JDK ⊃ JRE ⊃ JVM. Historically the JRE was a separate download. Since Java 11 Oracle no longer ships a separate JRE; you install a JDK (which includes the runtime), some vendors still publish JRE packages, and the jlink tool can build a trimmed runtime for one application. The conceptual distinction — development tools versus runtime — remains exam-relevant.
JDK tools you will use
| Tool | Purpose | Example |
|---|---|---|
javac | Java compiler: .java → .class | javac Hello.java |
java | Launcher: starts the JVM and runs a class | java Hello |
jdb | Command-line debugger: breakpoints, stepping, inspecting variables | javac -g Hello.java then jdb Hello, stop in Hello.main, run, locals, step, cont |
javadoc | Generates HTML API documentation from /** ... */ comments | javadoc -d docs Hello.java |
jar | Packages class files into a .jar archive (topic 2) | jar --create --file app.jar ... |
javap | Disassembles a class file to show its members or bytecode | javap Hello |
jshell | Interactive shell (Java 9+) for trying expressions | jshell |
javap Hello on the class above prints its compiled members, including the default constructor the compiler added:
Compiled from "Hello.java"
public class Hello {
public Hello();
public static void main(java.lang.String[]);
}Structure of a Java class
A source file follows a fixed order: an optional package declaration, then import declarations, then one or more type declarations. Inside a class you place fields (state), constructors (initialisation), methods (behaviour) and, optionally, nested types. Statements live inside methods, constructors or initialiser blocks.
package com.acme.payroll; // 1. optional package (topic 2)
import java.util.List; // 2. imports
public class Payslip { // 3. type declaration
private double basic; // field
public Payslip(double basic) { // constructor
this.basic = basic;
}
public double gross() { // method
return basic * 1.4;
}
}Comments: // to end of line, /* ... */ for blocks, and /** ... */ for documentation read by javadoc.
Variables and their scope
A variable's scope is the region of code where its name can be used; its lifetime is how long its storage exists.
| Kind | Declared | Scope | Lifetime | Default value? |
|---|---|---|---|---|
| Local variable | Inside a method or block | From declaration to end of the enclosing block | While the block executes | No — must be assigned before use |
| Parameter | In a method/constructor header | Whole method body | During the call | Receives the argument value |
| Instance field | In a class, without static | Whole class (through an object) | As long as the object exists | Yes |
| Static field | In a class, with static | Whole class | While the class is loaded | Yes |
public class ScopeDemo {
static int counter = 0; // static (class) variable
int instanceValue = 5; // instance variable
public static void main(String[] args) {
int total = 0; // local to main
for (int row = 1; row <= 2; row++) { // row: local to this loop
int rowSum = 0; // re-created on every outer iteration
for (int col = 1; col <= 3; col++) {
rowSum += col; // inner loop can see row, rowSum, total
}
total += rowSum;
// System.out.println(col); // compile-time error: col is out of scope
}
// System.out.println(row); // compile-time error: row is out of scope
counter++;
System.out.println("total=" + total + " counter=" + counter
+ " instanceValue=" + new ScopeDemo().instanceValue);
}
}Output: total=12 counter=1 instanceValue=5.
In nested loops, an inner block can see variables of the outer blocks, but not the other way round. Java also forbids declaring a local variable with the same name as another local still in scope: int n = 1; for (...) { int n = 2; } fails with "variable n is already defined". A local variable may have the same name as a field; it then hides the field, and this.name refers to the field.
Primitive data types
Java has exactly eight primitive types. Their sizes are fixed by the language, not by the machine.
| Type | Size | Range / values | Default (fields, array elements) | Literal example |
|---|---|---|---|---|
byte | 8-bit signed | -128 to 127 | 0 | (byte) 10 |
short | 16-bit signed | -32,768 to 32,767 | 0 | (short) 300 |
int | 32-bit signed | -2,147,483,648 to 2,147,483,647 | 0 | 42 |
long | 64-bit signed | about ±9.22 × 10^18 | 0L | 8_100_000_000L |
float | 32-bit IEEE 754 | about 7 decimal digits | 0.0f | 0.75F |
double | 64-bit IEEE 754 | about 15–16 decimal digits | 0.0 | 1.8e1 |
char | 16-bit unsigned UTF-16 code unit | 0 to 65,535 | '\u0000' | 'A', '\n' |
boolean | true/false | true, false | false | true |
For a signed integer of b bits the range is -2^(b-1) to 2^(b-1) - 1. boolean is not a number in Java: if (1) does not compile, and there is no sizeof operator. A char holds one UTF-16 code unit; a few rare Unicode characters (such as many emoji) need two code units, so String.length() counts code units, not always visible characters.
Literals
long population = 8_100_000_000L; // L suffix: too big for int
int mask = 0b1111_0000; // binary literal = 240
int color = 0xFF_AA_00; // hexadecimal literal = 16755200
int octal = 017; // leading 0 means octal = 15
double rate = 1.8e1; // 18.0; decimal literals are double by default
float ratio = 0.75F; // F suffix required for float
char newline = '\n'; // escape sequence- Integer literals are
intunless suffixed withL(prefer uppercase; lowercasellooks like1). - Floating-point literals are
double;float f = 1.5;fails with "possible lossy conversion from double to float". - Underscores may separate digits for readability but not appear at the start/end or next to the decimal point.
Default values and definite assignment
Fields and array elements get default values (0, 0.0, false, '\u0000', null for references). Local variables do not; the compiler checks that every path assigns them before use:
int result;
if (args.length > 0) {
result = 10;
}
// System.out.println(result); // error: variable result might not have been initializedDo not silence this by writing int result = 0; everywhere — that can hide missing logic. Make sure every control-flow path assigns a meaningful value.
`char` is a number too
A char is stored as an unsigned 16-bit code. In arithmetic it is promoted to int:
char letter = 'A';
System.out.println(letter + 1); // 66 (int addition)
System.out.println((char) (letter + 1)); // B (cast back to char)
letter++; // ++ keeps the char type
System.out.println(letter); // B
System.out.println('a' + 'b'); // 195 (97 + 98)
System.out.println("" + 'a' + 'b'); // ab (string concatenation)Primitive variables versus reference variables
A primitive variable holds its value directly. A reference variable (any class, interface, array, enum or record type, including String) holds a reference — a pointer-like handle — to an object, or null. Copying a primitive copies the value; copying a reference copies the handle, so both variables reach the same object.
Reading and writing object fields
Fields are read and written with the dot operator on a reference: reference.field.
class Point {
int x;
int y;
}
public class FieldsDemo {
public static void main(String[] args) {
int a = 10;
int b = a; // copies the value 10
b = 20;
System.out.println("a=" + a + " b=" + b);
Point p = new Point();
p.x = 3; // write a field through the reference
p.y = 4;
Point q = p; // copies the reference, not the object
q.x = 99;
System.out.println("p.x=" + p.x + " p.y=" + p.y);
Point empty = null;
try {
System.out.println(empty.x);
} catch (NullPointerException ex) {
System.out.println("NullPointerException");
}
}
}a=10 b=20
p.x=99 p.y=4
NullPointerExceptionChanging b did not affect a, but changing q.x changed what p sees because both reference one Point. Assigning q = null would change only q. Dereferencing null throws NullPointerException at run time. In real classes fields are usually private and accessed through methods (topic 4).
Type conversion and casting
Widening (automatic)
A widening conversion moves to a type that can hold the whole range: byte → short → int → long → float → double, and char → int. It happens automatically: long widened = 100;, double d = 100; (prints 100.0). Converting a large int or long to float/double can still lose precision.
Narrowing (explicit cast)
A narrowing conversion needs a cast and may lose information:
double price = 99.99;
int truncated = (int) price; // 99 (fraction dropped, not rounded)
System.out.println((int) -3.99); // -3 (truncates toward zero)
int value = 130;
byte narrowed = (byte) value; // -126: only the low 8 bits are kept130 is 1000 0010 in the low 8 bits; read as a signed byte that is 130 − 256 = −126.
Integer overflow
Integer arithmetic wraps around silently using two's-complement — this is defined Java behaviour, not an error:
int max = Integer.MAX_VALUE;
max = max + 1;
System.out.println(max); // -2147483648 (Integer.MIN_VALUE)
long wrong = 1_000_000 * 1_000_000; // int * int overflows first
long right = 1_000_000L * 1_000_000; // long arithmetic
System.out.println(wrong + " " + right); // -727379968 1000000000000
Math.addExact(Integer.MAX_VALUE, 1); // throws ArithmeticException: integer overflowAssigning to a long does not help if the multiplication was already done in int. Use a long operand or Math.addExact/multiplyExact when overflow must be detected.
Promotion in expressions and compound assignment
Before arithmetic, byte, short and char operands are promoted to int; if either operand is long, float or double, the other is promoted to that wider type. So byte + byte is an int:
byte counter = 10;
// counter = counter + 1; // error: possible lossy conversion from int to byte
counter += 1; // OK: compound assignment includes a cast back to byte -> 11
byte edge = 127;
edge += 1; // compiles, but wraps to -128Compound assignment is convenient but can hide truncation: it does not guarantee the result fits.
Operators
| Category | Operators | Example | |||
|---|---|---|---|---|---|
| Unary | + - ++ -- ! ~ and casts | -x, !done, ++i | |||
| Arithmetic (binary) | * / % + - | a % b | |||
| Assignment | = | x = 5 | |||
| Compound assignment | += -= *= /= %= (and bitwise forms) | total += price | |||
| Relational | < > <= >= and instanceof | age >= 18 | |||
| Equality | == != | count == 0 | |||
| Logical | `&& | ! (and non-short-circuit & | ^` on booleans) | a > 0 && b > 0 | |
| Bitwise / shift | `& | ^ ~ << >> >>>` | flags & 4 | ||
| Conditional (ternary) | ?: | x > y ? x : y |
"Unary" operators take one operand; "binary" operators take two; the conditional operator is the only ternary (three-operand) operator.
Arithmetic operators and integer division
The operand types decide the operation, before any assignment:
System.out.println(7 / 2); // 3 int / int -> integer division
System.out.println(7 / 2.0); // 3.5 one double operand
System.out.println(-7 / 2); // -3 truncates toward zero
System.out.println(-7 % 2); // -1 remainder takes the sign of the left operand
System.out.println(7 % -2); // 1
System.out.println(5.5 % 2); // 1.5 % also works on floating point
double a = 7 / 2; // 3.0 integer division happened first
System.out.println(1.0 / 0); // Infinity
System.out.println(0.0 / 0); // NaN
System.out.println(1 / 0); // throws ArithmeticException: / by zeroInteger division by zero throws ArithmeticException; floating-point division by zero gives Infinity, -Infinity or NaN (floating-point values also include a signed zero, -0.0). (double) 7 / 2 gives 3.5 because the cast applies to 7 before dividing.
Increment and decrement
Prefix (++x) changes the variable and yields the new value; postfix (x++) changes it but yields the old value.
int x = 5;
int post = x++; // post = 5, x = 6
int pre = ++x; // x = 7, pre = 7
int y = 5;
int z = y++ + ++y; // 5 + 7 = 12, y ends as 7Java evaluates operands left to right, so y++ supplies 5 (y becomes 6), then ++y makes y 7 and supplies 7. Such expressions are legal but hard to read; put increments in separate statements in real code.
Assignment and compound assignment
= stores the right-hand value and is itself an expression whose value is the stored value; it is right-associative, so a = b = 0 sets both. Compound operators combine an operation with assignment and an implicit cast:
int total = 10;
total += 5; // 15
total *= 2; // 30
total -= 4; // 26
total /= 3; // 8 (integer division)
total %= 4; // 0Relational and equality operators
Relational operators (<, >, <=, >=) compare numeric values and produce a boolean. == and != compare primitive values after promotion (5 == 5.0 is true). For references, == asks whether two references point to the same object, not whether the objects have equal content:
String first = new String("Java");
String second = new String("Java");
System.out.println(first == second); // false: two different objects
System.out.println(first.equals(second)); // true: same characters
System.out.println(Double.NaN == Double.NaN); // false: NaN equals nothingUse equals for content comparison (strings are covered fully in topic 4). Objects.equals(a, b) is a null-safe version. Compare computed floating-point values with a tolerance when the domain allows it.
Logical operators and short-circuit evaluation
&& (AND) and || (OR) evaluate the right operand only if needed: false && ... is already false, true || ... is already true. ! negates. The single-character forms & and | on booleans always evaluate both sides.
static int calls = 0;
static boolean check(boolean result) { calls++; return result; }
boolean r1 = check(false) && check(true); // r1=false, calls=1
boolean r2 = check(false) & check(true); // r2=false, calls=2 (counter reset before)
boolean r3 = check(true) || check(false); // r3=true, calls=1 (counter reset before)Short-circuiting makes guards safe. The null test must come first; reversed, text.isEmpty() would run on null and throw:
if (text != null && !text.isEmpty()) {
System.out.println(text);
}Conditional (ternary) operator
condition ? valueIfTrue : valueIfFalse is an expression that yields one of two values:
int score = 55;
String category = score >= 40 ? "pass" : "fail"; // pass
int bigger = (8 > 3) ? 8 : 3; // 8Its result type is determined from both branches at compile time. Nesting ternaries quickly becomes unreadable; prefer if/else for more than two outcomes.
Bitwise and shift operators (brief)
On integers, &, |, ^ and ~ work bit by bit: 6 & 3 is 2, 6 | 3 is 7, 6 ^ 3 is 5. << shifts left (1 << 3 is 8), >> shifts right keeping the sign (-8 >> 1 is -4) and >>> shifts right filling with zeros (-8 >>> 28 is 15). Shift distances are masked to the operand width (0–31 for int).
String concatenation with `+`
If either operand of + is a String, + concatenates. Evaluation is left to right:
System.out.println("sum=" + 2 + 3); // sum=23
System.out.println("sum=" + (2 + 3)); // sum=5
System.out.println(1 + 2 + "3" + 4 + 5); // 3345: 1+2=3 first, then textPrecedence and associativity
Precedence decides how an expression is grouped; operands are still evaluated left to right. From highest to lowest:
| Level | Operators | Associativity | ||
|---|---|---|---|---|
| Postfix | x++ x-- | left | ||
| Unary | ++x --x + - ! ~ (type) | right | ||
| Multiplicative | * / % | left | ||
| Additive | + - | left | ||
| Shift | << >> >>> | left | ||
| Relational | < > <= >= instanceof | left | ||
| Equality | == != | left | ||
| Bitwise AND / XOR / OR | & then ^ then ` | | left | |
| Logical AND | && | left | ||
| Logical OR | | | left | |
| Conditional | ?: | right | ||
| Assignment | = += -= *= /= %= ... | right |
System.out.println(2 + 3 * 4); // 14
System.out.println((2 + 3) * 4); // 20
System.out.println(10 - 4 - 3); // 3 left-associative: (10 - 4) - 3
System.out.println(20 / 4 * 2); // 10 (20 / 4) * 2When in doubt, add parentheses: they document intent and cost nothing.
Control statements
`if`, `if-else` and the else-if ladder
An if condition must be a boolean; Java does not treat integers as conditions (if (x) with int x is a compile error). Conditions in an else-if ladder are tested top to bottom and the first true branch runs:
static char grade(int marks) {
char grade;
if (marks < 0 || marks > 100) {
throw new IllegalArgumentException("marks outside range");
} else if (marks >= 75) {
grade = 'A';
} else if (marks >= 60) {
grade = 'B';
} else {
grade = 'C';
}
return grade;
}
// grade(82) -> A, grade(60) -> B, grade(12) -> COrder ranges so no branch is unreachable, and always use braces: they prevent the dangling-else problem and bugs when a second statement is added later.
`switch` statement (classic form)
A classic switch jumps to the matching case label and runs from there until a `break` (or the end). Forgetting break causes fall-through:
int day = 2;
switch (day) {
case 1:
System.out.println("Monday");
case 2:
System.out.println("Tuesday");
case 3:
System.out.println("Wednesday");
break;
default:
System.out.println("Other");
}Output: Tuesday then Wednesday — execution started at case 2 and fell into case 3 until the break. Fall-through can be used deliberately to group cases, but is a common defect.
The selector may be byte, short, char, int, their wrappers, an enum, or a String (Java 7+). It cannot be long, float, double or boolean. Case labels must be compile-time constants and unique. A null selector throws NullPointerException in Java 17, so check nullable values first.
String command = "stop";
switch (command) {
case "start": System.out.println("Starting"); break;
case "stop": System.out.println("Stopping"); break; // prints Stopping
default: System.out.println("Unknown command");
}Arrow switch and switch expressions (Java 14+)
Since Java 14 (standard in Java 17), case ... -> labels never fall through, and one case can list several constants:
int month = 4;
switch (month) {
case 12, 1, 2 -> System.out.println("Winter");
case 3, 4, 5 -> System.out.println("Spring"); // prints Spring
default -> System.out.println("Other season");
}A switch expression produces a value. It must be exhaustive (usually via default), and a block body returns its value with yield:
String name = switch (day) { // day = 2
case 1 -> "Monday";
case 2 -> "Tuesday";
default -> "Unknown";
}; // name = "Tuesday"
int length = switch (name) {
case "Monday", "Tuesday" -> {
int n = name.length();
yield n * 10; // 70
}
default -> 0;
};These forms do not exist in Java 8; use the classic form if a course toolchain is configured for Java 8.
`while`, `do-while` and `for`
int i = 1;
while (i <= 3) { // tests before each iteration
System.out.print(i + " "); // 1 2 3
i++;
}
int n = 10;
do { // body runs first, test afterwards
System.out.println("do-while ran with n=" + n);
n++;
} while (n < 5); // prints once even though 10 < 5 is false
int sum = 0;
for (int k = 1; k <= 5; k++) { // initialisation; condition; update
if (k == 3) continue; // skip 3
if (k == 5) break; // stop before adding 5
sum += k; // 1 + 2 + 4
}
System.out.println("sum=" + sum); // sum=7A while loop may run zero times; a do-while loop always runs at least once. Every loop needs a variable that moves it towards termination; forgetting i++ creates an infinite loop.
Choosing a loop
| Situation | Best loop | Example |
|---|---|---|
| Number of iterations known in advance | for | Print 1 to 10; visit each index of an array |
| Repeat while a condition holds; may be zero times | while | Read lines until there are none; halve a number until it is 1 |
| Body must run at least once, then decide | do-while | Show a menu, then ask "again?" (see the calculator lab) |
| Read every element of an array or collection | enhanced for | Sum all marks |
Enhanced `for` loop and its limitations
The enhanced for-loop (for-each) visits every element of an array or Iterable:
int[] values = {1, 2, 3};
for (int v : values) {
v = v * 10; // changes only the loop variable
}
System.out.println(values[0] + " " + values[1] + " " + values[2]); // 1 2 3Limitations: it gives no index; assigning to the loop variable does not change the array; it always moves forward one element at a time (no reverse traversal or skipping); it cannot walk two arrays in parallel; and removing elements from a collection during a for-each throws ConcurrentModificationException (topic 8). If elements are object references, calling a mutating method on the element does change the object, but assigning a new object to the loop variable does not replace the entry. Use an index loop when you need positions or replacement.
`break`, `continue`, labels and `return`
break leaves the nearest loop or switch; continue skips to the next iteration of the nearest loop; return leaves the whole method. A label lets break or continue target an outer loop:
int[][] matrix = { {4, 7}, {9, 2} };
int target = 9;
int foundRow = -1, foundCol = -1;
search:
for (int row = 0; row < matrix.length; row++) {
for (int col = 0; col < matrix[row].length; col++) {
if (matrix[row][col] == target) {
foundRow = row;
foundCol = col;
break search; // leaves both loops
}
}
}
System.out.println("found at " + foundRow + "," + foundCol); // found at 1,0continue outer; abandons the rest of the inner loop and starts the next iteration of the outer loop:
outer:
for (int row = 1; row <= 3; row++) {
for (int col = 1; col <= 3; col++) {
if (col == 2) continue outer;
if (row == 3) break outer;
System.out.print(row + "" + col + " ");
}
}
// prints: 11 21Labels are occasionally useful, but extracting the search into a method that returns the result is often clearer.
Lab: four-operation calculator
The session lab asks for a calculator supporting +, -, *, / using operators and control statements, tested with several inputs, and handling division by zero with conditional logic. The design below separates the calculation (a switch inside a method) from input handling (a do-while menu loop with Scanner).
import java.util.Scanner;
public class Calculator {
static String calculate(double left, char op, double right) {
switch (op) {
case '+':
return "Result: " + (left + right);
case '-':
return "Result: " + (left - right);
case '*':
return "Result: " + (left * right);
case '/':
if (right == 0) {
return "Error: division by zero";
}
return "Result: " + (left / right);
default:
return "Error: unknown operator " + op;
}
}
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
String again;
do {
System.out.print("First number: ");
double left = in.nextDouble();
System.out.print("Operator (+ - * /): ");
char op = in.next().charAt(0);
System.out.print("Second number: ");
double right = in.nextDouble();
System.out.println(calculate(left, op, right));
System.out.print("Again? (y/n): ");
again = in.next();
} while (again.equalsIgnoreCase("y"));
System.out.println("Bye");
}
}Test run with the inputs 10 / 0, 7 * 6, 9 % 2 (what the user types is shown after each prompt):
First number: 10
Operator (+ - * /): /
Second number: 0
Error: division by zero
Again? (y/n): y
First number: 7
Operator (+ - * /): *
Second number: 6
Result: 42.0
Again? (y/n): y
First number: 9
Operator (+ - * /): %
Second number: 2
Error: unknown operator %
Again? (y/n): n
ByePoints to notice:
- Because the operands are
double,10 / 0without the check would printResult: Infinityrather than crash. Withintoperands, the same division would throwArithmeticException. Either way the explicitif (right == 0)check gives the user a clear message. returninside eachcaseends the method, so nobreakis needed there. WithSystem.out.printlninstead ofreturn, every case would needbreak.do-whilesuits the menu: the user always gets at least one calculation.- Extensions: rewrite
calculatewith an arrow-style switch expression (Java 14+), or use anif-elseladder; reject non-numeric input (in.hasNextDouble()).
Common misconceptions
| Misconception | Correct idea |
|---|---|
| "Java bytecode runs without a JVM." | Bytecode is portable, but each platform needs its own JVM. |
"double d = 7 / 2; gives 3.5." | The division is done in int first, giving 3, then widened to 3.0. |
| "Overflow throws an exception." | int/long arithmetic wraps silently; only Math.*Exact methods throw. |
"(int) 3.99 rounds to 4." | Casting to an integer type truncates toward zero: 3 (and (int) -3.99 is -3). |
| "Local variables default to 0." | Only fields and array elements get defaults; locals must be assigned. |
"== compares string contents." | == compares references; use equals. |
"A classic switch stops after the matching case." | It falls through until break; arrow cases do not fall through. |
"b = b + 1 and b += 1 are identical for a byte." | The first is a compile error; the second inserts a cast. |
| "Changing the for-each variable updates the array." | It only changes a local copy of the element. |
Key points
javaccompiles.javato.classbytecode;java ClassNameruns it on a JVM. JDK = JRE + tools; JRE = JVM + libraries.- The entry point is
public static void main(String[] args); a public class lives in a file with the same name. - Eight primitives with fixed sizes;
charis unsigned 16-bit;booleanis not numeric. Fields get defaults, locals do not. - Widening is automatic; narrowing needs a cast and can lose data (
(byte) 130is -126). Integer overflow wraps. - Integer division truncates;
%takes the sign of the left operand;int / 0throws,double / 0gives Infinity or NaN. x++yields the old value,++xthe new value.&&and||short-circuit.+with aStringoperand concatenates, left to right.- Classic
switchfalls through withoutbreak; arrowswitchand switch expressions (Java 14+) do not, and expressions useyieldin blocks. whilemay run zero times,do-whileat least once; for-each cannot modify the array or give an index; labelledbreak/continuetarget outer loops.