Welcome to IT Wallah – Learn Coding, Modern Technology, Computer Troubleshooting, and Explore Helpful Tech Blogs.

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.

  1. Download JDK from Oracle's official site.
  2. Run the installer and follow instructions.
  3. Set JAVA_HOME system variable.
  4. 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
TypeSizeDescription
byte1 byteStores whole numbers from -128 to 127
short2 bytesStores whole numbers from -32,768 to 32,767
int4 bytesStores whole numbers from -2,147,483,648 to 2,147,483,647
long8 bytesStores huge whole numbers. Suffix with L
float4 bytesFractional numbers (6-7 decimal digits). Suffix with f
double8 bytesFractional numbers (15 decimal digits). Suffix with d
boolean1 bitStores true or false values
char2 bytesStores 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., int to double).
  • Narrowing (Manual): Larger type to smaller type (e.g., double to int). 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 String
  • nextInt() - Reads an int
  • nextDouble() - Reads a double
  • nextBoolean() - 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 is true.
  • else if - Tests a new condition if the previous if was false.
  • else - Executes if all preceding conditions are false.
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
  • break completely terminates the loop.
  • continue skips 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
ModifierClassPackageSubclassWorld
publicYesYesYesYes
protectedYesYesYesNo
default (no keyword)YesYesNoNo
privateYesNoNoNo
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