Java Tutorial
Java is a programming language, created in 1995. More than 3 billion devices run Java. It is used to develop desktop and mobile applications, embedded systems, etc.
Get Started
Welcome to the Java Tutorial tutorial series! To begin learning, navigate through the chapters on the left sidebar.
Installation & Setup
To run Java, you need to install the JDK (Java Development Kit) and set up the environmental variables on your computer.
- Download JDK from Oracle's official site.
- Run the installer and follow instructions.
- Set JAVA_HOME system variable.
- Add bin directory to system PATH.
JVM, JRE & JDK
It's important to understand the differences between JVM, JRE, and JDK.
- JVM (Java Virtual Machine): An abstract machine that enables your computer to run a Java program.
- JRE (Java Runtime Environment): Contains the JVM and library classes necessary to run a program.
- JDK (Java Development Kit): Contains JRE plus development tools like the compiler (javac).
Basic Java Syntax
Every Java program requires a class, and the name of the class must match the filename. Execution always begins in the main() method.
public class Main {
// The entry point of every Java application
public static void main(String[] args) {
System.out.println("Hello World");
}
}
Understanding the Syntax
public: An access modifier meaning the class/method is accessible everywhere.class: Keyword to declare a class.static: Means the method belongs to the class itself, not to a specific object.void: The method does not return a value.String[] args: An array of Strings passed as command-line arguments.
Comments
Comments are ignored by the compiler. They are used for explaining code, preventing execution of specific lines during testing, or generating documentation.
1. Single-line Comments
Starts with // and comments out everything on that line.
// Prints greeting
System.out.println("Hello World");
2. Multi-line Comments
Starts with /* and ends with */.
/* This is a multi-line comment
that spans multiple lines */
int x = 10;
3. Documentation Comments (Javadoc)
Starts with /** and ends with */. Used to auto-generate HTML documentation for APIs.
/**
* Calculates the sum of two integers.
* @param a The first number
* @param b The second number
* @return The sum
*/
public int sum(int a, int b) { return a + b; }
Data Types & Casting
Java is strongly typed, meaning all variables must have a defined data type.
Primitive Data Types
| Type | Size | Description |
|---|---|---|
byte | 1 byte | Stores whole numbers from -128 to 127 |
short | 2 bytes | Stores whole numbers from -32,768 to 32,767 |
int | 4 bytes | Stores whole numbers from -2,147,483,648 to 2,147,483,647 |
long | 8 bytes | Stores huge whole numbers. Suffix with L |
float | 4 bytes | Fractional numbers (6-7 decimal digits). Suffix with f |
double | 8 bytes | Fractional numbers (15 decimal digits). Suffix with d |
boolean | 1 bit | Stores true or false values |
char | 2 bytes | Stores a single character/letter or ASCII values |
Type Casting
Casting is assigning a value of one primitive type to another.
- Widening (Automatic): Smaller type to larger type (e.g.,
inttodouble). - Narrowing (Manual): Larger type to smaller type (e.g.,
doubletoint). Done by placing the target type in parentheses.
int myInt = 9;
double myDouble = myInt; // Widening: 9.0
double myD = 9.78d;
int myI = (int) myD; // Narrowing: 9 (decimals truncated)
Variables & Scope
Variables act as containers. Java defines three main types of variables based on their scope.
Variable Types
- Local Variables: Declared inside a method, constructor, or block. Destroyed when the block ends.
- Instance Variables: Declared inside a class but outside methods. Specific to each object (instance).
- Static Variables: Declared with
static. Shared across all objects of the class.
public class Car {
static int totalCars = 0; // Static Variable
String color; // Instance Variable
public void drive() {
int speed = 100; // Local Variable
System.out.println("Driving at " + speed + "km/h");
}
}
The final Keyword
If you don't want others (or yourself) to overwrite existing values, use the final keyword (creates constants).
final int myNum = 15;
myNum = 20; // This will generate an error: cannot assign a value to final variable
Operators Deep Dive
Operators perform operations on variables and values.
Types of Operators
- Arithmetic:
+,-,*,/,%(Modulus),++(Increment),--(Decrement) - Assignment:
=,+=,-=,*=,/=,%= - Comparison:
==,!=,>,<,>=,<= - Logical:
&&(Logical AND),||(Logical OR),!(Logical NOT) - Bitwise:
&(AND),|(OR),^(XOR),~(Complement),<<(Left shift),>>(Right shift)
Ternary Operator
A shorthand for an if...else statement.
// Syntax: variable = (condition) ? expressionTrue : expressionFalse;
int time = 20;
String result = (time < 18) ? "Good day." : "Good evening.";
System.out.println(result); // Outputs: Good evening.
Input/Output
Handling input via Scanner and output via System.out and formatting.
Scanner Input Types
The Scanner class can read different data types:
nextLine()- Reads a StringnextInt()- Reads an intnextDouble()- Reads a doublenextBoolean()- Reads a boolean
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scan = new Scanner(System.in);
System.out.print("Enter age: ");
int age = scan.nextInt();
System.out.println("You are " + age + " years old.");
scan.close(); // Important to prevent memory leaks!
}
}
Formatted Output
Use System.out.printf() for formatting strings.
double price = 10.5;
// %f for floats, %.2f for 2 decimal places, %s for strings, %d for ints
System.out.printf("The price is $%.2f", price); // Outputs: The price is $10.50
If...Else Statements
Conditional statements let you execute different code blocks based on boolean expressions.
Syntax Varieties
if- Executes code if the condition istrue.else if- Tests a new condition if the previousifwasfalse.else- Executes if all preceding conditions arefalse.
int score = 85;
if (score >= 90) {
System.out.println("Grade: A");
} else if (score >= 80) {
System.out.println("Grade: B"); // This will execute
} else {
System.out.println("Grade: C");
}
Block Scope Note
Variables declared inside the {} block of an if statement are local to that block and cannot be accessed outside.
Switch Statement & Expressions
The switch statement is an elegant alternative to a long if...else if chain.
Classic Switch
int day = 3;
switch (day) {
case 1:
System.out.println("Monday");
break; // Important! Stops execution of further cases.
case 3:
System.out.println("Wednesday"); // Executes this
break;
default:
System.out.println("Weekend");
}
Enhanced Switch (Java 14+)
Java introduced arrow syntax -> to eliminate the need for break and allow the switch to return a value (Switch Expression).
String dayType = switch (day) {
case 1, 2, 3, 4, 5 -> "Weekday";
case 6, 7 -> "Weekend";
default -> "Unknown";
};
System.out.println(dayType); // Outputs: Weekday
Loops (For, While, Do-While)
Loops are used to execute a set of instructions repeatedly.
1. While Loop
Evaluates condition before executing the block.
int i = 0;
while (i < 3) {
System.out.println(i);
i++;
}
2. Do-While Loop
Executes the block at least once before checking the condition.
int j = 0;
do {
System.out.println("Runs at least once: " + j);
j++;
} while (j < 0);
3. For Loop
Ideal when you know exactly how many times you want to loop.
for (int k = 0; k < 3; k++) {
System.out.println(k);
}
4. Enhanced For-Loop (For-Each)
Used exclusively to loop through elements in arrays or collections.
String[] cars = {"Volvo", "BMW", "Ford"};
for (String car : cars) {
System.out.println(car);
}
Break, Continue & Labeled Loops
Control the flow within loops precisely.
Break vs Continue
breakcompletely terminates the loop.continueskips the rest of the current iteration and jumps to the next one.
for (int i = 0; i < 5; i++) {
if (i == 2) continue; // Skips 2
if (i == 4) break; // Stops before 4
System.out.print(i + " "); // Output: 0 1 3
}
Labeled Break/Continue
Useful for breaking out of nested loops.
outerLoop:
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
if (i == 2 && j == 2) {
break outerLoop; // Breaks BOTH loops
}
}
}
Arrays Deep Dive
Arrays hold multiple values of the same type in a fixed-size, contiguous memory location.
1D Arrays
int[] myNum = {10, 20, 30, 40};
System.out.println(myNum.length); // Output: 4
myNum[0] = 50; // Modify first element
Multi-Dimensional Arrays
An array of arrays. Useful for matrices or grids.
int[][] myNumbers = { {1, 2, 3, 4}, {5, 6, 7} };
System.out.println(myNumbers[1][2]); // Output 7 (Second array, 3rd element)
The java.util.Arrays Class
A utility class with static methods for array manipulation.
import java.util.Arrays;
// ...
int[] arr = {5, 2, 8, 1};
Arrays.sort(arr); // Sorts array in ascending order
System.out.println(Arrays.toString(arr)); // Output: [1, 2, 5, 8]
Strings in Depth
In Java, String is an object, not a primitive type. Strings are immutable (cannot be changed once created).
String Literal vs new String()
Strings created without new go into the String Constant Pool to save memory.
String s1 = "Hello"; // Goes to String Pool
String s2 = "Hello"; // Reuses the pool object (s1 == s2 is true)
String s3 = new String("Hello"); // Creates a new object in Heap memory (s1 == s3 is false)
Essential String Methods
length()- Length of the string.charAt(index)- Character at a specific index.substring(start, end)- Extracts a portion of the string.split(regex)- Breaks string into an array.equals(str)- Compares content (always use this over==!).
StringBuilder and StringBuffer
Because Strings are immutable, modifying them inside a loop is slow. Use StringBuilder (fast) or StringBuffer (thread-safe) instead.
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World"); // Modifies the object without creating a new one
System.out.println(sb.toString());
Classes & Objects
Java is strictly object-oriented. A Class is a blueprint, and an Object is an instance of that blueprint.
Constructors & The this Keyword
Constructors initialize objects. The this keyword refers to the current object instance.
public class Employee {
String name;
int id;
// Parameterized Constructor
public Employee(String name, int id) {
this.name = name; // 'this.name' refers to instance variable
this.id = id;
}
public static void main(String[] args) {
Employee emp1 = new Employee("Alice", 101);
System.out.println(emp1.name); // Outputs: Alice
}
}
Methods & Overloading
Methods define the behavior of an object.
Method Overloading
Multiple methods can have the same name if they have different parameters (different type or number of parameters).
public class MathUtil {
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; }
}
Varargs (Variable Arguments)
Allows a method to accept zero or multiple arguments of the same type.
static void printNames(String... names) {
for (String name : names) {
System.out.print(name + " ");
}
}
// Calling: printNames("John", "Doe", "Smith");
Inheritance Deep Dive
Inheritance promotes code reusability by acquiring properties of a parent class.
The super Keyword
Used to call the parent class's constructor or methods.
class Animal {
Animal() { System.out.println("Animal created"); }
}
class Dog extends Animal {
Dog() {
super(); // Calls parent constructor
System.out.println("Dog created");
}
}
Note: Java does NOT support multiple inheritance with classes (a class cannot extend multiple classes) to avoid the Diamond Problem. It is achieved through interfaces.
Polymorphism
Polymorphism means "many forms". It allows performing a single action in different ways.
Compile-time vs Run-time
- Compile-time (Static): Method Overloading.
- Run-time (Dynamic): Method Overriding.
Method Overriding
When a subclass provides a specific implementation of a method already provided by its parent.
class Animal {
void sound() { System.out.println("Generic sound"); }
}
class Cat extends Animal {
@Override
void sound() { System.out.println("Meow"); }
}
// Upcasting: Reference is parent, object is child
Animal myAnimal = new Cat();
myAnimal.sound(); // Outputs "Meow" (Runtime Polymorphism)
Abstraction: Abstract Classes vs Interfaces
Abstraction hides complex implementation details and shows only the essential features.
Abstract Classes (abstract)
Can have both abstract (without body) and regular methods. Cannot be instantiated.
abstract class Shape {
abstract void draw(); // Abstract method
void print() { System.out.println("Printing shape..."); } // Concrete method
}
Interfaces (interface)
A completely "abstract class" that is used to group related methods with empty bodies. A class implements it using implements.
interface Animal {
void animalSound(); // interface method (implicitly public and abstract)
// Java 8+ allows default and static methods in interfaces
default void sleep() {
System.out.println("Zzz");
}
}
Encapsulation & Access Modifiers
Encapsulation binds data and code together, keeping it safe from outside interference.
Access Modifiers
| Modifier | Class | Package | Subclass | World |
|---|---|---|---|---|
public | Yes | Yes | Yes | Yes |
protected | Yes | Yes | Yes | No |
| default (no keyword) | Yes | Yes | No | No |
private | Yes | No | No | No |
Getters and Setters
public class Person {
private String name; // Hidden data
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
Exception Handling Deep Dive
Exceptions disrupt the normal flow of the program. Handling them prevents application crashes.
Checked vs Unchecked
- Checked Exceptions: Checked at compile-time (e.g.,
IOException,SQLException). Must be handled. - Unchecked Exceptions: Checked at runtime (e.g.,
NullPointerException,ArithmeticException).
try-catch-finally and throws
public void readFile() throws IOException { // declares exception
try {
int data = 10 / 0; // ArithmeticException
} catch (ArithmeticException e) {
System.out.println("Cannot divide by zero!");
} finally {
System.out.println("This block ALWAYS executes (cleanup).");
}
}
Custom Exceptions
class InvalidAgeException extends Exception {
public InvalidAgeException(String msg) { super(msg); }
}
// throw new InvalidAgeException("Age must be 18+");
Collections Framework
A unified architecture for representing and manipulating collections of objects.
The Hierarchy Overview
- List: Ordered, allows duplicates. (
ArrayList: fast random access.LinkedList: fast insertions/deletions). - Set: Unordered, NO duplicates. (
HashSet: fast.TreeSet: sorted). - Map: Key-Value pairs. Keys are unique. (
HashMap: fast.TreeMap: sorted keys).
Using Lists and Maps
import java.util.*;
// List Example
List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
// Map Example
Map<String, Integer> map = new HashMap<>();
map.put("Alice", 25);
map.put("Bob", 30);
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println(entry.getKey() + " is " + entry.getValue());
}
Multithreading & Concurrency
Allows concurrent execution of two or more parts of a program.
Thread Class vs Runnable Interface
It is generally better to implement Runnable because Java doesn't support multiple inheritance of classes.
class MyTask implements Runnable {
public void run() {
System.out.println("Thread is running...");
}
}
// Execution
Thread t1 = new Thread(new MyTask());
t1.start(); // Always call start(), not run()
Synchronization
When multiple threads access shared resources, data inconsistency can occur. The synchronized keyword ensures only one thread accesses the method/block at a time.
public synchronized void incrementCount() {
count++; // Thread-safe
}
File I/O Streams
Java uses Streams to handle I/O operations rapidly.
Byte vs Character Streams
- Byte Streams: Handle raw binary data (e.g.,
FileInputStream). - Character Streams: Handle text data natively (e.g.,
FileReader,FileWriter).
Modern File Reading (Java NIO)
Java NIO (New I/O) provides a more efficient way to deal with files using the Files and Paths classes.
import java.nio.file.*;
// Writing to a file
Path path = Paths.get("data.txt");
Files.writeString(path, "Hello Java NIO!");
// Reading from a file
String content = Files.readString(path);
System.out.println(content);
Generics
Generics enable types (classes and interfaces) to be parameters when defining classes, interfaces and methods. They provide strong compile-time type checking.
Generic Classes
class Box<T> {
private T item;
public void set(T item) { this.item = item; }
public T get() { return item; }
}
Box<String> stringBox = new Box<>();
stringBox.set("Hello"); // Type-safe
Generic Methods
public <E> void printArray(E[] array) {
for(E element : array) {
System.out.print(element + " ");
}
}
Java 8+ Advanced Features
Java 8 brought functional programming concepts to Java, dramatically changing how code is written.
Lambda Expressions
Provides a clear and concise way to represent one method interface using an expression.
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
// Using lambda
names.forEach(name -> System.out.println(name));
Streams API
Used to process collections of objects in a declarative manner (filter, map, reduce).
List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5, 6);
int sumOfEvens = nums.stream()
.filter(n -> n % 2 == 0) // Keep evens
.mapToInt(n -> n) // Convert to int stream
.sum(); // Add them up
System.out.println(sumOfEvens); // Outputs: 12
Optional Class
A container object which may or may not contain a non-null value. Prevents NullPointerException.
Optional<String> opt = Optional.ofNullable(null);
System.out.println(opt.orElse("Default Value")); // Outputs: Default Value