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.