Java Tutorial

Introduction to Java

Welcome to the comprehensive Java tutorial! Java is a high-level, class-based, object-oriented programming language that is designed to have as few implementation dependencies as possible. It is a general-purpose programming language intended to let application developers write once, run anywhere (WORA), meaning that compiled Java code can run on all platforms that support Java without the need for recompilation.

What is Java?

Java was originally developed by James Gosling at Sun Microsystems (now owned by Oracle) and released in 1995. It's one of the most popular programming languages in the world, widely used for:

JVM, JRE, and JDK

Setting Up Your Development Environment

To start coding in Java, you need to install the JDK and choose an Integrated Development Environment (IDE).

  1. **Download JDK:** Go to the official Oracle Java SE Downloads page or Adoptium (OpenJDK) and download the latest LTS version of the JDK for your operating system.
  2. **Installation:** Follow the installer instructions. Set up the `JAVA_HOME` environment variable and add the JDK's `bin` directory to your system's `PATH`.
  3. **Verify Installation:** Open your command prompt or terminal and type `java -version` and `javac -version`. You should see the installed Java version.
  4. **Choose an IDE:**
    • **IntelliJ IDEA:** A powerful and popular IDE for Java development (Community Edition is free). Download from jetbrains.com/idea.
    • **Eclipse:** Another widely used open-source IDE for Java. Download from eclipse.org/downloads.
    • **VS Code:** With Java extensions, it can serve as a lightweight IDE.

Your First Java Program: "Hello, World!"

Let's write the classic "Hello, World!" program. Save this code as `HelloWorld.java`. To compile: `javac HelloWorld.java` To run: `java HelloWorld`


// This is a single-line comment in Java
/* This is a
   multi-line comment */

public class HelloWorld { // Declares a public class named HelloWorld
    public static void main(String[] args) { // The main method, entry point of the application
        // Prints "Hello, World!" to the console
        System.out.println("Hello, World!");
    }
}
            

Explanation:


Java Fundamentals

Variables and Data Types

Variables are containers for storing data values. In Java, variables are strongly typed, meaning you must declare their data type explicitly.


// Primitive Data Types
int age = 30;          // Integer (whole numbers)
double price = 19.99;  // Floating-point number (decimal numbers)
char initial = 'J';    // Character (single character)
boolean isActive = true; // Boolean (true or false)
long bigNumber = 123456789012345L; // Long integer
float temperature = 25.5f; // Single-precision floating-point

// Non-Primitive Data Types (Reference Types)
String name = "John Doe"; // String (sequence of characters, an object)
int[] numbers = {1, 2, 3}; // Array (an object)

System.out.println("Age: " + age);
System.out.println("Name: " + name);
System.out.println("First number in array: " + numbers[0]);
            

Java has two main categories of data types: **Primitive** (byte, short, int, long, float, double, boolean, char) and **Non-Primitive/Reference** (String, Arrays, Classes, Interfaces).

Operators

Operators are symbols that perform operations on variables and values.


int a = 10;
int b = 5;

System.out.println(a + b);   // Addition: 15
System.out.println(a - b);   // Subtraction: 5
System.out.println(a * b);   // Multiplication: 50
System.out.println(a / b);   // Division: 2
System.out.println(a % b);   // Modulus: 0

boolean isGreater = (a > b); // Comparison: true
boolean logicalAnd = (a > 0 && b > 0); // Logical AND: true

String status = (a > 10) ? "Greater" : "Not Greater";
System.out.println(status); // Output: Not Greater
            

Control Flow

Control flow statements allow you to execute different blocks of code based on conditions or to repeat code.

If-Elseif-Else Statements


int score = 75;
if (score >= 90) {
    System.out.println("Grade: A");
} else if (score >= 80) {
    System.out.println("Grade: B");
} else if (score >= 70) {
    System.out.println("Grade: C");
} else {
    System.out.println("Grade: D or F");
}
            

Switch Statements


int dayOfWeek = 3; // 1=Monday, 7=Sunday
switch (dayOfWeek) {
    case 1:
        System.out.println("Monday");
        break;
    case 5:
        System.out.println("Friday");
        break;
    case 7:
        System.out.println("Sunday");
        break;
    default:
        System.out.println("Another weekday");
}
            

Loops (for, while, do-while, for-each)


// For loop
for (int i = 0; i < 5; i++) {
    System.out.println("For loop iteration: " + i);
}

// While loop
int count = 0;
while (count < 3) {
    System.out.println("While loop iteration: " + count);
    count++;
}

// For-each loop (enhanced for loop for arrays and collections)
String[] fruits = {"Apple", "Banana", "Cherry"};
for (String fruit : fruits) {
    System.out.println("Fruit: " + fruit);
}
            

Methods

Methods (also known as functions) are blocks of code that perform a specific task. They help organize code and promote reusability.


public class MyMath {
    // Method that returns a value
    public int add(int num1, int num2) {
        return num1 + num2;
    }

    // Method that doesn't return a value (void)
    public void greet(String name) {
        System.out.println("Hello, " + name + "!");
    }

    public static void main(String[] args) {
        MyMath calculator = new MyMath();
        int sum = calculator.add(10, 20);
        System.out.println("Sum: " + sum); // Output: Sum: 30
        calculator.greet("Alice"); // Output: Hello, Alice!
    }
}
            

Object-Oriented Programming (OOP) in Java

Java is a pure object-oriented programming language. OOP is a paradigm based on the concept of "objects," which can contain data and code. The four main pillars of OOP are Encapsulation, Inheritance, Polymorphism, and Abstraction.

Classes and Objects

A **class** is a blueprint or a template for creating objects. An **object** is an instance of a class.


public class Car {
    // Properties (instance variables)
    String model;
    String color;
    int year;

    // Constructor (special method to initialize objects)
    public Car(String model, String color, int year) {
        this.model = model;
        this.color = color;
        this.year = year;
    }

    // Method (behavior)
    public void drive() {
        System.out.println(color + " " + model + " is driving.");
    }

    public static void main(String[] args) {
        // Creating objects (instances of the Car class)
        Car myCar = new Car("Toyota", "Blue", 2023);
        System.out.println("My car is a " + myCar.color + " " + myCar.model + " from " + myCar.year + ".");
        myCar.drive(); // Output: Blue Toyota is driving.
    }
}
            

Inheritance

Inheritance allows a class (subclass/derived class) to inherit properties and methods from another class (superclass/base class). This promotes code reusability.


class Vehicle { // Superclass
    String brand;

    public Vehicle(String brand) {
        this.brand = brand;
    }

    public void honk() {
        System.out.println("Tuut, tuut!");
    }
}

class Bicycle extends Vehicle { // Subclass inheriting from Vehicle
    int numberOfGears;

    public Bicycle(String brand, int gears) {
        super(brand); // Call superclass constructor
        this.numberOfGears = gears;
    }

    public static void main(String[] args) {
        Bicycle myBicycle = new Bicycle("Giant", 21);
        System.out.println("My bicycle is a " + myBicycle.brand + " with " + myBicycle.numberOfGears + " gears.");
        myBicycle.honk(); // Inherited method
    }
}
            

Polymorphism

Polymorphism means "many forms." It allows objects of different classes to be treated as objects of a common base class or interface. This is achieved through method overriding and method overloading.


class Animal {
    public void makeSound() { // Method to be overridden
        System.out.println("The animal makes a sound");
    }
}

class Dog extends Animal {
    @Override // Annotation indicating method overriding
    public void makeSound() {
        System.out.println("The dog barks");
    }
}

class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("The cat meows");
    }
}

public class PolyExample {
    public static void main(String[] args) {
        Animal myAnimal = new Animal(); // Animal object
        Animal myDog = new Dog();    // Dog object treated as Animal
        Animal myCat = new Cat();    // Cat object treated as Animal

        myAnimal.makeSound(); // Output: The animal makes a sound
        myDog.makeSound();    // Output: The dog barks
        myCat.makeSound();    // Output: The cat meows
    }
}
            

Encapsulation

Encapsulation is the bundling of data (attributes) and methods that operate on the data into a single unit (class), and restricting direct access to some of an object's components. This is achieved using access modifiers (`public`, `private`, `protected`, `default`).


public class BankAccount {
    private double balance; // Private field, not directly accessible from outside

    public BankAccount(double initialBalance) {
        this.balance = initialBalance;
    }

    // Public method to modify balance (setter)
    public void deposit(double amount) {
        if (amount > 0) {
            this.balance += amount;
            System.out.println("Deposited: " + amount + ". New balance: " + this.balance);
        } else {
            System.out.println("Deposit amount must be positive.");
        }
    }

    // Public method to access balance (getter)
    public double getBalance() {
        return balance;
    }

    public static void main(String[] args) {
        BankAccount account = new BankAccount(1000);
        account.deposit(500); // Access via public method
        System.out.println("Current balance: " + account.getBalance());
        // account.balance = 0; // This would cause a compile-time error due to private access
    }
}
            

Abstraction

Abstraction means hiding the complex implementation details and showing only the essential features of the object. This can be achieved using abstract classes and interfaces.


// Abstract class
abstract class Shape {
    // Abstract method (no body), must be implemented by concrete subclasses
    public abstract double getArea();

    // Regular method
    public void display() {
        System.out.println("This is a shape.");
    }
}

// Concrete class inheriting from abstract class
class Circle extends Shape {
    double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double getArea() { // Must implement the abstract method
        return Math.PI * radius * radius;
    }

    public static void main(String[] args) {
        Circle circle = new Circle(5);
        System.out.println("Area of Circle: " + circle.getArea());
        circle.display(); // Inherited method
        // Shape s = new Shape(); // Cannot instantiate abstract class
    }
}
            

Key Java Features

Exception Handling

Java provides a robust mechanism for handling runtime errors (exceptions) using `try-catch-finally` blocks.


public class ExceptionDemo {
    public static void main(String[] args) {
        try {
            int[] numbers = {1, 2, 3};
            System.out.println(numbers[10]); // This will cause an ArrayIndexOutOfBoundsException
        } catch (ArrayIndexOutOfBoundsException e) {
            System.out.println("Error: Array index out of bounds!");
            System.out.println("Message: " + e.getMessage());
        } catch (ArithmeticException e) {
            System.out.println("Error: Arithmetic operation failed!");
        } finally {
            System.out.println("The 'try catch' block is finished.");
        }

        try {
            int result = 10 / 0; // This will cause an ArithmeticException
        } catch (ArithmeticException e) {
            System.out.println("Error: Division by zero!");
        }
    }
}
            

Java Collections Framework

The Java Collections Framework provides a unified architecture for representing and manipulating collections, allowing them to be manipulated independently of their implementation details.


import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class CollectionsDemo {
    public static void main(String[] args) {
        // ArrayList (ordered, allows duplicates, dynamic array)
        List<String> names = new ArrayList<>();
        names.add("Alice");
        names.add("Bob");
        names.add("Alice");
        System.out.println("ArrayList: " + names); // Output: [Alice, Bob, Alice]

        // HashSet (unordered, no duplicates)
        Set<String> uniqueNames = new HashSet<>();
        uniqueNames.add("Alice");
        uniqueNames.add("Bob");
        uniqueNames.add("Alice"); // Duplicate is ignored
        System.out.println("HashSet: " + uniqueNames); // Output: [Alice, Bob] (order may vary)

        // HashMap (key-value pairs, unordered)
        Map<String, Integer> ages = new HashMap<>();
        ages.put("Alice", 30);
        ages.put("Bob", 25);
        System.out.println("HashMap: " + ages); // Output: {Alice=30, Bob=25} (order may vary)
        System.out.println("Bob's age: " + ages.get("Bob")); // Output: Bob's age: 25
    }
}
            

Multithreading

Java provides built-in support for multithreading, allowing multiple parts of a program to run concurrently. This is crucial for building responsive and high-performance applications.


// Option 1: Extending Thread class
class MyThread extends Thread {
    public void run() {
        for (int i = 0; i < 3; i++) {
            System.out.println("Thread A: " + i);
            try { Thread.sleep(100); } catch (InterruptedException e) {}
        }
    }
}

// Option 2: Implementing Runnable interface (preferred)
class MyRunnable implements Runnable {
    public void run() {
        for (int i = 0; i < 3; i++) {
            System.out.println("Thread B: " + i);
            try { Thread.sleep(150); } catch (InterruptedException e) {}
        }
    }
}

public class MultithreadingDemo {
    public static void main(String[] args) {
        // Create and start Thread A
        MyThread threadA = new MyThread();
        threadA.start(); // Calls run() method

        // Create and start Thread B
        Thread threadB = new Thread(new MyRunnable());
        threadB.start(); // Calls run() method of MyRunnable instance

        for (int i = 0; i < 3; i++) {
            System.out.println("Main Thread: " + i);
            try { Thread.sleep(120); } catch (InterruptedException e) {}
        }
    }
}
            

Lambda Expressions and Streams (Java 8+)

Introduced in Java 8, Lambda Expressions provide a concise way to represent anonymous functions, while the Streams API enables functional-style operations on collections of data.


import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

public class LambdaStreamDemo {
    public static void main(String[] args) {
        List<String> names = new ArrayList<>();
        names.add("Alice");
        names.add("Bob");
        names.add("Charlie");
        names.add("David");

        // Using Lambda Expression with forEach
        System.out.println("Names using Lambda:");
        names.forEach(name -> System.out.println(name));

        // Using Streams to filter and transform data
        List<String> filteredNames = names.stream()
                                         .filter(name -> name.startsWith("A")) // Filter names starting with 'A'
                                         .map(name -> name.toUpperCase())      // Convert to uppercase
                                         .collect(Collectors.toList());        // Collect into a new List

        System.out.println("Filtered and Uppercased Names: " + filteredNames); // Output: [ALICE]

        // Stream operations for aggregation
        int sumOfLengths = names.stream()
                               .mapToInt(name -> name.length()) // Map to integer stream (lengths)
                               .sum();                         // Sum all lengths
        System.out.println("Sum of name lengths: " + sumOfLengths); // Output: 22 (5+3+7+7)
    }
}
            

Conclusion and Next Steps

This tutorial has provided a solid introduction to Java programming, covering its core concepts, OOP principles, and some powerful modern features. Java's robustness, scalability, and vast ecosystem make it a cornerstone of enterprise and Android development. To further your Java journey:

Happy coding!