CDCDAC Prep
PGCP-BDACMCE (CCE Modules Exam)•~30 min read

Java Programming Study Notes

Topic 1 (Free Preview)

Java Platform, JVM, JDK, Types, Operators and Control Flow

Topic 1 notes are 100% free. Topics 2–15 are available on the CDACPrep app.

Open in App

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:

FeatureWhat it means in practice
Simple and familiarC-like syntax without pointer arithmetic, header files or manual free
Object-orientedCode is organised into classes and objects (see topic 3)
Platform independent / architecture-neutralThe same .class file runs on any OS that has a compatible JVM
RobustStrong static typing, mandatory initialisation of locals, array bounds checks, exception handling and garbage collection
SecureNo raw pointers; the JVM verifies bytecode before running it
MultithreadedThreads are built into the language and library
High performanceThe JVM's just-in-time (JIT) compiler turns frequently used bytecode into native machine code
Dynamic and distributedClasses 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

text
Hello.java  --javac-->  Hello.class (bytecode)  --java-->  JVM loads, verifies and executes it
  1. You write source code in a .java file.
  2. The compiler javac checks syntax and types and writes one .class file per class. A class file contains bytecode plus metadata (fields, methods, constant pool).
  3. The launcher java starts a JVM, which loads the class, verifies that the bytecode is safe, links it, initialises static state, and executes main.
  4. 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

java
/**
 * 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);
    }
}
bash
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 file

Output of java Hello and java Hello a b:

text
Hello, Java
Arguments received: 0
Hello, Java
Arguments received: 2

Rules to remember:

  • A public top-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... args is also accepted, and the parameter name can change. If static is missing, Java 17 prints Error: Main method is not static in class ....
  • java takes 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

TermContainsWho needs it
JVMThe engine that loads and runs bytecodePart of every runtime
JRE (Java Runtime Environment)JVM + standard class librariesSomeone 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

ToolPurposeExample
javacJava compiler: .java → .classjavac Hello.java
javaLauncher: starts the JVM and runs a classjava Hello
jdbCommand-line debugger: breakpoints, stepping, inspecting variablesjavac -g Hello.java then jdb Hello, stop in Hello.main, run, locals, step, cont
javadocGenerates HTML API documentation from /** ... */ commentsjavadoc -d docs Hello.java
jarPackages class files into a .jar archive (topic 2)jar --create --file app.jar ...
javapDisassembles a class file to show its members or bytecodejavap Hello
jshellInteractive shell (Java 9+) for trying expressionsjshell

javap Hello on the class above prints its compiled members, including the default constructor the compiler added:

text
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.

java
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.

KindDeclaredScopeLifetimeDefault value?
Local variableInside a method or blockFrom declaration to end of the enclosing blockWhile the block executesNo — must be assigned before use
ParameterIn a method/constructor headerWhole method bodyDuring the callReceives the argument value
Instance fieldIn a class, without staticWhole class (through an object)As long as the object existsYes
Static fieldIn a class, with staticWhole classWhile the class is loadedYes
java
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.

TypeSizeRange / valuesDefault (fields, array elements)Literal example
byte8-bit signed-128 to 1270(byte) 10
short16-bit signed-32,768 to 32,7670(short) 300
int32-bit signed-2,147,483,648 to 2,147,483,647042
long64-bit signedabout ±9.22 × 10^180L8_100_000_000L
float32-bit IEEE 754about 7 decimal digits0.0f0.75F
double64-bit IEEE 754about 15–16 decimal digits0.01.8e1
char16-bit unsigned UTF-16 code unit0 to 65,535'\u0000''A', '\n'
booleantrue/falsetrue, falsefalsetrue

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

java
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 int unless suffixed with L (prefer uppercase; lowercase l looks like 1).
  • 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:

java
int result;
if (args.length > 0) {
    result = 10;
}
// System.out.println(result); // error: variable result might not have been initialized

Do 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:

java
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.

java
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");
        }
    }
}
text
a=10 b=20
p.x=99 p.y=4
NullPointerException

Changing 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:

java
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 kept

130 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:

java
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 overflow

Assigning 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:

java
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 -128

Compound assignment is convenient but can hide truncation: it does not guarantee the result fits.

Operators

CategoryOperatorsExample
Unary+ - ++ -- ! ~ and casts-x, !done, ++i
Arithmetic (binary)* / % + -a % b
Assignment=x = 5
Compound assignment+= -= *= /= %= (and bitwise forms)total += price
Relational< > <= >= and instanceofage >= 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:

java
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 zero

Integer 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.

java
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 7

Java 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:

java
int total = 10;
total += 5;   // 15
total *= 2;   // 30
total -= 4;   // 26
total /= 3;   // 8  (integer division)
total %= 4;   // 0

Relational 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:

java
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 nothing

Use 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.

java
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:

java
if (text != null && !text.isEmpty()) {
    System.out.println(text);
}

Conditional (ternary) operator

condition ? valueIfTrue : valueIfFalse is an expression that yields one of two values:

java
int score = 55;
String category = score >= 40 ? "pass" : "fail"; // pass
int bigger = (8 > 3) ? 8 : 3;                     // 8

Its 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:

java
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 text

Precedence and associativity

Precedence decides how an expression is grouped; operands are still evaluated left to right. From highest to lowest:

LevelOperatorsAssociativity
Postfixx++ x--left
Unary++x --x + - ! ~ (type)right
Multiplicative* / %left
Additive+ -left
Shift<< >> >>>left
Relational< > <= >= instanceofleft
Equality== !=left
Bitwise AND / XOR / OR& then ^ then `left
Logical AND&&left
Logical ORleft
Conditional?:right
Assignment= += -= *= /= %= ...right
java
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) * 2

When 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:

java
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) -> C

Order 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:

java
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.

java
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:

java
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:

java
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`

java
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=7

A 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

SituationBest loopExample
Number of iterations known in advanceforPrint 1 to 10; visit each index of an array
Repeat while a condition holds; may be zero timeswhileRead lines until there are none; halve a number until it is 1
Body must run at least once, then decidedo-whileShow a menu, then ask "again?" (see the calculator lab)
Read every element of an array or collectionenhanced forSum all marks

Enhanced `for` loop and its limitations

The enhanced for-loop (for-each) visits every element of an array or Iterable:

java
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 3

Limitations: 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:

java
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,0

continue outer; abandons the rest of the inner loop and starts the next iteration of the outer loop:

java
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 21

Labels 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).

java
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):

text
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
Bye

Points to notice:

  • Because the operands are double, 10 / 0 without the check would print Result: Infinity rather than crash. With int operands, the same division would throw ArithmeticException. Either way the explicit if (right == 0) check gives the user a clear message.
  • return inside each case ends the method, so no break is needed there. With System.out.println instead of return, every case would need break.
  • do-while suits the menu: the user always gets at least one calculation.
  • Extensions: rewrite calculate with an arrow-style switch expression (Java 14+), or use an if-else ladder; reject non-numeric input (in.hasNextDouble()).

Common misconceptions

MisconceptionCorrect 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

  • javac compiles .java to .class bytecode; java ClassName runs 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; char is unsigned 16-bit; boolean is not numeric. Fields get defaults, locals do not.
  • Widening is automatic; narrowing needs a cast and can lose data ((byte) 130 is -126). Integer overflow wraps.
  • Integer division truncates; % takes the sign of the left operand; int / 0 throws, double / 0 gives Infinity or NaN.
  • x++ yields the old value, ++x the new value. && and || short-circuit.
  • + with a String operand concatenates, left to right.
  • Classic switch falls through without break; arrow switch and switch expressions (Java 14+) do not, and expressions use yield in blocks.
  • while may run zero times, do-while at least once; for-each cannot modify the array or give an index; labelled break/continue target outer loops.
Ready to test what you learned?

Practice 20 MCQs with Instant Explanations

Solve exam-level questions for Java Programming with scoring, option checking, and detailed answers.

Go to Practice MCQs