0% found this document useful (0 votes)
0 views50 pages

Complete Java Notes Detailed Edition

The document is a comprehensive guide for beginners learning Java, covering topics such as the language's history, key features, and the setup of the development environment. It includes detailed explanations of Java programming concepts, including object-oriented programming, data types, and memory management. Additionally, it provides practical examples and common beginner mistakes to aid understanding and application of Java programming.

Uploaded by

sgsoni37
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
0 views50 pages

Complete Java Notes Detailed Edition

The document is a comprehensive guide for beginners learning Java, covering topics such as the language's history, key features, and the setup of the development environment. It includes detailed explanations of Java programming concepts, including object-oriented programming, data types, and memory management. Additionally, it provides practical examples and common beginner mistakes to aid understanding and application of Java programming.

Uploaded by

sgsoni37
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Complete Java Notes for Beginners ���

Detailed Edition

Complete Java Notes for Beginners (Detailed Edition)

Complete Java Notes for Beginners (Detailed


Edition)

Table of Contents

1. Introduction to Java
2. Setting Up the Environment
3. Anatomy of a Java Program
4. Variables, Data Types, and Memory
5. Operators (Complete Reference)
6. Taking Input from the User
7. Control Flow Statements
8. Loops
9. Arrays
10. Strings
11. Methods (Functions) In Depth
12. Object-Oriented Programming — Classes and Objects
13. Constructors
14. Static vs Instance Members
15. Inheritance
16. Polymorphism
17. Encapsulation
18. Abstraction (Abstract Classes and Interfaces)
19. Packages and Access Modifiers
20. Exception Handling
21. Generics
22. Collections Framework
23. File Handling (I/O)
24. Multithreading
25. Enums
26. Wrapper Classes and Utility Classes
27. Common Beginner Mistakes (Explained)
28. Practice Programs with Explanations
29. Keywords Cheat Sheet
1. Introduction to Java

1.1 What Is Java?

Java is a general-purpose, high-level, class-based, object-oriented programming language. It was


designed by James Gosling and his team at Sun Microsystems, and released publicly in 1995. Sun
Microsystems was later acquired by Oracle Corporation, which now maintains the language.

Java was originally created for embedded consumer electronics, but it found its real success on the web
and later in enterprise, mobile (Android), and server-side backend systems. Today Java powers
everything from Android apps to banking systems to large-scale distributed backends (Netflix, LinkedIn,
Amazon all use Java extensively).

1.2 Why Java Was Revolutionary: “Write Once, Run Anywhere” (WORA)

Before Java, most compiled languages (like C or C++) compiled source code directly into machine code
specific to one operating system and processor architecture. A program compiled for Windows would not
run on Linux or macOS without recompilation.

Java solved this with a two-step execution model:

1. The Java compiler ( javac ) does not produce native machine code. Instead, it produces an
intermediate form called bytecode, stored in .class files.
2. This bytecode is not tied to any specific operating system or CPU. It is executed by a program
called the Java Virtual Machine (JVM).
3. Every operating system has its own version of the JVM, but the bytecode itself is identical
everywhere.

This means you compile your code once, and the resulting .class file can run on any machine that
has a JVM installed — Windows, Linux, macOS, or embedded devices — without changes. This is the
meaning of “Write Once, Run Anywhere.”

[ [Link] ]
|
| javac (compiler)
v
[ [Link] ] <-- bytecode (platform independent)
|
| distributed to any machine
v
[ JVM on Windows ] [ JVM on Linux ] [ JVM on macOS ]
| | |
runs runs runs

1.3 Key Features of Java Explained

Feature What It Actually Means

Bytecode runs unchanged on any device with a


Platform Independent
compatible JVM.

Nearly everything in Java is modeled as an object with


Object-Oriented state (fields) and behavior (methods), which promotes
modular, reusable, maintainable code.

Java removed complexities found in C++ such as explicit


pointer arithmetic, multiple inheritance of classes, and
Simple
manual memory management (operator overloading is
also absent).

No raw pointers means programs cannot directly access


Secure arbitrary memory addresses. The JVM also runs
bytecode inside a controlled sandbox and verifies it
before execution.

Strong compile-time type checking, automatic garbage


Robust collection (no manual free() ), and a mandatory
exception-handling model reduce many classes of bugs.

Built-in language and library support (the Thread class,


synchronized keyword, [Link]
Multithreaded
package) makes writing concurrent programs more
approachable.

The bytecode format has no dependency on any specific


Architecture Neutral
hardware architecture’s data sizes or instruction set.

While bytecode interpretation is inherently slower than


native machine code, the JVM uses a Just-In-Time (JIT)
High Performance compiler which converts frequently executed bytecode
into native machine code at runtime, closing much of the
performance gap.

Java has extensive networking libraries ( [Link] ), and


Distributed technologies like RMI made building distributed
applications easier.

Java programs carry a fair amount of runtime type


Dynamic information, which supports reflection — inspecting and
manipulating classes, methods, and fields at runtime.

1.4 JDK vs JRE vs JVM — The Full Picture

These three acronyms confuse almost every beginner, so let’s break them down layer by layer, from the
inside out.

JVM (Java Virtual Machine) This is the engine that actually executes the bytecode. It is an abstract
computing machine — it does not exist as a single physical thing, but as a specification implemented
differently on each OS (HotSpot JVM, OpenJ9, etc.). The JVM’s jobs include: - Loading .class files
(Class Loader subsystem) - Verifying bytecode for safety (Bytecode Verifier) - Executing bytecode
(Execution Engine — includes an interpreter and the JIT compiler) - Managing memory automatically
(Garbage Collector)

JRE (Java Runtime Environment) This is the JVM plus the standard class libraries (like [Link] ,
[Link] , [Link] ) and supporting files needed to actually run a compiled Java program. If you only
want to run Java applications (not develop them), the JRE is sufficient.

JDK (Java Development Kit) This is the JRE plus development tools: the compiler ( javac ), the
debugger ( jdb ), the archiver ( jar ), documentation generator ( javadoc ), and more. If you want to
write and compile Java code, you need the JDK. (In modern Java distributions, JRE is no longer
distributed separately — installing the JDK gives you everything.)

JDK
|-- Development Tools (javac, javadoc, jar, jdb...)
|-- JRE
|-- Class Libraries ([Link], [Link], [Link]...)
|-- JVM
|-- Class Loader
|-- Bytecode Verifier
|-- Execution Engine (Interpreter + JIT Compiler)
|-- Garbage Collector

1.5 Compilation and Execution Flow, Step by Step


1. You write source code in a file named, for example, [Link] .
2. You run javac [Link] . The compiler checks your code for syntax errors and type errors. If
everything is correct, it generates [Link] — the bytecode.
3. You run java HelloWorld . This launches a new JVM instance, which:
Loads [Link] using the class loader
Verifies the bytecode is safe and well-formed
Locates the main method as the entry point
Begins interpreting/JIT-compiling and executing instructions
4. When main finishes (or [Link]() is called), the JVM shuts down.

2. Setting Up the Environment

2.1 Installing the JDK

1. Download a JDK distribution. Popular free options include Oracle OpenJDK, Eclipse Temurin
(AdoptOpenJDK), or Amazon Corretto.
2. Run the installer for your OS (Windows .msi , macOS .pkg , or Linux package manager, e.g. sudo apt
install openjdk-21-jdk ).
3. Configure environment variables (usually automatic on modern installers):
JAVA_HOME should point to the JDK installation directory.
The bin folder inside that directory should be added to your system PATH so that java and
javac commands are available from any terminal location.

2.2 Verifying the Installation

java -version
javac -version

If both commands print a version number, your setup is complete. A mismatch (e.g., java shows one
version and javac shows a very different one) usually indicates multiple JDKs are installed and your
PATH is picking up the wrong one.

2.3 Choosing a Development Tool

Plain text editor + terminal: good for absolute beginners to understand the compile/run cycle
without abstraction (Notepad++, VS Code with no plugins).
VS Code with the “Extension Pack for Java”: lightweight IDE-like experience, free.
IntelliJ IDEA (Community Edition): the most popular full-featured Java IDE, free and very
beginner-friendly with excellent error highlighting and refactoring tools.
Eclipse: another long-standing free, full-featured IDE, widely used in academic settings.

2.4 Compiling and Running from the Command Line

javac [Link] # produces [Link] in the same folder


java HelloWorld # note: no ".class" extension when running

If your class belongs to a package (see Section 19), you must compile and run it while respecting the
folder structure that mirrors the package name, and run it using the fully qualified name, e.g. java
[Link] .

3. Anatomy of a Java Program


package [Link]; // (optional) package declaration

import [Link]; // (optional) import statements

public class HelloWorld { // class declaration

public static void main(String[] args) { // program entry point


[Link]("Hello, World!");
}
}

3.1 Line-by-Line Breakdown

package [Link]; Packages are Java’s namespacing mechanism — a way to organize related
classes into folders/groups and avoid naming collisions between classes from different libraries. This
line, if present, must be the very first non-comment line in the file.

import [Link]; Tells the compiler where to find a class you’re referencing by its short name
( Scanner ) instead of writing its fully qualified name ( [Link] ) every time. You do not need to
import classes from [Link] (like String , System , Math ) — these are imported automatically into
every Java file.

public class HelloWorld { Declares a class named HelloWorld . Rules: - A .java source file can contain
at most one public top-level class. - If a public class exists, the file name must exactly match the
class name, including capitalization ( [Link] ). - A file can contain multiple non-public classes
in addition to one public one.

public static void main(String[] args) { This is the method the JVM looks for and calls automatically
when your program starts. It must have exactly this signature: - public — the JVM (which is external to
your class) must be able to call it, so it cannot be private or protected . - static — the JVM calls main
without first creating an object of your class, so the method must belong to the class itself, not to an
instance. - void — main does not return any value back to the JVM. - main — this exact name is what
the JVM looks for. - (String[] args) — an array of command-line arguments passed to the program is
delivered here. args can be any name, but the type must be String[] (or the equivalent varargs form
String... args ).

[Link]("Hello, World!"); - System is a built-in class in [Link] . - out is a static field of


System , an object of type PrintStream representing “standard output” (usually the console). -
println(...) is a method on that PrintStream object that prints its argument followed by a line break.
There’s also print() (no line break) and printf() (formatted output, similar to C’s printf).

3.2 Comments

// single-line comment — everything after // on this line is ignored

/* multi-line
comment — everything between /* and */ is ignored */

/**
* Documentation comment (Javadoc).
* Used to auto-generate HTML documentation with the `javadoc` tool.
* @param name the name to greet
*/

Comments are not executed; they exist purely to help human readers (including your future self)
understand the code’s intent.

4. Variables, Data Types, and Memory


4.1 What Is a Variable, Really?

A variable is a named piece of memory that holds a value of a particular type. In Java, every variable
must have a declared type, and that type never changes for the lifetime of the variable (Java is a
statically typed language — type checking happens at compile time, not at runtime like in Python or
JavaScript).

int age = 25; // 'age' is a container that can only ever hold an int

4.2 Stack vs Heap — Where Do Variables Actually Live?

Understanding memory layout demystifies a lot of confusing Java behavior later on (especially around
object references).

The Stack: Stores local variables (method parameters and variables declared inside a method) and
the primitive values assigned directly to them. Each method call gets its own “stack frame,” which is
destroyed automatically when the method returns. This is very fast memory.
The Heap: Stores all objects (anything created with new , including arrays and instances of classes)
. Objects live on the heap until no references to them remain, at which point the Garbage Collector
reclaims that memory automatically — you never manually free memory in Java.

void example() {
int x = 10; // x (value 10) lives on the stack
Car myCar = new Car(); // 'myCar' (a reference/pointer) lives on the stack
// the actual Car OBJECT lives on the heap
}

When myCar is assigned to another variable, only the reference (memory address) is copied — both
variables end up pointing to the same object on the heap.

Car c1 = new Car();


Car c2 = c1; // c2 points to the SAME object as c1
[Link] = 100;
[Link]([Link]); // 100 - because c1 and c2 are the same object!

4.3 Primitive Data Types (Complete Table)

Java has exactly 8 primitive types. Unlike objects, primitives store their actual value directly (not a
reference), and they are not part of the object hierarchy (they have no methods of their own).

Type Size Default Value Range Example

byte 1 byte (8 bits) 0 -128 to 127 byte b = 100;

short 2 bytes (16 bits) 0 -32,768 to 32,767 short s = 20000;

≈ -2.1 billion to 2.1


int 4 bytes (32 bits) 0 int i = 100000;
billion

≈ -9.2 quintillion to
long 8 bytes (64 bits) 0L long l = 100000L;
9.2 quintillion

~7 decimal digits of
float 4 bytes 0.0f float f = 3.14f;
precision

~15 decimal digits double d =


double 8 bytes 0.0d
of precision 3.14159;

single 16-bit
char 2 bytes '\u0000' Unicode character char c = 'A';
(0 to 65,535)
JVM-dependent true or false boolean flag =
boolean false
(conceptually 1 bit) only true;

Notes: - long literals need an L suffix ( 100000L ) so the compiler knows to treat the literal as 64-bit, not
32-bit. - float literals need an f suffix ( 3.14f ); without it, a decimal literal like 3.14 is treated as a
double by default. - char is actually a numeric type under the hood — it stores a Unicode code point, so
you can do arithmetic on characters: char nextLetter = (char)('a' + 1); // 'b' .

4.4 Reference Types

Anything that is not a primitive is a reference type: String , arrays, and every object created from a
class (including your own custom classes). A reference type variable holds the memory address of the
object, not the object’s data directly. The default value of an uninitialized reference variable is null
(meaning “points to nothing”).

String name; // default value: null


name = "Alice"; // now points to a String object on the heap

4.5 Type Casting

Widening (implicit/automatic) conversion — happens automatically because no data can be lost:

int i = 100;
long l = i; // int -> long, automatic
double d = l; // long -> double, automatic

Widening order: byte -> short -> int -> long -> float -> double (char also widens to int and beyond).

Narrowing (explicit) conversion — you must manually cast, because data loss or unexpected results
are possible:

double d = 9.78;
int i = (int) d; // 9 - the fractional part is simply truncated (not rounded!)

int big = 130;


byte b = (byte) big; // overflow! byte range is -128 to 127, result wraps around

4.6 Variable Scope: Local, Instance, and Static

public class Example {


static int staticVar = 1; // static (class) variable - shared by ALL objects
int instanceVar = 2; // instance variable - each object gets its own copy

void method() {
int localVar = 3; // local variable - exists only during this method call
}
}

Local variables must be initialized before use (the compiler enforces this) and only exist within the
block { } where they are declared.
Instance variables belong to a specific object; every object has its own copy. They get a default
value automatically (0, false, or null) even if not explicitly initialized.
Static variables belong to the class itself, not any one object — there is only ever one copy, shared
across every instance (see Section 14 for a deep dive).

4.7 Constants with final


final double PI = 3.14159;
PI = 3.0; // COMPILE ERROR - cannot reassign a final variable

Convention: constant names are usually written in UPPER_SNAKE_CASE .

4.8 Naming Rules and Conventions

Identifiers may contain letters, digits, $ , and _ , but cannot start with a digit.
Cannot be a reserved keyword ( class , if , int , new , etc.).
Java is case-sensitive: age and Age are different identifiers.
Convention (not enforced by the compiler, but expected by every Java developer):
camelCase for variables and methods: studentAge , calculateTotal()
PascalCase for classes and interfaces: StudentRecord , Runnable
UPPER_SNAKE_CASE for constants: MAX_SIZE
all-lowercase for packages: [Link]

5. Operators (Complete Reference)

5.1 Arithmetic Operators

int a = 10, b = 3;
[Link](a + b); // 13 addition
[Link](a - b); // 7 subtraction
[Link](a * b); // 30 multiplication
[Link](a / b); // 3 integer division - fractional part discarded!
[Link](a % b); // 1 modulus (remainder)

double x = 10, y = 3;
[Link](x / y); // 3.3333... - division is only integer division when BOTH operands are integer types

Important trap for beginners: int / int always produces an int result (truncated), even if you
assign it to a double variable:

double result = 10 / 3; // result is 3.0, NOT 3.333! The division happens as int/int first.
double correct = 10.0 / 3; // 3.333... - at least one operand must be a floating type

5.2 Relational (Comparison) Operators

All return a boolean result.

a == b // equal to
a != b // not equal to
a > b // greater than
a < b // less than
a >= b // greater than or equal to
a <= b // less than or equal to

Important trap: for reference types like String , == compares memory addresses (whether two
variables point to the same object), not content. Use .equals() to compare content — see Section 10.

5.3 Logical Operators

boolean p = true, q = false;


p && q // AND - true only if both are true
p || q // OR - true if at least one is true
!p // NOT - inverts the value
Java’s && and || are short-circuit operators: in p && q , if p is already false , q is never evaluated
(because the result is guaranteed false regardless). This matters when the second operand has side
effects or could throw an error:

// Safe: if arr is null, the length check on the right is never evaluated
if (arr != null && [Link] > 0) { ... }

Java also has non-short-circuiting logical operators & and | , which always evaluate both sides — rarely
used for boolean logic but common for bitwise operations (see below).

5.4 Assignment Operators

int x = 10;
x += 5; // same as x = x + 5; -> 15
x -= 3; // x = x - 3; -> 12
x *= 2; // x = x * 2; -> 24
x /= 4; // x = x / 4; -> 6
x %= 4; // x = x % 4; -> 2

5.5 Increment / Decrement Operators

int x = 5;
int y = x++; // POST-increment: y gets 5 (old value), THEN x becomes 6
int z = ++x; // PRE-increment: x becomes 7 FIRST, then z gets 7

This distinction commonly trips up beginners inside loop conditions or array indexing — always be clear
on whether the increment happens before or after the value is used.

5.6 Bitwise Operators

Operate directly on the binary representation of integer types.

int a = 5; // 0101
int b = 3; // 0011

a & b; // 0001 = 1 (AND - bit set only if both bits are 1)


a | b; // 0111 = 7 (OR - bit set if either bit is 1)
a ^ b; // 0110 = 6 (XOR - bit set if bits differ)
~a; // inverts all bits (bitwise NOT)
a << 1; // 1010 = 10 (left shift - multiplies by 2 per shift)
a >> 1; // 0010 = 2 (right shift - divides by 2 per shift, sign-preserving)
a >>> 1; // unsigned right shift - fills with 0 regardless of sign

5.7 Ternary (Conditional) Operator

A compact one-line if-else that produces a value.

int a = 10, b = 20;


int max = (a > b) ? a : b; // if a > b, max = a; else max = b

5.8 instanceof Operator

Checks whether an object is an instance of a particular class or interface.

Object obj = "Hello";


if (obj instanceof String) {
[Link]("It's a String");
}
5.9 Operator Precedence (Simplified, High to Low)

1. Postfix ( x++ , x-- )


2. Unary ( ++x , --x , ! , ~ )
3. Multiplicative ( * , / , % )
4. Additive ( + , - )
5. Shift ( << , >> , >>> )
6. Relational ( < , > , <= , >= , instanceof )
7. Equality ( == , != )
8. Bitwise AND / XOR / OR ( & , ^ , | )
9. Logical AND / OR ( && , || )
10. Ternary ( ?: )
11. Assignment ( = , += , etc.)

When in doubt, use parentheses () to make evaluation order explicit — it costs nothing and prevents
subtle bugs.

6. Taking Input from the User

6.1 Using Scanner (most common for beginners)

import [Link];

public class InputExample {


public static void main(String[] args) {
Scanner sc = new Scanner([Link]);

[Link]("Enter your name: ");


String name = [Link](); // reads an entire line of text

[Link]("Enter your age: ");


int age = [Link](); // reads a single int token

[Link]("Enter your GPA: ");


double gpa = [Link]();

[Link](name + " is " + age + " years old with GPA " + gpa);

[Link](); // always close the Scanner when finished with [Link]


}
}

6.2 The Classic nextInt() + nextLine() Trap

nextInt() , nextDouble() , etc. only consume the token itself, leaving the trailing newline character
in the input buffer. If you call nextLine() right after, it immediately reads that leftover empty newline
instead of waiting for new input, which looks like your program “skipped” a line.

Scanner sc = new Scanner([Link]);


[Link]("Age: ");
int age = [Link]();
[Link]("Name: ");
String name = [Link](); // BUG: this reads an empty string, not your typed name!

// FIX: consume the leftover newline first


[Link](); // discard leftover newline
String name2 = [Link](); // now this works correctly

6.3 Common Scanner Methods


Method Reads

nextInt() an int

nextDouble() a double

nextLong() a long

nextBoolean() a boolean ( true / false )

next() a single whitespace-delimited token ( String )

nextLine() an entire line, including spaces

hasNext() / hasNextInt() etc. check whether more input is available (useful in loops)

6.4 Reading via BufferedReader (faster, more manual)

import [Link];
import [Link];
import [Link];

BufferedReader br = new BufferedReader(new InputStreamReader([Link]));


String line = [Link](); // reads a line as String
int number = [Link]([Link]()); // manual parsing needed

BufferedReader is generally faster for reading large amounts of input (common in competitive
programming) but requires you to manually parse strings into numbers using methods like
[Link]() .

7. Control Flow Statements

7.1 if / else if / else

The most fundamental decision-making structure. Conditions are evaluated top to bottom; the first true
branch executes and the rest are skipped.

int marks = 75;

if (marks >= 90) {


[Link]("Grade A");
} else if (marks >= 75) {
[Link]("Grade B");
} else if (marks >= 50) {
[Link]("Grade C");
} else {
[Link]("Fail");
}

Java requires the condition inside if (...) to be a boolean expression — unlike C, you cannot write if
(1) expecting it to mean “true.”

7.2 Nested if Statements


int age = 25;
boolean hasLicense = true;

if (age >= 18) {


if (hasLicense) {
[Link]("Can drive");
} else {
[Link]("Needs a license");
}
} else {
[Link]("Too young to drive");
}

7.3 switch Statement

An alternative to long if-else if chains when comparing one variable against many discrete values.

int day = 3;
switch (day) {
case 1:
[Link]("Monday");
break;
case 2:
[Link]("Tuesday");
break;
case 3:
[Link]("Wednesday");
break;
default:
[Link]("Invalid day");
}

Fall-through behavior: without break , execution continues into the next case rather than exiting the
switch. This is sometimes used intentionally to group cases:

switch (day) {
case 6:
case 7:
[Link]("Weekend");
break;
default:
[Link]("Weekday");
}

7.4 Modern switch Expressions (Java 14+)

The newer arrow syntax removes fall-through entirely and can directly produce a value.

String dayType = switch (day) {


case 6, 7 -> "Weekend";
case 1, 2, 3, 4, 5 -> "Weekday";
default -> "Invalid";
};

8. Loops

Loops repeat a block of code while a condition holds.

8.1 for Loop

Best when you know the number of iterations in advance.


for (int i = 1; i <= 5; i++) {
[Link](i);
}

Anatomy: for (initialization; condition; update) . 1. initialization runs once, before the loop starts.
2. condition is checked before every iteration; if false , the loop ends. 3. The loop body runs. 4. update
runs after every iteration. 5. Go back to step 2.

8.2 while Loop

Best when the number of iterations is not known ahead of time, and depends on some condition
evaluated during the loop.

int i = 1;
while (i <= 5) {
[Link](i);
i++;
}

The condition is checked before each iteration — if it’s false at the very start, the loop body never runs
at all.

8.3 do-while Loop

Same as while , except the condition is checked after the loop body — guaranteeing the body executes
at least once, which is useful for things like input-validation menus.

int i = 1;
do {
[Link](i);
i++;
} while (i <= 5);

8.4 Enhanced for-each Loop

Used to iterate over every element of an array or collection without manually managing an index
variable.

int[] numbers = {1, 2, 3, 4, 5};


for (int n : numbers) {
[Link](n);
}

Limitation: you cannot get the current index, and you cannot modify the original array through n (it’s a
copy of each element for primitives).

8.5 Nested Loops

for (int i = 1; i <= 3; i++) {


for (int j = 1; j <= 3; j++) {
[Link]("i=" + i + " j=" + j);
}
}
// outer loop runs 3 times; for EACH outer iteration, inner loop runs 3 times fully
// total inner-body executions = 3 * 3 = 9

8.6 break and continue


for (int i = 1; i <= 10; i++) {
if (i == 5) break; // immediately exits the entire loop
if (i % 2 == 0) continue; // skips the rest of THIS iteration, goes to next
[Link](i); // prints: 1, 3
}

8.7 Labeled break/continue (for nested loops)

By default, break / continue only affect the innermost loop. Labels let you target an outer loop.

outer:
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
if (j == 2) continue outer; // skips to next iteration of the OUTER loop
[Link](i + "," + j);
}
}

9. Arrays

9.1 What Is an Array?

An array is a fixed-size, ordered collection of elements of the same type, stored in a single contiguous
block of memory on the heap. Because array size is fixed at creation, you cannot add or remove elements
later — you’d need to create a new array (this is one of the main reasons the Collections framework’s
ArrayList , covered later, is often preferred).

9.2 Declaring and Initializing

// Method 1: array literal


int[] numbers = {10, 20, 30, 40, 50};

// Method 2: specify size, then fill in


int[] scores = new int[5]; // all elements default to 0
scores[0] = 95;
scores[1] = 88;

// Method 3: explicit new with values


String[] names = new String[]{"Alice", "Bob", "Charlie"};

Default values when using new type[size] : 0 for numeric types, false for boolean, '\u0000' for char,
null for reference types.

9.3 Accessing Elements

Arrays are zero-indexed: the first element is at index 0 , and the last element is at index length - 1 .

[Link](numbers[0]); // 10 (first element)


[Link]([Link]); // 5 (note: .length is a FIELD, no parentheses — unlike String's .length())
[Link](numbers[[Link] - 1]); // 50 (last element)

Accessing an index outside the valid range (e.g., numbers[5] when length is 5) throws an
ArrayIndexOutOfBoundsException at runtime — the compiler cannot catch this ahead of time.

9.4 Multidimensional Arrays

A 2D array is essentially “an array of arrays.”


int[][] matrix = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9}
};

[Link](matrix[1][2]); // 6 (row index 1, column index 2)

for (int row = 0; row < [Link]; row++) {


for (int col = 0; col < matrix[row].length; col++) {
[Link](matrix[row][col] + " ");
}
[Link]();
}

9.5 Arrays of Objects

Student[] students = new Student[3];


students[0] = new Student("Alice", 20);
students[1] = new Student("Bob", 22);
// students[2] is still null until assigned - accessing it throws NullPointerException

9.6 Useful Methods from [Link]

import [Link];

int[] arr = {5, 2, 8, 1, 9};

[Link](arr); // sorts in place, ascending: [1, 2, 5, 8, 9]


[Link]([Link](arr)); // pretty-print: "[1, 2, 5, 8, 9]"

int[] copy = [Link](arr, 3); // {1, 2, 5} - first 3 elements


[Link](arr, 0); // sets every element to 0
boolean eq = [Link](arr, copy); // compares contents element-by-element
int idx = [Link](arr, 5); // fast search - array MUST already be sorted

9.7 Common Array Algorithms (Building Blocks)

// Finding the maximum value


int[] nums = {3, 7, 2, 9, 4};
int max = nums[0];
for (int n : nums) {
if (n > max) max = n;
}

// Summing all elements


int sum = 0;
for (int n : nums) sum += n;

// Reversing an array in place


for (int i = 0; i < [Link] / 2; i++) {
int temp = nums[i];
nums[i] = nums[[Link] - 1 - i];
nums[[Link] - 1 - i] = temp;
}

10. Strings

10.1 Strings Are Objects, and They Are Immutable


Unlike primitives, String is a full class in [Link] . Crucially, String objects cannot be changed
after creation — every method that appears to “modify” a string (like .toUpperCase() or .concat() )
actually returns a brand-new String object, leaving the original untouched.

String s = "hello";
[Link](); // this does NOTHING to s - the return value is discarded!
[Link](s); // still prints "hello"

s = [Link](); // correct - reassign the variable to the new String


[Link](s); // now prints "HELLO"

10.2 The String Pool (Why == Is Dangerous for Strings)

To save memory, Java maintains a special memory region called the String pool (part of the heap).
String literals (written directly in code with quotes) are automatically placed in this pool, and identical
literals are reused rather than duplicated.

String a = "hello";
String b = "hello";
[Link](a == b); // true - both point to the SAME pooled object

String c = new String("hello"); // explicitly forces a NEW object outside the pool
[Link](a == c); // false - different objects in memory, even though content is equal!
[Link]([Link](c)); // true - .equals() compares CONTENT, not memory address

Rule of thumb: always use .equals() (or .equalsIgnoreCase() ) to compare String content —
never == .

10.3 Creating Strings

String s1 = "Hello"; // literal - placed in String pool


String s2 = new String("Hello"); // explicit object - NOT pooled
String s3 = s1 + " World"; // concatenation creates a new String

10.4 Common String Methods (with Explanations)

String s = " Hello, World! ";

[Link](); // 19 - total number of characters, including spaces


[Link](0); // ' ' - character at index 0
[Link](); // "Hello, World!" - removes LEADING/TRAILING whitespace only
[Link](); // like trim(), but Unicode-aware (preferred in modern Java)
[Link](); // " HELLO, WORLD! "
[Link](); // " hello, world! "
[Link]().substring(0, 5); // "Hello" - characters from index 0 up to (not including) index 5
[Link]().replace("Hello", "Hi"); // "Hi, World!" - replaces ALL occurrences
[Link]().contains("World"); // true
[Link]().indexOf("World"); // 7 - starting index of first occurrence, or -1 if not found
[Link]().split(","); // ["Hello", " World!"] - splits into an array by a regex/delimiter
[Link]().equals("Hello, World!"); // true - content comparison
[Link]().equalsIgnoreCase("hello, world!"); // true - ignores case
[Link](); // false - checks if length == 0
[Link](); // false - checks if empty OR only whitespace (Java 11+)

10.5 String Formatting


String name = "Alice";
int age = 25;

// Concatenation
String s1 = "Name: " + name + ", Age: " + age;

// [Link] (like printf, but returns a String instead of printing)


String s2 = [Link]("Name: %s, Age: %d", name, age);

// printf directly
[Link]("Name: %s, Age: %d%n", name, age);

Common format specifiers: %s (String), %d (integer), %f (floating point, e.g. %.2f for 2 decimal
places), %n (platform-independent newline).

10.6 StringBuilder — Mutable Strings

Because every String “modification” creates a brand-new object, building up a string piece by piece
inside a loop using += is inefficient — it creates a new object on every single iteration, wasting memory
and CPU time. StringBuilder solves this by using an internally resizable, mutable character array.

StringBuilder sb = new StringBuilder();


for (int i = 1; i <= 5; i++) {
[Link](i).append(", "); // methods can be CHAINED because append() returns 'this'
}
String result = [Link](); // convert back to an immutable String when done
[Link](result); // "1, 2, 3, 4, 5, "

Other useful StringBuilder methods:

[Link](0, "Numbers: "); // insert at a specific index


[Link](); // reverses the entire sequence in place
[Link](0); // removes a single character
[Link](0, 5); // removes a range
[Link](0, 5, "New"); // replaces a range with new text
[Link](); // current length

Rule of thumb: use String for values that don’t change or change rarely; use StringBuilder when
doing many concatenations, especially inside loops. ( StringBuffer is an older, thread-safe but slower
cousin of StringBuilder — prefer StringBuilder unless multiple threads modify the same buffer
concurrently.)

11. Methods (Functions) In Depth

11.1 Anatomy of a Method

public static int add(int a, int b) {


return a + b;
}

public — access modifier (who can call this method)


static — belongs to the class, not a specific object (see Section 14)
int — the return type (the type of value this method sends back)
add — the method name
(int a, int b) — the parameter list, with types
{ return a + b; } — the method body; return sends a value back and immediately exits the method
A method that doesn’t return anything uses void as the return type, and typically has no return
statement (or a bare return; used to exit early).

11.2 Calling a Method

public class Calculator {


static int add(int a, int b) {
return a + b;
}

public static void main(String[] args) {


int result = add(5, 3); // 'a' becomes 5, 'b' becomes 3
[Link](result); // 8
}
}

11.3 Parameter Passing: Java Is Always Pass-by-Value

This is one of the most misunderstood topics for beginners. Java always copies the value of an
argument into the method’s parameter. The behavior differs depending on whether that value is a
primitive or a reference:

For primitives: the actual value is copied. Changes to the parameter inside the method have zero effect
on the original variable.

static void increment(int x) {


x = x + 1;
}

int num = 5;
increment(num);
[Link](num); // still 5 - 'x' was a completely separate copy

For objects: the value copied is the reference (the memory address), not the object itself. This means
both the original variable and the parameter point to the same object — so changes made through the
reference (like modifying a field) are visible from outside. However, reassigning the parameter to point
somewhere else does not affect the original variable.

static void modify(Car c) {


[Link] = 200; // modifies the SHARED object - visible outside!
}

static void reassign(Car c) {


c = new Car(); // only changes the LOCAL copy of the reference - invisible outside
}

Car myCar = new Car();


[Link] = 100;
modify(myCar);
[Link]([Link]); // 200 - the shared object was changed

reassign(myCar);
[Link]([Link]); // still 200 - reassignment inside the method didn't propagate out

11.4 Method Overloading

Multiple methods can share the same name as long as their parameter lists differ (in number, type, or
order of parameters). The compiler picks the correct version to call based on the arguments provided.
This is resolved at compile time, so it’s called “compile-time polymorphism.”
static int add(int a, int b) { return a + b; }
static double add(double a, double b) { return a + b; }
static int add(int a, int b, int c) { return a + b + c; }

add(2, 3); // calls the (int, int) version -> 5


add(2.5, 3.5); // calls the (double, double) version -> 6.0
add(1, 2, 3); // calls the (int, int, int) version -> 6

Note: overloading based only on return type (keeping the same parameters) is not allowed — the
compiler wouldn’t be able to tell which one you meant just from the call.

11.5 Varargs (Variable-Length Argument Lists)

Lets you pass any number of arguments of the same type without manually creating an array.

static int sum(int... numbers) { // 'numbers' behaves like an int[] inside the method
int total = 0;
for (int n : numbers) total += n;
return total;
}

sum(); // 0
sum(5); // 5
sum(1, 2, 3, 4); // 10

11.6 Recursion

A method that calls itself to solve a smaller version of the same problem. Every recursive method needs
a base case (a condition that stops the recursion) to avoid infinite recursion, which eventually crashes
with a StackOverflowError .

static int factorial(int n) {


if (n <= 1) return 1; // base case
return n * factorial(n - 1); // recursive case - calls itself with a smaller input
}

Trace of factorial(4) :

factorial(4) = 4 * factorial(3)
factorial(3) = 3 * factorial(2)
factorial(2) = 2 * factorial(1)
factorial(1) = 1 <- base case reached
// unwinding: 2*1=2, 3*2=6, 4*6=24

12. Object-Oriented Programming — Classes and Objects

12.1 Class vs Object

A class is a blueprint or template — it defines what data (fields) and behavior (methods) objects of that
type will have, but a class by itself holds no actual data. An object (also called an “instance”) is a
concrete thing created from that blueprint, with its own actual values stored in memory.

Analogy: Car the class is like an architectural blueprint for a car. myCar and yourCar are objects —
actual cars built from that blueprint, each possibly painted a different color or with a different amount of
fuel.
public class Car {
// fields (a.k.a. instance variables) - describe the STATE of each object
String brand;
String color;
int speed;

// methods - describe the BEHAVIOR of each object


void accelerate(int amount) {
speed += amount;
}

void displayInfo() {
[Link](brand + " (" + color + ") going " + speed + " km/h");
}
}

public class Main {


public static void main(String[] args) {
Car myCar = new Car(); // creates a new object on the heap; 'myCar' holds its reference
[Link] = "Toyota";
[Link] = "Red";
[Link](60);
[Link](); // "Toyota (Red) going 60 km/h"

Car yourCar = new Car(); // a SEPARATE object with its own independent fields
[Link] = "Honda";
[Link] = "Blue";
[Link](40);
[Link](); // "Honda (Blue) going 40 km/h" - independent of myCar
}
}

12.2 The this Keyword

Inside an instance method or constructor, this refers to the specific object the method was called on.
It’s most commonly needed to disambiguate between a field and a parameter/local variable that share
the same name.

public class Student {


String name;

void setName(String name) { // parameter also named "name"


[Link] = name; // [Link] = the FIELD, name = the PARAMETER
}
}

12.3 The Four Pillars of OOP (Preview)

1. Encapsulation — bundling data and the methods that operate on it together, hiding internal details
(Section 17)
2. Inheritance — a class acquiring fields and methods from a parent class (Section 15)
3. Polymorphism — the same method call behaving differently depending on the actual object (Section
16)
4. Abstraction — exposing only essential features while hiding implementation complexity (Section 18)

13. Constructors

13.1 What Is a Constructor?


A constructor is a special block of code that runs automatically whenever an object is created with new .
Its job is to initialize the object’s fields into a valid starting state. A constructor: - Has the exact same
name as the class - Has no return type at all (not even void ) - Runs exactly once per object, at
creation time

public class Student {


String name;
int age;

// Constructor
Student(String name, int age) {
[Link] = name;
[Link] = age;
[Link]("A new Student object was created!");
}
}

Student s1 = new Student("Alice", 20); // prints the message, then [Link]="Alice", [Link]=20

13.2 The Default Constructor

If you don’t write any constructor at all, Java automatically provides an invisible no-argument “default
constructor” that does nothing but initialize fields to their default values (0, false, null). The moment you
write any constructor yourself, this automatic default constructor disappears — if you still want a no-
argument constructor, you must write it explicitly.

public class Point {


int x, y;
// no constructor written -> Java secretly provides Point() { }
}

Point p = new Point(); // works fine; x=0, y=0

13.3 Constructor Overloading

Just like methods, a class can have multiple constructors as long as their parameter lists differ, giving
callers flexible ways to create objects.

public class Student {


String name;
int age;

Student() { // no-arg constructor


this("Unknown", 0); // delegates to the two-arg constructor below
}

Student(String name) { // one-arg constructor


this(name, 18); // delegates, supplying a default age
}

Student(String name, int age) { // two-arg constructor


[Link] = name;
[Link] = age;
}
}

Student s1 = new Student(); // name="Unknown", age=0


Student s2 = new Student("Bob"); // name="Bob", age=18
Student s3 = new Student("Alice", 22); // name="Alice", age=22

13.4 Constructor Chaining with this(...)


Calling this(...) as the very first line of a constructor invokes another constructor in the same class,
letting you avoid duplicating initialization logic (as shown above). It must be the first statement in the
constructor body.

13.5 Instance Initializer Blocks (Less Common, Good to Recognize)

public class Example {


int x;

{ // instance initializer block - runs before the constructor body, every time an object is made
x = 10;
[Link]("Initializer block ran");
}

Example() {
[Link]("Constructor ran, x = " + x);
}
}

14. Static vs Instance Members

This distinction is fundamental and worth its own section, since it explains why main must be static
and clarifies a lot of confusing beginner errors.

14.1 Instance Members (the Default)

Instance fields and methods belong to a specific object. Each object gets its own independent copy of
every instance field. You must create an object before you can access its instance members.

public class Counter {


int count = 0; // instance field - each Counter object has ITS OWN count

void increment() { // instance method - operates on THIS object's count


count++;
}
}

Counter c1 = new Counter();


Counter c2 = new Counter();
[Link]();
[Link]();
[Link]();
[Link]([Link]); // 2
[Link]([Link]); // 1 - completely independent from c1

14.2 Static Members (Belong to the Class Itself)

A static field or method belongs to the class, not to any individual object. There is only ever one copy
of a static field, shared by every object of that class (and even accessible without creating any object at
all).
public class Counter {
static int totalCounters = 0; // ONE shared copy across ALL Counter objects
int count = 0; // each object's own copy

Counter() {
totalCounters++; // every time a new Counter is made, the shared count increases
}
}

Counter c1 = new Counter();


Counter c2 = new Counter();
Counter c3 = new Counter();
[Link]([Link]); // 3 - accessed via the CLASS name, not an object

14.3 Why main Must Be Static

The JVM needs to call main before any object of your class exists. Since static methods belong to the
class itself and don’t require an object to be called, this is exactly what makes public static void
main(...) callable as the program’s entry point.

14.4 Static Methods Cannot Directly Access Instance Members

A static method has no associated object ( this doesn’t exist inside it), so it cannot directly reference
instance fields or call instance methods — it would need an explicit object reference to do so.

public class Example {


int instanceField = 5;
static int staticField = 10;

static void staticMethod() {


[Link](staticField); // OK - static accessing static
// [Link](instanceField); // COMPILE ERROR - no object context!

Example obj = new Example();


[Link]([Link]); // OK - accessed THROUGH an explicit object
}
}

14.5 Static Utility Methods

Because they don’t require an object, static methods are perfect for pure “utility” operations that don’t
depend on any particular object’s state — this is exactly why [Link]() , [Link]() , and
[Link]() are all static.

[Link](16); // called on the CLASS, not an object - no "new Math()" needed

15. Inheritance

15.1 What Is Inheritance?

Inheritance lets a class (the subclass or child class) acquire the fields and methods of another class
(the superclass or parent class), using the extends keyword. This models “is-a” relationships (a Dog
is an Animal ) and enables code reuse — shared behavior lives in one place instead of being duplicated
across similar classes.
class Animal {
String name;

void eat() {
[Link](name + " is eating");
}

void sleep() {
[Link](name + " is sleeping");
}
}

class Dog extends Animal {


void bark() {
[Link](name + " says Woof!");
}
}

public class Main {


public static void main(String[] args) {
Dog d = new Dog();
[Link] = "Rex"; // inherited field, from Animal
[Link](); // inherited method, from Animal
[Link](); // inherited method, from Animal
[Link](); // Dog's own method
}
}

Dog automatically has everything Animal has ( name , eat() , sleep() ), plus its own additional method
bark() . Animal itself has no idea Dog exists — the relationship only flows from child to parent, never
the other way.

15.2 The super Keyword

super refers to the parent class, and is used for two main purposes:

1. Calling the parent’s constructor:

class Animal {
String name;
Animal(String name) {
[Link] = name;
[Link]("Animal constructor ran");
}
}

class Dog extends Animal {


Dog(String name) {
super(name); // MUST be the first line - calls Animal(String)
[Link]("Dog constructor ran");
}
}

Dog d = new Dog("Rex");


// Output:
// Animal constructor ran
// Dog constructor ran

If you don’t explicitly call super(...) , Java automatically inserts a call to the parent’s no-argument
constructor as the first line. If the parent class has no no-argument constructor available, this causes a
compile error, forcing you to call super(...) explicitly with matching arguments.

2. Calling an overridden method’s original version:


class Animal {
void sound() { [Link]("Some generic animal sound"); }
}

class Dog extends Animal {


@Override
void sound() {
[Link](); // still calls Animal's version first
[Link]("Woof!"); // then adds Dog-specific behavior
}
}

15.3 Method Overriding vs Method Hiding

When a subclass defines an instance method with the exact same signature as one in its parent, it
overrides it — the subclass’s version is used whenever called on a subclass object (see Polymorphism,
Section 16). When a subclass defines a static method with the same signature as a static parent method,
this is called hiding, not overriding, and it behaves very differently (resolved at compile time based on
the reference type, not the actual object type) — a subtlety usually only relevant once you’re comfortable
with the basics.

15.4 The final Keyword and Inheritance

A final class cannot be extended at all: final class Immutable { } .


A final method cannot be overridden by any subclass: final void criticalMethod() { } .
(As covered earlier, a final variable cannot be reassigned after its first assignment.)

15.5 Types of Inheritance Java Supports

Single inheritance: one subclass, one direct superclass ( class B extends A ) — this is what Java
supports for classes.
Multilevel inheritance: a chain of inheritance ( class C extends B extends A ) — C inherits from B ,
which inherits from A .
Hierarchical inheritance: multiple subclasses share the same single superclass ( class Dog extends
Animal , class Cat extends Animal ).

Java deliberately does NOT support multiple inheritance of classes (a class extending two or more
classes at once) — this is disallowed specifically to avoid the “Diamond Problem”: if two parent classes
both defined a conflicting method, the compiler couldn’t unambiguously decide which version the child
should inherit. Java sidesteps this entirely by allowing a class to extend only one class, while still
permitting it to implement multiple interfaces (Section 18), which don’t carry the same conflict risk.

15.6 Every Class Ultimately Inherits from Object

If a class doesn’t explicitly extend anything, Java implicitly makes it extend [Link] . This
means every single class in Java automatically has methods like toString() , equals() , and hashCode()
available, which you can override for custom behavior.

class Point {
int x, y;
Point(int x, int y) { this.x = x; this.y = y; }

@Override
public String toString() {
return "(" + x + ", " + y + ")";
}
}

Point p = new Point(3, 4);


[Link](p); // automatically calls toString() -> "(3, 4)"
16. Polymorphism

“Poly” (many) + “morph” (forms) — the ability for the same method call to behave differently depending
on context. Java has two kinds.

16.1 Compile-Time Polymorphism (Method Overloading)

Already covered in Section 11.4 — multiple methods share a name but differ in parameters, and the
compiler decides which one to call based on the arguments at compile time.

16.2 Runtime Polymorphism (Method Overriding) — The More Important Kind

When a subclass provides its own implementation of a method that’s already defined in its superclass
(same name, same parameters), calling that method on a subclass object uses the subclass’s version,
even if you’re referring to the object through a superclass-typed variable.

class Animal {
void sound() { [Link]("Animal makes a sound"); }
}

class Cat extends Animal {


@Override
void sound() { [Link]("Cat meows"); }
}

class Dog extends Animal {


@Override
void sound() { [Link]("Dog barks"); }
}

public class Main {


public static void main(String[] args) {
Animal a1 = new Cat(); // "upcasting" - a Cat treated as an Animal
Animal a2 = new Dog();

[Link](); // "Cat meows" - NOT "Animal makes a sound"!


[Link](); // "Dog barks"
}
}

16.3 Why This Matters: Dynamic Method Dispatch

Even though the declared type of a1 is Animal , the JVM looks at the actual object it points to at runtime
(a Cat ) to decide which sound() implementation to run. This is called dynamic method dispatch, and
it’s what makes polymorphism genuinely powerful: you can write code that operates on the general
Animal type, and it will automatically behave correctly for whatever specific subtype is actually passed
in — without needing if/else chains checking types.

static void makeItSound(Animal a) {


[Link](); // works correctly no matter which subclass of Animal is passed in
}

makeItSound(new Cat()); // "Cat meows"


makeItSound(new Dog()); // "Dog barks"

16.4 The @Override Annotation

Placing @Override above a method that’s meant to override a parent method is optional, but strongly
recommended: it tells the compiler to verify that a matching method genuinely exists in the superclass.
Without it, a small typo in the method signature (wrong parameter type, misspelled name) silently
creates an unrelated new method instead of overriding, and the bug can be very hard to spot.

16.5 Upcasting and Downcasting

Animal a = new Cat(); // upcasting - implicit, always safe (a Cat IS an Animal)

Cat c = (Cat) a; // downcasting - must be explicit, and only safe if 'a' REALLY points to a Cat

Downcasting to the wrong type throws a ClassCastException at runtime. Use instanceof to check first if
you’re unsure:

if (a instanceof Cat) {
Cat c = (Cat) a; // safe
}

17. Encapsulation

17.1 The Core Idea

Encapsulation means bundling an object’s data (fields) together with the methods that operate on that
data, while restricting direct outside access to the internal fields. Instead, controlled access is
provided through public methods — typically getters (to read a value) and setters (to change a value,
often with validation).

17.2 Why Hide Fields Directly?

Without encapsulation, any part of a program could set a field to an invalid state:

class BankAccount {
public double balance; // BAD: directly public, no protection
}

BankAccount acc = new BankAccount();


[Link] = -5000; // nonsensical, but nothing stops it!

With encapsulation, the field is hidden ( private ), and all access is routed through methods that can
enforce rules:
class BankAccount {
private double balance; // hidden from outside code entirely

public double getBalance() { // getter - read access


return balance;
}

public void deposit(double amount) { // controlled write access, with validation


if (amount > 0) {
balance += amount;
} else {
[Link]("Deposit amount must be positive");
}
}

public void withdraw(double amount) {


if (amount > 0 && amount <= balance) {
balance -= amount;
} else {
[Link]("Invalid withdrawal");
}
}
}

BankAccount acc = new BankAccount();


[Link](1000);
[Link](300);
[Link]([Link]()); // 700
// [Link] = -5000; // COMPILE ERROR - balance is private, inaccessible directly

17.3 The JavaBeans Getter/Setter Convention

Standard naming convention used throughout the Java ecosystem (and required by many frameworks
and tools):

private String name;

public String getName() { // getter: "get" + FieldName (capitalized)


return name;
}

public void setName(String name) { // setter: "set" + FieldName (capitalized)


[Link] = name;
}

// for boolean fields, the getter conventionally uses "is" instead of "get"
private boolean active;
public boolean isActive() {
return active;
}

17.4 Benefits of Encapsulation

Validation: setters can reject invalid values before they’re stored.


Flexibility to change internal implementation: you can later change how a field is stored
internally (e.g., splitting one field into two) without breaking any code that uses the public
getters/setters, since the interface stays the same.
Read-only or write-only fields: simply omit the setter (read-only) or the getter (write-only, less
common) to control what outside code is allowed to do.
Debugging: it’s much easier to track down where a value changed when all changes must go
through a specific method, rather than being modified from anywhere in the program.
18. Abstraction (Abstract Classes and Interfaces)

18.1 The Core Idea

Abstraction means exposing only the essential, high-level features of something while hiding the
complex implementation details behind them. When you drive a car, you use the steering wheel and
pedals (the abstraction) without needing to know how the engine’s internal combustion actually works
(the implementation). Java provides two language mechanisms for abstraction: abstract classes and
interfaces.

18.2 Abstract Classes

An abstract class is declared with the abstract keyword. It cannot be instantiated directly (you can
never write new Shape() if Shape is abstract) — it exists only to be extended. It can mix: - Abstract
methods: declared but with no body — subclasses are forced to provide an implementation. - Concrete
methods: fully implemented, inherited as-is (or optionally overridden) by subclasses. - Regular fields,
constructors, and static methods.
abstract class Shape {
String color;

Shape(String color) { // abstract classes CAN have constructors


[Link] = color;
}

abstract double area(); // abstract method - NO body, ends with a semicolon

void displayColor() { // concrete method - has a full implementation


[Link]("Color: " + color);
}
}

class Circle extends Shape {


double radius;

Circle(String color, double radius) {


super(color);
[Link] = radius;
}

@Override
double area() { // MUST implement this - otherwise Circle would ALSO have to be abstract
return [Link] * radius * radius;
}
}

class Rectangle extends Shape {


double width, height;

Rectangle(String color, double width, double height) {


super(color);
[Link] = width;
[Link] = height;
}

@Override
double area() {
return width * height;
}
}

public class Main {


public static void main(String[] args) {
Shape[] shapes = { new Circle("Red", 5), new Rectangle("Blue", 4, 6) };
for (Shape s : shapes) {
[Link](); // inherited concrete method
[Link]("Area: " + [Link]()); // polymorphic call to the overridden version
}
}
}

18.3 Interfaces

An interface defines a pure contract: a set of method signatures that any implementing class promises
to provide, without dictating how. Before Java 8, every method in an interface was implicitly public
abstract (no body allowed at all). Since Java 8, interfaces can also include default methods (with a
body, providing a fallback implementation) and static methods.
interface Drawable {
void draw(); // implicitly public abstract

default void printInfo() { // default method - has a body, optional to override


[Link]("This is a drawable shape");
}
}

interface Resizable {
void resize(double factor);
}

// A class can implement MULTIPLE interfaces, separated by commas


class Square implements Drawable, Resizable {
double side;
Square(double side) { [Link] = side; }

@Override
public void draw() {
[Link]("Drawing a square with side " + side);
}

@Override
public void resize(double factor) {
side *= factor;
}
}

All fields declared in an interface are implicitly public static final (constants) — interfaces cannot
hold ordinary instance state.

18.4 Why Interfaces Enable “Multiple Inheritance” Safely

A class can implement as many interfaces as it wants, because interfaces (traditionally) only specify
what must be done, not how — there’s no field-level state to create ambiguity, so no diamond-problem
conflict arises the way it would with multiple class inheritance.

class FlyingCar implements Drawable, Resizable {


// must implement both draw() and resize()
}

18.5 Abstract Class vs Interface — When to Use Which

Aspect Abstract Class Interface

Instantiable? No No

Can mix abstract + fully Mostly abstract; default / static


Method bodies
implemented methods allowed (Java 8+)

Only public static final


Fields Any kind (instance, static, final, etc.)
constants

Constructors Yes No

A class can extend only ONE A class can implement MANY


Multiple inheritance
abstract class interfaces

A strong “is-a” relationship with


A “can-do” capability contract that
shared code among closely related
Best used for unrelated classes might share (e.g.,
classes (e.g., Shape for
Comparable , Drawable , Runnable )
Circle / Rectangle )
Rule of thumb: if you’re modeling a close family of related objects that share meaningful common code,
lean toward an abstract class. If you’re describing a capability that many unrelated classes might need
to plug into, lean toward an interface.

19. Packages and Access Modifiers

19.1 What Is a Package?

A package is Java’s way of grouping related classes together, similar to how folders organize files.
Packages prevent naming collisions (two different libraries can each have a class named Utils as long
as they’re in different packages) and provide a natural access-control boundary.

package [Link];

public class Main { ... }

The folder structure on disk must mirror the package name: a class in package [Link] must
live inside a folder path com/example/myapp/ .

19.2 Importing Classes from Other Packages

import [Link]; // import ONE specific class


import [Link].*; // import ALL classes in a package (generally discouraged - be explicit)
import static [Link]; // static import - lets you write "PI" instead of "[Link]"

Classes in [Link] (like String , Math , System , Object ) are automatically available everywhere
without any import.

19.3 The Four Access Modifiers, Explained in Full

Subclass Everywhere
Modifier Same Class Same Package (different (different
package) package)

private Yes No No No

(default / no
Yes Yes No No
modifier)

protected Yes Yes Yes No

public Yes Yes Yes Yes

private : only code inside the exact same class can access it. The strictest option — used for internal
implementation details, typically fields (see Encapsulation, Section 17).
default (package-private): if you write no modifier at all, the member is accessible to any class
within the same package, but invisible outside it.
protected : accessible within the same package, AND accessible to subclasses even if they live in a
different package (commonly used for members meant to be extended/customized by subclasses).
public : accessible from absolutely anywhere.
package [Link];

public class Animal {


private String secret = "hidden"; // only Animal itself can access this
String packageInfo = "package-visible"; // default - visible to other classes in [Link]
protected String forSubclasses = "protected"; // visible to subclasses, even in other packages
public String openToAll = "public"; // visible everywhere
}

19.4 General Design Guidance

A well-designed class typically keeps fields private and exposes controlled access through public
methods — this is the essence of encapsulation. Access modifiers on classes themselves are usually just
public (visible everywhere) or default (visible only within the package, useful for internal helper classes
not meant to be part of a library’s public API).

20. Exception Handling

20.1 What Is an Exception?

An exception is an event that disrupts the normal flow of a program’s instructions — typically an error
condition detected at runtime (dividing by zero, accessing an invalid array index, trying to open a file
that doesn’t exist). Without handling, an exception terminates the program (or at least the current
thread) and prints a stack trace. Java’s exception-handling mechanism lets you detect and gracefully
respond to these situations instead.

20.2 The Exception Class Hierarchy

Throwable
/ \
Error Exception
(serious, usually / \
unrecoverable — RuntimeException (checked exceptions,
e.g. OutOfMemoryError) | e.g. IOException,
(unchecked exceptions — SQLException)
e.g. NullPointerException,
ArithmeticException,
ArrayIndexOutOfBoundsException)

Error : represents serious problems a normal application generally shouldn’t try to catch (e.g.,
OutOfMemoryError , StackOverflowError ) — usually indicates something is fundamentally wrong with
the JVM or environment.
Exception : represents conditions a program might reasonably want to catch and handle. Splits
further into:
Checked exceptions: subclasses of Exception (but not RuntimeException ). The compiler forces
you to either catch them or declare them with throws — examples include IOException (file
operations) and SQLException (database operations).
Unchecked exceptions: subclasses of RuntimeException . The compiler does not force you to
handle these — they usually represent programming bugs (like a null reference or bad array
index) rather than expected external failure conditions. Examples: NullPointerException ,
ArithmeticException , ArrayIndexOutOfBoundsException , ClassCastException ,
NumberFormatException .

20.3 try-catch-finally
public class Main {
public static void main(String[] args) {
try {
int result = 10 / 0; // this line throws ArithmeticException
[Link]("This never runs");
} catch (ArithmeticException e) {
[Link]("Error: " + [Link]());
} finally {
[Link]("This ALWAYS runs, whether an exception occurred or not");
}
[Link]("Program continues normally");
}
}

Execution flow: 1. Code inside try runs until either it finishes normally, or an exception is thrown. 2. If
an exception is thrown, execution immediately jumps to a matching catch block (skipping the rest of
try ). 3. finally runs no matter what — whether an exception occurred, was caught, or even if the
try / catch contained a return statement. It’s the ideal place for cleanup code (closing files, releasing
resources). 4. If no catch block matches the thrown exception’s type, the exception propagates upward
(out of the current method, to whatever called it), potentially crashing the program if never caught
anywhere.

20.4 Multiple catch Blocks

You can catch different exception types differently. Catch blocks are checked top to bottom, and only the
first matching one runs — so more specific exception types must be listed before more general ones.

try {
int[] arr = new int[5];
arr[10] = 50; // throws ArrayIndexOutOfBoundsException
} catch (ArrayIndexOutOfBoundsException e) {
[Link]("Array error: " + [Link]());
} catch (ArithmeticException e) {
[Link]("Arithmetic error: " + [Link]());
} catch (Exception e) { // general fallback - catches anything not matched above
[Link]("Unexpected error: " + [Link]());
}

20.5 Multi-catch (Combining Types in One Block)

If two exception types should be handled identically, you can combine them:

try {
// risky code
} catch (IOException | SQLException e) {
[Link]("I/O or database error: " + [Link]());
}

20.6 throw vs throws

throw : an actual statement used inside code to trigger an exception right now.
throws : appears in a method’s signature to declare that this method might propagate a checked
exception, requiring callers to handle it.
static void checkAge(int age) throws IllegalArgumentException {
if (age < 18) {
throw new IllegalArgumentException("Must be at least 18 years old");
}
[Link]("Age is valid");
}

public static void main(String[] args) {


try {
checkAge(15);
} catch (IllegalArgumentException e) {
[Link]("Caught: " + [Link]());
}
}

20.7 Custom (User-Defined) Exceptions

You can create your own exception types by extending Exception (checked) or RuntimeException
(unchecked), which is useful for representing domain-specific error conditions clearly.

class InsufficientBalanceException extends Exception {


InsufficientBalanceException(String message) {
super(message); // passes the message up to the built-in Exception class
}
}

class BankAccount {
double balance;

void withdraw(double amount) throws InsufficientBalanceException {


if (amount > balance) {
throw new InsufficientBalanceException("Not enough funds to withdraw " + amount);
}
balance -= amount;
}
}

public class Main {


public static void main(String[] args) {
BankAccount acc = new BankAccount();
[Link] = 100;
try {
[Link](500);
} catch (InsufficientBalanceException e) {
[Link]("Transaction failed: " + [Link]());
}
}
}

20.8 try-with-resources

Any object implementing the AutoCloseable interface (like file streams, scanners, and database
connections) can be declared inside the parentheses of try (...) , and Java automatically closes it
when the block ends — whether normally or due to an exception — eliminating the need for a manual
finally { [Link](); } block.

try (Scanner fileScanner = new Scanner(new File("[Link]"))) {


while ([Link]()) {
[Link]([Link]());
}
} // [Link]() is called AUTOMATICALLY here, guaranteed
catch (FileNotFoundException e) {
[Link]("File not found: " + [Link]());
}
21. Generics

21.1 The Problem Generics Solve

Before generics (introduced in Java 5), collections like ArrayList stored plain Object references,
meaning you could accidentally put mismatched types into the same list, and you had to manually cast
every element back to its real type when retrieving it — an error-prone process discovered only at
runtime.

// OLD, pre-generics style (avoid this)


ArrayList list = new ArrayList();
[Link]("Hello");
[Link](42); // no error - Object accepts anything!
String s = (String) [Link](1); // compiles fine, but CRASHES at runtime (ClassCastException)

21.2 Generics: Type Safety at Compile Time

Generics let you parameterize a class or method with a specific type, so the compiler enforces
correctness before the program even runs.

ArrayList<String> list = new ArrayList<>(); // this list can ONLY ever hold Strings
[Link]("Hello");
// [Link](42); // COMPILE ERROR - caught immediately, not at runtime!
String s = [Link](0); // no cast needed - the compiler already knows it's a String

21.3 Writing Your Own Generic Class

class Box<T> { // T is a "type parameter" - a placeholder for whatever type is used later
private T content;

void set(T content) { [Link] = content; }


T get() { return content; }
}

Box<String> stringBox = new Box<>();


[Link]("Hello");
String s = [Link](); // no casting needed

Box<Integer> intBox = new Box<>();


[Link](42);
int i = [Link]();

Common type parameter naming conventions: T (Type), E (Element, common in collections), K / V


(Key/Value, common in maps), N (Number).

21.4 Generic Methods

static <T> void printArray(T[] array) {


for (T element : array) {
[Link](element);
}
}

Integer[] intArr = {1, 2, 3};


String[] strArr = {"a", "b", "c"};
printArray(intArr); // works
printArray(strArr); // also works - same method, different types

21.5 Bounded Type Parameters

You can restrict a type parameter to be a subtype of a particular class or interface using extends .
static <T extends Number> double sum(T[] numbers) { // T must be Number or a subclass (Integer, Double, ...)
double total = 0;
for (T n : numbers) {
total += [Link]();
}
return total;
}

21.6 Why Generics Matter for Collections

Every class in the Collections Framework (covered next) is built around generics — this is why you’ll
almost always see angle-bracket syntax like List<String> or Map<String, Integer> when working with
collections.

22. Collections Framework

22.1 Why Not Just Use Arrays?

Arrays have a fixed size decided at creation and offer very few built-in operations (no easy way to insert
in the middle, remove an item, or check for existence). The Collections Framework ( [Link] )
provides flexible, resizable, feature-rich data structures for storing groups of objects, built around a
small set of core interfaces.

22.2 The Core Interfaces

Collection
/ | \
List Set Queue

Map (separate hierarchy - stores key-value pairs)

22.3 List — Ordered, Allows Duplicates

A List maintains insertion order and allows duplicate elements, and lets you access elements by
numeric index (like an array, but resizable).

import [Link];
import [Link];

List<String> names = new ArrayList<>(); // ArrayList is the most common List implementation
[Link]("Alice");
[Link]("Bob");
[Link]("Alice"); // duplicates ARE allowed
[Link](1, "Charlie"); // insert "Charlie" at index 1, shifting others right

[Link](names); // [Alice, Charlie, Bob, Alice]


[Link]([Link](0)); // Alice
[Link]("Bob"); // removes the FIRST occurrence matching this value
[Link](0); // removes the element AT this index
[Link]([Link]("Alice")); // true
[Link]([Link]()); // current number of elements

for (String name : names) { // iterate with a for-each loop


[Link](name);
}

ArrayList (backed by a resizable array — fast random access, slower inserts/removes in the middle) vs
LinkedList (backed by a doubly linked list — fast inserts/removes at the ends, slower random access).
U s e ArrayList by default unless you specifically need frequent insertions/removals at arbitrary
positions.

22.4 Set — No Duplicates Allowed

A Set automatically rejects duplicate elements (based on .equals() ), and generally does not guarantee
any particular ordering (with HashSet ) unless you use TreeSet (sorted) or LinkedHashSet (insertion
order preserved).

import [Link];
import [Link];

Set<Integer> numbers = new HashSet<>();


[Link](10);
[Link](20);
[Link](10); // silently ignored - already present
[Link]([Link]()); // 2, not 3

Set<String> sortedSet = new [Link]<>();


[Link]("Banana");
[Link]("Apple");
[Link]("Cherry");
[Link](sortedSet); // [Apple, Banana, Cherry] - TreeSet keeps things sorted automatically

22.5 Map — Key-Value Pairs

A Map associates unique keys with values — think of it as a dictionary or lookup table. Keys must be
unique (adding a value with an existing key overwrites the old value); values can be duplicated.

import [Link];
import [Link];

Map<String, Integer> ages = new HashMap<>();


[Link]("Alice", 25);
[Link]("Bob", 30);
[Link]("Alice", 26); // OVERWRITES the previous value for "Alice"

[Link]([Link]("Alice")); // 26
[Link]([Link]("Charlie")); // null - key doesn't exist
[Link]([Link]("Charlie", 0)); // 0 - safe fallback instead of null
[Link]([Link]("Bob")); // true
[Link]("Bob");

for (String key : [Link]()) { // iterate over just the keys


[Link](key);
}

for ([Link]<String, Integer> entry : [Link]()) { // iterate over key-value pairs together
[Link]([Link]() + " is " + [Link]() + " years old");
}

22.6 Queue — First-In-First-Out (FIFO) Processing

import [Link];
import [Link];

Queue<String> queue = new LinkedList<>();


[Link]("first"); // add to the back
[Link]("second");
[Link]("third");

[Link]([Link]()); // "first" - removes and returns the FRONT element


[Link]([Link]()); // "second" - looks at the front WITHOUT removing it

22.7 Iterating Safely While Modifying — the Iterator


Modifying a collection (adding/removing elements) directly inside a for-each loop over it throws a
ConcurrentModificationException . The Iterator interface allows safe removal during traversal.

import [Link];

List<Integer> nums = new ArrayList<>([Link](1, 2, 3, 4, 5, 6));


Iterator<Integer> it = [Link]();
while ([Link]()) {
int n = [Link]();
if (n % 2 == 0) {
[Link](); // safe - removes the CURRENT element via the iterator itself
}
}
[Link](nums); // [1, 3, 5]

22.8 Sorting Custom Objects: Comparable and Comparator

Comparable — implemented by the class itself, defining its “natural” default sort order:

class Student implements Comparable<Student> {


String name;
int age;
Student(String name, int age) { [Link] = name; [Link] = age; }

@Override
public int compareTo(Student other) {
return [Link] - [Link]; // sorts ascending by age
}
}

List<Student> students = new ArrayList<>();


[Link](new Student("Alice", 22));
[Link](new Student("Bob", 19));
[Link](students); // uses compareTo() automatically

Comparator — a separate object defining a custom sort order, useful when you want multiple different
sort orders without modifying the class itself:

import [Link];

[Link]([Link](s -> [Link])); // sort by name instead


[Link]([Link]((Student s) -> [Link]).reversed()); // by age, descending

22.9 Quick Comparison Table

Interface Common Implementations Key Characteristic

ordered, indexable, allows


List ArrayList , LinkedList
duplicates

no duplicates; TreeSet keeps


Set HashSet , TreeSet , LinkedHashSet
sorted order

key-value pairs; TreeMap keeps


Map HashMap , TreeMap , LinkedHashMap
keys sorted

FIFO order (or priority order for


Queue LinkedList , PriorityQueue
PriorityQueue )

23. File Handling (I/O)


23.1 The File Class

Represents a path to a file or directory on disk (does not itself read/write content).

import [Link];

File f = new File("[Link]");


[Link]([Link]()); // does this file currently exist?
[Link]([Link]()); // "[Link]"
[Link]([Link]()); // full path on disk
[Link]([Link]()); // size in bytes
[Link](); // creates an empty file if it doesn't exist yet
[Link](); // deletes the file

23.2 Writing to a File

import [Link];
import [Link];

try (FileWriter writer = new FileWriter("[Link]")) { // overwrites the file if it exists


[Link]("Hello, File!\n");
[Link]("Second line.\n");
} catch (IOException e) {
[Link]("Write error: " + [Link]());
}

U s e new FileWriter("[Link]", true) (with a second true argument) to append instead of


overwriting.

23.3 Reading from a File

import [Link];
import [Link];
import [Link];

try (BufferedReader reader = new BufferedReader(new FileReader("[Link]"))) {


String line;
while ((line = [Link]()) != null) { // readLine() returns null at end-of-file
[Link](line);
}
} catch (IOException e) {
[Link]("Read error: " + [Link]());
}

23.4 Modern Alternative: [Link] (Simpler for Small Files)

import [Link].*;
import [Link];

// Write all lines at once


[Link]([Link]("[Link]"), [Link]("Line 1", "Line 2"));

// Read all lines at once


List<String> lines = [Link]([Link]("[Link]"));
[Link]([Link]::println);

23.5 try-with-resources Recap for File I/O

As covered in Section 20.8, wrapping file streams in try (...) guarantees they are closed
automatically, even if an exception occurs mid-read/write — this prevents resource leaks (files being left
“locked” open) which is a very common bug when file handling is done manually with explicit close()
calls that might get skipped due to an early exception.
24. Multithreading

24.1 What Is a Thread?

A thread is an independent path of execution within a program. Every Java program has at least one
thread automatically (the “main thread,” which runs your main method). Multithreading means
running multiple threads concurrently, which allows a program to perform multiple tasks seemingly at
the same time (e.g., keeping a UI responsive while downloading a file in the background).

24.2 Creating Threads: Two Approaches

Approach 1: Extending the Thread class

class MyThread extends Thread {


@Override
public void run() { // define what the thread should do
for (int i = 1; i <= 5; i++) {
[Link]([Link]().getName() + ": " + i);
}
}
}

public class Main {


public static void main(String[] args) {
MyThread t1 = new MyThread();
[Link](); // starts a NEW thread, which then calls run() concurrently
// NEVER call [Link]() directly - that would just run the code on the CURRENT thread, defeating the purpose
}
}

Approach 2: Implementing Runnable (generally preferred)

class MyTask implements Runnable {


@Override
public void run() {
[Link]("Task running on: " + [Link]().getName());
}
}

public class Main {


public static void main(String[] args) {
Thread t = new Thread(new MyTask());
[Link]();

// Or, using a lambda expression (since Runnable has a single abstract method):
Thread t2 = new Thread(() -> [Link]("Lambda thread running"));
[Link]();
}
}

Why Runnable is usually preferred: since Java classes can only extend one class, if your class already
extends something else, it cannot also extend Thread . Implementing Runnable keeps your class free to
extend another class while still being runnable on a thread — it also cleanly separates “the task to run”
from “the mechanism that runs it.”

24.3 Thread Lifecycle

A thread moves through several states: New (created but not started) → Runnable (started, eligible to
run, waiting for CPU time) → Running (actively executing) → Blocked/Waiting (paused, e.g. waiting for
a lock or sleep() ) → Terminated (finished executing run() ).
[Link](1000); // pauses the CURRENT thread for ~1000 milliseconds (checked exception: InterruptedException)
[Link](); // makes the CALLING thread wait until t1 finishes before continuing

24.4 Race Conditions and the Need for Synchronization

When multiple threads access and modify shared data at the same time without coordination,
unpredictable results can occur — this is called a race condition.

class Counter {
int count = 0;
void increment() {
count++; // NOT ATOMIC! This is actually: read count, add 1, write count back - 3 separate steps
}
}

If two threads call increment() at nearly the same instant, both might read the same starting value
before either writes back the incremented result, causing one increment to be silently lost.

24.5 The synchronized Keyword

Ensures that only one thread at a time can execute a particular method or block for a given object,
preventing race conditions on shared data.

class Counter {
int count = 0;

synchronized void increment() { // only one thread can be inside this method at a time, per object
count++;
}
}

Synchronization has a performance cost (threads must wait their turn), so it should be applied only to
the specific sections of code that actually touch shared, mutable state — not entire programs
indiscriminately.

24.6 Beginner Takeaway

Multithreading is a deep topic (thread pools, [Link] utilities like ExecutorService , locks,
atomic variables, and more lie beyond this introduction) — but understanding Thread / Runnable ,
start() vs run() , and the basic idea of race conditions and synchronized gives you a solid foundation
to build on.

25. Enums

25.1 What Is an Enum?

An enum (enumeration) is a special data type that represents a fixed set of named constant values.
It’s ideal for situations where a variable should only ever hold one of a small, known set of options (days
of the week, directions, states of an order, card suits, etc.) — far safer than using arbitrary int codes or
raw String s, since the compiler enforces that only valid values are used.
enum Day {
MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

public class Main {


public static void main(String[] args) {
Day today = [Link];

if (today == [Link] || today == [Link]) {


[Link]("Weekend!");
} else {
[Link]("Weekday");
}

// Enums work great with switch statements


switch (today) {
case MONDAY -> [Link]("Start of the work week");
case FRIDAY -> [Link]("Almost the weekend");
default -> [Link]("Just another day");
}
}
}

25.2 Enums Are Actually Classes

Unlike in some languages where enums are just glorified integers, Java enums are full classes under the
hood — they can have fields, constructors, and methods, and each constant is technically a singleton
instance of the enum type.

enum Planet {
MERCURY(3.3e23, 2.4e6),
VENUS(4.9e24, 6.1e6),
EARTH(5.9e24, 6.4e6);

private final double mass; // in kilograms


private final double radius; // in meters

Planet(double mass, double radius) { // enum constructors are always implicitly private
[Link] = mass;
[Link] = radius;
}

double surfaceGravity() {
final double G = 6.67300E-11;
return G * mass / (radius * radius);
}
}

[Link]([Link]());

25.3 Useful Built-in Enum Methods

Day d = [Link];
[Link]([Link]()); // "MONDAY" - the constant's exact name as a String
[Link]([Link]()); // 0 - position in the declaration order (zero-based)

Day[] allDays = [Link](); // array of every constant, in declared order


Day parsed = [Link]("FRIDAY"); // converts a String back into the matching enum constant

26. Wrapper Classes and Utility Classes

26.1 Wrapper Classes: Bridging Primitives and Objects


Every primitive type has a corresponding wrapper class in [Link] , which packages a primitive value
inside a full object. This is necessary because many parts of Java (notably the Collections Framework,
which is built entirely around generics and objects) cannot work with raw primitives directly.

Primitive Wrapper Class

byte Byte

short Short

int Integer

long Long

float Float

double Double

char Character

boolean Boolean

26.2 Autoboxing and Unboxing

Java automatically converts between a primitive and its wrapper when needed, without you having to
write explicit conversion code.

int a = 10;
Integer obj = a; // AUTOBOXING - int automatically wrapped into an Integer object
int b = obj; // UNBOXING - Integer automatically unwrapped back into an int

List<Integer> numbers = new ArrayList<>();


[Link](5); // 5 (an int literal) is autoboxed into Integer automatically

Caution: autoboxing/unboxing has a small performance cost and a notable trap — comparing wrapper
objects with == compares references (like Strings), not values, for values outside a small internally
cached range:

Integer x = 100, y = 100;


[Link](x == y); // true - small Integer values (-128 to 127) are cached and reused

Integer p = 200, q = 200;


[Link](p == q); // false! outside the cache range, these are separate objects
[Link]([Link](q)); // true - always use .equals() for wrapper comparisons

26.3 Useful Wrapper Class Methods

int num = [Link]("123"); // String -> int


double d = [Link]("3.14"); // String -> double
String s = [Link](123); // int -> String
String binary = [Link](10); // "1010"

int max = Integer.MAX_VALUE; // largest possible int value


int min = Integer.MIN_VALUE; // smallest possible int value

26.4 The Math Class


[Link](10, 20); // 20
[Link](10, 20); // 10
[Link](-5); // 5
[Link](2, 3); // 8.0 (2 to the power of 3)
[Link](16); // 4.0
[Link](4.7); // 4.0 - rounds down
[Link](4.2); // 5.0 - rounds up
[Link](4.5); // 5 (long or int, depending on input type) - rounds to nearest
[Link](); // random double in the range [0.0, 1.0)

Generating a random integer within a specific range:

int min = 1, max = 100;


int randomNum = min + (int)([Link]() * (max - min + 1)); // random int between 1 and 100 inclusive

Alternatively, the [Link] class provides more control:

import [Link];
Random rand = new Random();
int n = [Link](100); // random int from 0 to 99 (exclusive upper bound)

27. Common Beginner Mistakes (Explained)

27.1 Comparing Strings (or Wrapper Objects) with ==

String a = new String("test");


String b = new String("test");
if (a == b) { ... } // WRONG - compares memory addresses, will be false
if ([Link](b)) { ... } // CORRECT - compares actual content

Why it happens: == works correctly for primitives (comparing actual values) but for objects it
compares references. This is one of the single most common bugs for people coming from other
languages.

27.2 Forgetting break in a switch Statement

switch (x) {
case 1:
[Link]("one"); // if x==1 and break is missing, execution FALLS THROUGH
case 2:
[Link]("two"); // this ALSO runs even though x wasn't 2!
break;
}

Fix: always include break at the end of each case unless fall-through is deliberate (and comment it
clearly if so).

27.3 Integer Division Truncation

double avg = 5 / 2; // WRONG - this is int/int = 2 (as an int), THEN converted to 2.0
double avg2 = 5.0 / 2; // CORRECT - 2.5, because at least one operand is already a double

27.4 Off-by-One Errors in Loops


int[] arr = new int[5];
for (int i = 0; i <= [Link]; i++) { // WRONG - should be < not <=, this goes out of bounds!
arr[i] = i;
}

Arrays are zero-indexed with valid indices from 0 to length - 1 . Using <= with .length causes an
ArrayIndexOutOfBoundsException on the final iteration.

27.5 NullPointerException

String s = null;
[Link]([Link]()); // throws NullPointerException - can't call a method on "nothing"

Fix: check for null before use, or ensure objects are always properly initialized:

if (s != null) {
[Link]([Link]());
}

27.6 Confusing this and super

this refers to the current object; super refers specifically to the immediate parent class. Mixing them
up (especially in constructor chaining) causes confusing compile errors, since this(...) and
super(...) are both only valid as the very first statement in a constructor, and you can never use both in
the same constructor.

27.7 Not Closing Resources

FileWriter writer = new FileWriter("[Link]");


[Link]("Hello");
// forgot [Link]() - the write might not even be flushed to disk, and the file handle stays locked

Fix: always use try-with-resources (Section 20.8) so closing happens automatically and reliably.

27.8 Modifying a Collection While Iterating with a For-Each Loop

for (String name : list) {


if ([Link]("Bob")) {
[Link](name); // throws ConcurrentModificationException!
}
}

Fix: use an explicit Iterator and its .remove() method (Section 22.7), or collect items to remove into a
separate list first and remove them afterward.

27.9 Confusing == with .equals() Generally

Applies to any object type, not just Strings — == should almost always be reserved for primitives (and,
occasionally, explicit reference-identity checks). When in doubt about objects, use .equals() .

27.10 Shadowing Variables Accidentally

class Example {
int value = 10;
void setValue(int value) { // parameter "value" SHADOWS the field "value"
value = value; // WRONG - this just assigns the parameter to itself, does nothing useful!
}
}
Fix: use [Link] = value; to clearly distinguish the field from the parameter (Section 12.2).

28. Practice Programs with Explanations

28.1 Check If a Number Is Prime

public class PrimeCheck {


public static void main(String[] args) {
int num = 29;
boolean isPrime = true;

if (num <= 1) {
isPrime = false; // numbers 1 and below are never prime by definition
}

// A number is prime if no integer from 2 up to its square root divides it evenly.


// We only need to check up to sqrt(num) because if a factor exists beyond that,
// its "pair" factor would already have been found below the square root.
for (int i = 2; i <= [Link](num); i++) {
if (num % i == 0) {
isPrime = false;
break; // no need to keep checking once we've found a factor
}
}

[Link](num + " is prime: " + isPrime);


}
}

28.2 Generate the Fibonacci Series

public class Fibonacci {


public static void main(String[] args) {
int n = 10; // how many terms to generate
int a = 0, b = 1; // the first two Fibonacci numbers

for (int i = 0; i < n; i++) {


[Link](a + " ");
int next = a + b; // each new term is the sum of the previous two
a = b; // shift the "window" forward by one position
b = next;
}
}
}
// Output: 0 1 1 2 3 5 8 13 21 34

28.3 Reverse a String

public class ReverseString {


public static void main(String[] args) {
String str = "hello";
String reversed = new StringBuilder(str).reverse().toString();
// StringBuilder wraps the string in a mutable buffer, .reverse() flips it
// in place, then .toString() converts it back to an immutable String.
[Link](reversed); // olleh
}
}

28.4 Factorial Using Recursion


public class Factorial {
static int factorial(int n) {
if (n <= 1) return 1; // base case - stops the recursion
return n * factorial(n - 1); // recursive case
}

public static void main(String[] args) {


[Link](factorial(5)); // 5*4*3*2*1 = 120
}
}

28.5 Bubble Sort

public class BubbleSort {


public static void main(String[] args) {
int[] arr = {64, 34, 25, 12, 22, 11, 90};

// Outer loop: controls how many passes through the array we make
for (int i = 0; i < [Link] - 1; i++) {
// Inner loop: compares each pair of ADJACENT elements,
// and swaps them if they're in the wrong order.
// After each full pass, the largest remaining unsorted element
// "bubbles up" to its correct final position at the end.
for (int j = 0; j < [Link] - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}

for (int n : arr) [Link](n + " ");


// Output: 11 12 22 25 34 64 90
}
}

28.6 Check If a String Is a Palindrome

public class PalindromeCheck {


public static void main(String[] args) {
String str = "madam";
String reversed = new StringBuilder(str).reverse().toString();

if ([Link](reversed)) {
[Link](str + " is a palindrome");
} else {
[Link](str + " is not a palindrome");
}
}
}

28.7 Find the Largest Element in an Array


public class LargestElement {
public static void main(String[] args) {
int[] arr = {23, 89, 12, 45, 67};
int max = arr[0]; // start by assuming the first element is the largest

for (int i = 1; i < [Link]; i++) { // start from index 1 - already have index 0
if (arr[i] > max) {
max = arr[i]; // found a bigger one - update our running maximum
}
}

[Link]("Largest element: " + max); // 89


}
}

28.8 Count Vowels in a String

public class CountVowels {


public static void main(String[] args) {
String str = "Hello World";
String vowels = "aeiouAEIOU";
int count = 0;

for (char c : [Link]()) { // convert String to a char array to iterate character by


character
if ([Link](c) != -1) { // if this character is found anywhere in "vowels"
count++;
}
}

[Link]("Vowel count: " + count); // 3


}
}

29. Keywords Cheat Sheet

Keyword Purpose

class defines a class

interface defines a pure contract of methods

extends class inheritance / interface extension

implements a class fulfilling an interface’s contract

new allocates a new object on the heap

static belongs to the class itself, not any one object

constant value / cannot override (method) / cannot


final
extend (class)

this reference to the current object

super reference to the immediate parent class

void a method returns no value

return exits a method, optionally sending back a value

an incomplete class or method requiring a subclass to


abstract
complete it

try / catch / finally exception handling blocks


throw manually triggers an exception

declares that a method may propagate a checked


throws
exception

public / private / protected access control modifiers

package groups related classes together

import brings a class from another package into scope

enum defines a fixed set of named constants

synchronized restricts a method/block to one thread at a time

instanceof checks an object’s runtime type

null represents “no object”/absence of a reference

break / continue loop and switch control flow

End of Detailed Notes. The best way to internalize these concepts is to actually type out and run every
code example yourself, then deliberately break them (introduce bugs) to see the error messages Java
produces — learning to read and understand compiler errors and stack traces is one of the most
valuable beginner skills of all.

You might also like