40 Intermediate Java Tutorials

40 Intermediate Java Tutorials

1. Enhanced For Loop

The enhanced for loop simplifies iteration over arrays and collections.

Example: Iterating over an array.

public class EnhancedForLoop {
public static void main(String[] args) {
int[] numbers = {1, 2, 3, 4, 5};
for (int num : numbers) {
System.out.println(num);
}
}
}

1
2
3
4
5

Note: Use for collections or arrays when you don't need the index.

2. ArrayList Operations

ArrayList supports dynamic operations like adding, removing, and searching.

Example: Manipulating an ArrayList.

import java.util.ArrayList;
public class ArrayListOps {
public static void main(String[] args) {
ArrayList names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.remove("Alice");
System.out.println("Contains Bob? " + names.contains("Bob"));
System.out.println("List: " + names);
}
}

Contains Bob? true
List: [Bob]

Note: Use methods like add(), remove(), and contains() for flexible lists.

3. HashMap Operations

HashMap stores key-value pairs with methods for adding, retrieving, and removing entries.

Example: Using a HashMap.

import java.util.HashMap;
public class HashMapOps {
public static void main(String[] args) {
HashMap ages = new HashMap<>();
ages.put("Alice", 25);
ages.put("Bob", 30);
System.out.println("Alice's age: " + ages.get("Alice"));
ages.remove("Bob");
System.out.println("Map: " + ages);
}
}

Alice's age: 25
Map: {Alice=25}

Note: Keys must be unique. Use getOrDefault() to avoid nulls.

4. HashSet Basics

HashSet stores unique elements with no specific order.

Example: Using a HashSet.

import java.util.HashSet;
public class HashSetDemo {
public static void main(String[] args) {
HashSet fruits = new HashSet<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Apple"); // Duplicate ignored
System.out.println("Fruits: " + fruits);
}
}

Fruits: [Apple, Banana]

Note: HashSet ensures uniqueness. Use for fast lookup of unique items.

5. Generics

Generics add type safety to collections, ensuring only specific types are used.

Example: Generic ArrayList.

import java.util.ArrayList;
public class GenericsDemo {
public static void main(String[] args) {
ArrayList numbers = new ArrayList<>();
numbers.add(10);
numbers.add(20);
System.out.println("Sum: " + (numbers.get(0) + numbers.get(1)));
}
}

Sum: 30

Note: Specify types in angle brackets (<Type>). Avoids casting.

6. Advanced Exception Handling

Multiple catch blocks and custom exceptions handle errors precisely.

Example: Multiple catch blocks.

public class AdvancedExceptions {
public static void main(String[] args) {
try {
int[] arr = new int[2];
arr[5] = 10; // ArrayIndexOutOfBoundsException
int x = 10 / 0; // ArithmeticException
} catch (ArithmeticException e) {
System.out.println("Math error: " + e.getMessage());
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Array error: " + e.getMessage());
}
}
}

Array error: Index 5 out of bounds for length 2

Note: Catch specific exceptions first, then general ones.

7. File Input/Output Streams

FileInputStream and FileOutputStream handle binary file operations.

Example: Copying a file.

import java.io.FileInputStream;
import java.io.FileOutputStream;
public class FileStreams {
public static void main(String[] args) {
try {
FileInputStream in = new FileInputStream("input.txt");
FileOutputStream out = new FileOutputStream("output.txt");
int byteRead;
while ((byteRead = in.read()) != -1) {
out.write(byteRead);
}
in.close();
out.close();
System.out.println("File copied.");
} catch (Exception e) {
System.out.println("Error: " + e.getMessage());
}
}
}

File copied.

Note: Use for binary files. Close streams to free resources.

8. Polymorphism

Polymorphism allows subclasses to be treated as their parent type.

Example: Using polymorphism.

class Animal {
void sound() {
System.out.println("Generic sound");
}
}
class Dog extends Animal {
void sound() {
System.out.println("Bark");
}
}
public class Polymorphism {
public static void main(String[] args) {
Animal animal = new Dog();
animal.sound();
}
}

Bark

Note: The actual object type determines the method called.

9. Abstract Class Usage

Abstract classes provide a base with some implementation and abstract methods.

Example: Abstract class with implementation.

abstract class Vehicle {
void start() {
System.out.println("Vehicle starting");
}
abstract void drive();
}
class Car extends Vehicle {
void drive() {
System.out.println("Car driving");
}
public static void main(String[] args) {
Car car = new Car();
car.start();
car.drive();
}
}

Vehicle starting
Car driving

Note: Abstract classes can have both concrete and abstract methods.

10. Interface Implementation

Interfaces define contracts that classes can implement for multiple behaviors.

Example: Multiple interface implementation.

interface Flyable {
void fly();
}
interface Drivable {
void drive();
}
class FlyingCar implements Flyable, Drivable {
public void fly() {
System.out.println("Flying");
}
public void drive() {
System.out.println("Driving");
}
public static void main(String[] args) {
FlyingCar fc = new FlyingCar();
fc.fly();
fc.drive();
}
}

Flying
Driving

Note: A class can implement multiple interfaces.

11. Lambda Expressions

Lambda expressions provide a concise way to implement functional interfaces.

Example: Using a lambda expression.

interface Operation {
int operate(int a, int b);
}
public class LambdaDemo {
public static void main(String[] args) {
Operation add = (a, b) -> a + b;
System.out.println("Sum: " + add.operate(5, 3));
}
}

Sum: 8

Note: Lambda syntax is (params) -> expression. Used with functional interfaces.

12. Stream API Basics

The Stream API processes collections in a functional style.

Example: Filtering a list with streams.

import java.util.Arrays;
import java.util.List;
public class StreamBasics {
public static void main(String[] args) {
List numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.stream()
.filter(n -> n % 2 == 0)
.forEach(System.out::println);
}
}

2
4

Note: Streams don't modify the original collection. Use forEach to output results.

13. Method References

Method references simplify lambda expressions by referring to existing methods.

Example: Using method references.

import java.util.Arrays;
import java.util.List;
public class MethodReferences {
public static void main(String[] args) {
List names = Arrays.asList("Alice", "Bob");
names.forEach(System.out::println);
}
}

Alice
Bob

Note: Use :: for method references. Replaces simple lambdas.

14. Optional Class

Optional handles nullable values to avoid NullPointerException.

Example: Using Optional.

import java.util.Optional;
public class OptionalDemo {
public static void main(String[] args) {
Optional name = Optional.ofNullable("Alice");
System.out.println(name.orElse("No name"));
}
}

Alice

Note: Use Optional.of() for non-null, ofNullable() for possible nulls.

15. Multithreading Basics

Threads allow concurrent execution of tasks.

Example: Creating a thread.

public class ThreadDemo extends Thread {
public void run() {
System.out.println("Thread running: " + Thread.currentThread().getName());
}
public static void main(String[] args) {
ThreadDemo thread = new ThreadDemo();
thread.start();
}
}

Thread running: Thread-0

Note: Extend Thread or implement Runnable. Use start() to run.

16. Synchronized Methods

Synchronized methods prevent concurrent access issues in multithreading.

Example: Synchronized counter.

public class SynchronizedDemo {
static int count = 0;
synchronized void increment() {
count++;
}
public static void main(String[] args) throws InterruptedException {
SynchronizedDemo demo = new SynchronizedDemo();
Thread t1 = new Thread(() -> { for (int i = 0; i < 1000; i++) demo.increment(); });
Thread t2 = new Thread(() -> { for (int i = 0; i < 1000; i++) demo.increment(); });
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Count: " + count);
}
}

Count: 2000

Note: Synchronized ensures thread-safe operations.

17. Thread Pools

Thread pools manage multiple threads efficiently using ExecutorService.

Example: Using a thread pool.

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolDemo {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2);
for (int i = 1; i <= 3; i++) {
int taskId = i;
executor.submit(() -> System.out.println("Task " + taskId + " by " + Thread.currentThread().getName()));
}
executor.shutdown();
}
}

Task 1 by pool-1-thread-1
Task 2 by pool-1-thread-2
Task 3 by pool-1-thread-1

Note: Use shutdown() to close the pool after tasks complete.

18. DateTime API

The DateTime API (java.time) handles dates and times effectively.

Example: Formatting a date.

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class DateTimeDemo {
public static void main(String[] args) {
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm");
System.out.println("Formatted: " + now.format(formatter));
}
}

Formatted: [current date and time]

Note: Use DateTimeFormatter for custom date formats.

19. Regular Expressions

Regular expressions validate and manipulate strings using patterns.

Example: Validating an email.

import java.util.regex.Pattern;
public class RegexDemo {
public static void main(String[] args) {
String email = "[email protected]";
String regex = "^[\\w-\\.]+@([\\w-]+\\.)+[\\w-]{2,4}$";
boolean isValid = Pattern.matches(regex, email);
System.out.println("Valid email? " + isValid);
}
}

Valid email? true

Note: Use Pattern and Matcher for complex regex operations.

20. Simple To-Do List Project

A to-do list project combines ArrayList, user input, and loops.

Example: Managing tasks.

import java.util.ArrayList;
import java.util.Scanner;
public class ToDoList {
public static void main(String[] args) {
ArrayList tasks = new ArrayList<>();
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println("1. Add 2. Remove 3. View 4. Exit");
int choice = scanner.nextInt();
scanner.nextLine();
if (choice == 1) {
System.out.println("Enter task: ");
tasks.add(scanner.nextLine());
} else if (choice == 2 && !tasks.isEmpty()) {
System.out.println("Enter task index: ");
tasks.remove(scanner.nextInt());
} else if (choice == 3) {
System.out.println("Tasks: " + tasks);
} else if (choice == 4) {
break;
}
}
}
}

1. Add 2. Remove 3. View 4. Exit
1
Enter task: Study
1. Add 2. Remove 3. View 4. Exit
3
Tasks: [Study]

Note: Uses ArrayList for dynamic task management.

21. Enums with Methods

Enums can include methods and fields for additional functionality.

Example: Enum with a method.

public class EnumMethods {
enum Level {
LOW(1), MEDIUM(2), HIGH(3);
private int value;
Level(int value) {
this.value = value;
}
int getValue() {
return value;
}
}
public static void main(String[] args) {
System.out.println("Level: " + Level.MEDIUM.getValue());
}
}

Level: 2

Note: Enums can have constructors and methods for logic.

22. Inner Classes

Inner classes are defined inside another class and can access its members.

Example: Using an inner class.

public class InnerClassDemo {
private String outer = "Outer";
class Inner {
void display() {
System.out.println("From inner: " + outer);
}
}
public static void main(String[] args) {
InnerClassDemo outer = new InnerClassDemo();
InnerClassDemo.Inner inner = outer.new Inner();
inner.display();
}
}

From inner: Outer

Note: Inner classes are useful for logical grouping.

23. Anonymous Classes

Anonymous classes implement interfaces or extend classes without a name.

Example: Anonymous class for an interface.

interface Action {
void perform();
}
public class AnonymousClass {
public static void main(String[] args) {
Action action = new Action() {
public void perform() {
System.out.println("Action performed");
}
};
action.perform();
}
}

Action performed

Note: Use for one-time implementations, often with interfaces.

24. Static Nested Classes

Static nested classes are static classes defined inside another class.

Example: Using a static nested class.

public class StaticNested {
static class Nested {
void display() {
System.out.println("Static nested class");
}
}
public static void main(String[] args) {
StaticNested.Nested nested = new StaticNested.Nested();
nested.display();
}
}

Static nested class

Note: Static nested classes don't require an outer class instance.

25. Collections Sorting

Collections.sort() sorts lists, with custom comparators for objects.

Example: Sorting a list.

import java.util.ArrayList;
import java.util.Collections;
public class SortDemo {
public static void main(String[] args) {
ArrayList numbers = new ArrayList<>();
numbers.add(5);
numbers.add(2);
numbers.add(8);
Collections.sort(numbers);
System.out.println("Sorted: " + numbers);
}
}

Sorted: [2, 5, 8]

Note: Use Comparator for custom sorting of objects.

26. Custom Exceptions

Custom exceptions extend Exception for specific error handling.

Example: Creating a custom exception.

class CustomException extends Exception {
CustomException(String message) {
super(message);
}
}
public class CustomExceptionDemo {
public static void main(String[] args) {
try {
throw new CustomException("Custom error");
} catch (CustomException e) {
System.out.println("Caught: " + e.getMessage());
}
}
}

Caught: Custom error

Note: Extend Exception or RuntimeException for custom errors.

27. Serialization

Serialization converts objects to a byte stream for storage or transmission.

Example: Serializing an object.

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
class Person implements Serializable {
String name;
Person(String name) {
this.name = name;
}
}
public class SerializationDemo {
public static void main(String[] args) {
try {
Person person = new Person("Alice");
FileOutputStream file = new FileOutputStream("person.ser");
ObjectOutputStream out = new ObjectOutputStream(file);
out.writeObject(person);
out.close();
file.close();
System.out.println("Object serialized");
} catch (Exception e) {
System.out.println("Error: " + e.getMessage());
}
}
}

Object serialized

Note: Classes must implement Serializable. Close streams properly.

28. Deserialization

Deserialization converts a byte stream back into an object.

Example: Deserializing an object.

import java.io.FileInputStream;
import java.io.ObjectInputStream;
import java.io.Serializable;
class Person implements Serializable {
String name;
Person(String name) {
this.name = name;
}
}
public class DeserializationDemo {
public static void main(String[] args) {
try {
FileInputStream file = new FileInputStream("person.ser");
ObjectInputStream in = new ObjectInputStream(file);
Person person = (Person) in.readObject();
in.close();
file.close();
System.out.println("Deserialized: " + person.name);
} catch (Exception e) {
System.out.println("Error: " + e.getMessage());
}
}
}

Deserialized: Alice

Note: Match the class structure during deserialization.

29. File Path Handling

The Path and Files classes handle file system operations.

Example: Checking if a file exists.

import java.nio.file.Files;
import java.nio.file.Path;
public class PathDemo {
public static void main(String[] args) {
Path path = Path.of("example.txt");
System.out.println("Exists? " + Files.exists(path));
}
}

Exists? false

Note: Use java.nio.file for modern file operations.

30. Functional Interfaces

Functional interfaces have a single abstract method, used with lambdas.

Example: Custom functional interface.

@FunctionalInterface
interface Calculator {
int calc(int x, int y);
}
public class FunctionalInterfaceDemo {
public static void main(String[] args) {
Calculator multiply = (x, y) -> x * y;
System.out.println("Product: " + multiply.calc(4, 5));
}
}

Product: 20

Note: Use @FunctionalInterface to enforce one abstract method.

31. Stream Filtering

Stream filtering selects elements based on a condition.

Example: Filtering names.

import java.util.Arrays;
import java.util.List;
public class StreamFilter {
public static void main(String[] args) {
List names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream()
.filter(name -> name.startsWith("A"))
.forEach(System.out::println);
}
}

Alice

Note: Filter takes a Predicate to evaluate each element.

32. Stream Mapping

Stream mapping transforms elements using a function.

Example: Mapping numbers to squares.

import java.util.Arrays;
import java.util.List;
public class StreamMap {
public static void main(String[] args) {
List numbers = Arrays.asList(1, 2, 3);
numbers.stream()
.map(n -> n * n)
.forEach(System.out::println);
}
}

1
4
9

Note: Map applies a function to each element, producing a new stream.

33. Parallel Streams

Parallel streams process data concurrently for performance.

Example: Using a parallel stream.

import java.util.Arrays;
import java.util.List;
public class ParallelStreamDemo {
public static void main(String[] args) {
List numbers = Arrays.asList(1, 2, 3, 4, 5);
numbers.parallelStream()
.forEach(n -> System.out.println(n + " by " + Thread.currentThread().getName()));
}
}

[Varies, e.g., 1 by ForkJoinPool.commonPool-worker-1]

Note: Parallel streams use multiple threads but may not preserve order.

34. Try-with-Resources

Try-with-resources automatically closes resources like files.

Example: Reading a file with try-with-resources.

import java.io.BufferedReader;
import java.io.FileReader;
public class TryWithResources {
public static void main(String[] args) {
try (BufferedReader reader = new BufferedReader(new FileReader("input.txt"))) {
System.out.println("First line: " + reader.readLine());
} catch (Exception e) {
System.out.println("Error: " + e.getMessage());
}
}
}

First line: [File content]

Note: Resources must implement AutoCloseable.

35. Annotations

Annotations provide metadata for code, like @Override.

Example: Using @Override.

class Parent {
void show() {}
}
public class AnnotationDemo extends Parent {
@Override
void show() {
System.out.println("Overridden method");
}
public static void main(String[] args) {
AnnotationDemo demo = new AnnotationDemo();
demo.show();
}
}

Overridden method

Note: Annotations like @Override ensure correct method overriding.

36. Reflection API

Reflection allows inspection and modification of classes at runtime.

Example: Inspecting a class.

import java.lang.reflect.Method;
public class ReflectionDemo {
public void exampleMethod() {}
public static void main(String[] args) {
try {
Class cls = ReflectionDemo.class;
Method[] methods = cls.getMethods();
for (Method m : methods) {
System.out.println("Method: " + m.getName());
}
} catch (Exception e) {
System.out.println("Error: " + e.getMessage());
}
}
}

Method: exampleMethod
[Other methods]

Note: Use reflection sparingly due to performance overhead.

37. Singleton Pattern

The Singleton pattern ensures one instance of a class.

Example: Implementing Singleton.

public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();
System.out.println("Same instance? " + (s1 == s2));
}
}

Same instance? true

Note: Use private constructor and static method for Singleton.

38. Factory Pattern

The Factory pattern creates objects without specifying the exact class.

Example: Factory for shapes.

interface Shape {
void draw();
}
class Circle implements Shape {
public void draw() {
System.out.println("Drawing Circle");
}
}
class Rectangle implements Shape {
public void draw() {
System.out.println("Drawing Rectangle");
}
}
public class ShapeFactory {
public static Shape getShape(String type) {
if (type.equalsIgnoreCase("circle")) return new Circle();
return new Rectangle();
}
public static void main(String[] args) {
Shape shape = ShapeFactory.getShape("circle");
shape.draw();
}
}

Drawing Circle

Note: Factory methods centralize object creation logic.

39. Builder Pattern

The Builder pattern constructs complex objects step-by-step.

Example: Building a product.

class Product {
private String part1, part2;
private Product(Builder builder) {
this.part1 = builder.part1;
this.part2 = builder.part2;
}
static class Builder {
private String part1, part2;
Builder setPart1(String part1) {
this.part1 = part1;
return this;
}
Builder setPart2(String part2) {
this.part2 = part2;
return this;
}
Product build() {
return new Product(this);
}
}
@Override
public String toString() {
return "Product [part1=" + part1 + ", part2=" + part2 + "]";
}
}
public class BuilderDemo {
public static void main(String[] args) {
Product product = new Product.Builder()
.setPart1("A")
.setPart2("B")
.build();
System.out.println(product);
}
}

Product [part1=A, part2=B]

Note: Builder improves readability for complex object creation.

40. Contact Management System

A contact management system uses collections and file handling for a practical project.

Example: Managing contacts.

import java.util.HashMap;
import java.util.Scanner;
public class ContactManager {
private static HashMap contacts = new HashMap<>();
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
while (true) {
System.out.println("1. Add 2. View 3. Remove 4. Exit");
int choice = scanner.nextInt();
scanner.nextLine();
if (choice == 1) {
System.out.println("Enter name: ");
String name = scanner.nextLine();
System.out.println("Enter phone: ");
String phone = scanner.nextLine();
contacts.put(name, phone);
} else if (choice == 2) {
System.out.println("Contacts: " + contacts);
} else if (choice == 3) {
System.out.println("Enter name to remove: ");
contacts.remove(scanner.nextLine());
} else if (choice == 4) {
break;
}
}
}
}

1. Add 2. View 3. Remove 4. Exit
1
Enter name: Alice
Enter phone: 1234567890
1. Add 2. View 3. Remove 4. Exit
2
Contacts: {Alice=1234567890}

Note: Uses HashMap for key-value storage of contacts.

Macro Nepal Helper