Skip to content

Java Advanced Guide

Mattscreative edited this page Mar 9, 2026 · 1 revision

Java Advanced Guide - Modern Java Features

Table of Contents


Introduction

This guide covers advanced Java features that will take your coding to the next level. These features were introduced in Java 8 and later, making Java more powerful and expressive.


Collections Framework

The Collections framework provides data structures to store and manipulate groups of objects.

List Interface

An ordered collection (sequence) that allows duplicates.

import java.util.*;

// ArrayList - dynamic array
List<String> names = new ArrayList<>();
names.add("Alice");
names.add("Bob");
names.add("Charlie");
names.add("Alice");  // Duplicates allowed

System.out.println(names.get(0));  // Alice
System.out.println(names.size());   // 4

// LinkedList - linked list
List<Integer> numbers = new LinkedList<>();
numbers.add(1);
numbers.add(2);
numbers.add(3);

// Loop through list
for (String name : names) {
    System.out.println(name);
}

Set Interface

Collection that doesn't allow duplicates.

import java.util.*;

// HashSet - no specific order
Set<String> fruits = new HashSet<>();
fruits.add("Apple");
fruits.add("Banana");
fruits.add("Apple");  // Won't be added (duplicate)
System.out.println(fruits.size());  // 2

// TreeSet - sorted order
Set<Integer> numbers = new TreeSet<>();
numbers.add(5);
numbers.add(2);
numbers.add(8);
numbers.add(1);
System.out.println(numbers);  // [1, 2, 5, 8]

Map Interface

Stores key-value pairs.

import java.util.*;

// HashMap - key-value storage
Map<String, Integer> ages = new HashMap<>();
ages.put("Alice", 25);
ages.put("Bob", 30);
ages.put("Charlie", 35);

System.out.println(ages.get("Alice"));  // 25
System.out.println(ages.containsKey("Bob"));  // true

// Iterate over map
for (Map.Entry<String, Integer> entry : ages.entrySet()) {
    System.out.println(entry.getKey() + ": " + entry.getValue());
}

Queue Interface

For handling collections that act as queues (FIFO).

import java.util.*;

// PriorityQueue - elements processed by priority
Queue<Integer> pq = new PriorityQueue<>();
pq.add(5);
pq.add(2);
pq.add(8);
pq.add(1);

while (!pq.isEmpty()) {
    System.out.println(pq.poll());  // 1, 2, 5, 8
}

Generics

Generics allow you to create classes, interfaces, and methods that work with any data type.

Generic Class

// Box can hold any type
class Box<T> {
    private T content;
    
    public void set(T content) {
        this.content = content;
    }
    
    public T get() {
        return content;
    }
}

public class Main {
    public static void main(String[] args) {
        Box<String> stringBox = new Box<>();
        stringBox.set("Hello");
        System.out.println(stringBox.get());  // Hello
        
        Box<Integer> intBox = new Box<>();
        intBox.set(42);
        System.out.println(intBox.get());  // 42
    }
}

Multiple Type Parameters

class Pair<K, V> {
    private K key;
    private V value;
    
    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }
    
    public K getKey() { return key; }
    public V getValue() { return value; }
}

Pair<String, Integer> pair = new Pair<>("Age", 25);
System.out.println(pair.getKey());   // Age
System.out.println(pair.getValue()); // 25

Generic Methods

public class Utils {
    // Generic method
    public static <T> void printArray(T[] array) {
        for (T item : array) {
            System.out.print(item + " ");
        }
        System.out.println();
    }
    
    public static void main(String[] args) {
        Integer[] nums = {1, 2, 3};
        String[] words = {"Hello", "World"};
        
        printArray(nums);   // 1 2 3 
        printArray(words);  // Hello World
    }
}

Lambda Expressions

Lambdas are concise way to represent anonymous functions (methods without names).

Basic Syntax

// Traditional way
Runnable r1 = new Runnable() {
    @Override
    public void run() {
        System.out.println("Running!");
    }
};

// Lambda way
Runnable r2 = () -> System.out.println("Running!");

Lambda with Parameters

// No parameters
() -> System.out.println("Hello")

// One parameter
x -> x * 2

// Multiple parameters
(a, b) -> a + b

// Multiple statements
(a, b) -> {
    int sum = a + b;
    return sum;
}

Using Lambdas with Collections

List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Diana");

// Traditional loop
for (String name : names) {
    System.out.println(name);
}

// Lambda with forEach
names.forEach(name -> System.out.println(name));

// Even shorter!
names.forEach(System.out::println);

Streams API

Streams provide a functional approach to processing collections.

Creating Streams

import java.util.*;

// From collection
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> stream = list.stream();

// From array
String[] arr = {"a", "b", "c"};
Stream<String> stream2 = Arrays.stream(arr);

// From values
Stream<Integer> stream3 = Stream.of(1, 2, 3, 4, 5);

Stream Operations

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

// filter - keep elements that match
List<Integer> evens = numbers.stream()
    .filter(n -> n % 2 == 0)
    .collect(Collectors.toList());
System.out.println(evens);  // [2, 4, 6, 8, 10]

// map - transform each element
List<Integer> doubled = numbers.stream()
    .map(n -> n * 2)
    .collect(Collectors.toList());
System.out.println(doubled);  // [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

// sorted - sort elements
List<String> sorted = names.stream()
    .sorted()
    .collect(Collectors.toList());

// distinct - remove duplicates
List<Integer> distinct = numbers.stream()
    .distinct()
    .collect(Collectors.toList());

// limit - take first N elements
List<Integer> first5 = numbers.stream()
    .limit(5)
    .collect(Collectors.toList());

// skip - skip first N elements
List<Integer> skipFirst5 = numbers.stream()
    .skip(5)
    .collect(Collectors.toList());

Terminal Operations

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

// forEach - do something for each element
numbers.stream().forEach(n -> System.out.println(n));

// collect - convert to collection
List<Integer> list = numbers.stream().collect(Collectors.toList());
Set<Integer> set = numbers.stream().collect(Collectors.toSet());

// count - count elements
long count = numbers.stream().count();

// min/max - find min/max
Optional<Integer> min = numbers.stream().min(Integer::compareTo);
Optional<Integer> max = numbers.stream().max(Integer::compareTo);

// reduce - combine elements
Optional<Integer> sum = numbers.stream().reduce((a, b) -> a + b);
System.out.println(sum.orElse(0));  // 15

// findFirst/findAny - find element
Optional<Integer> first = numbers.stream().findFirst();

// anyMatch/allMatch/noneMatch - check conditions
boolean hasEven = numbers.stream().anyMatch(n -> n % 2 == 0);
boolean allPositive = numbers.stream().allMatch(n -> n > 0);

Functional Interfaces

Functional interfaces have exactly one abstract method. They're used with lambdas.

Common Functional Interfaces

Interface Method Description
Predicate<T> test(T t) Returns boolean
Function<T,R> apply(T t) Transforms T to R
Consumer<T> accept(T t) Consumes T, returns nothing
Supplier<T> get() Returns T
UnaryOperator<T> apply(T t) T → T
BinaryOperator<T> apply(T t1, T t2) (T, T) → T

Using Functional Interfaces

import java.util.function.*;

// Predicate - returns boolean
Predicate<Integer> isEven = n -> n % 2 == 0;
System.out.println(isEven.test(4));  // true

// Function - transforms
Function<String, Integer> stringLength = s -> s.length();
System.out.println(stringLength.apply("Hello"));  // 5

// Consumer - uses value
Consumer<String> printer = s -> System.out.println(s);
printer.accept("Hello!");

// Supplier - provides value
Supplier<Double> randomValue = () -> Math.random();
System.out.println(randomValue.get());

// Combining
Predicate<Integer> isPositive = n -> n > 0;
Predicate<Integer> isEven2 = n -> n % 2 == 0;
Predicate<Integer> isPositiveAndEven = isPositive.and(isEven2);
System.out.println(isPositiveAndEven.test(4));  // true

Method References

Method references are a shorthand way to write lambdas that call methods.

Types of Method References

import java.util.*;

// 1. Static method: ClassName::staticMethod
Function<String, Integer> parser = Integer::parseInt;
System.out.println(parser.apply("123"));  // 123

// 2. Instance method of object: object::instanceMethod
String str = "Hello";
Function<Integer, Character> charAt = str::charAt;
System.out.println(charAt.apply(1));  // 'e'

// 3. Instance method of type: ClassName::instanceMethod
Function<String, String> upper = String::toUpperCase;
System.out.println.upper.apply("hello"));  // HELLO

// 4. Constructor: ClassName::new
Supplier<ArrayList<String>> listSupplier = ArrayList::new;
ArrayList<String> list = listSupplier.get();

Method Reference vs Lambda

// These are equivalent:
list.forEach(s -> System.out.println(s));
list.forEach(System.out::println);

list.stream().map(s -> s.toUpperCase());
list.stream().map(String::toUpperCase);

list.stream().filter(s -> s.isEmpty());
list.stream().filter(String::isEmpty);

Optional

Optional is a container that may or may not contain a value. It helps avoid NullPointerException.

Creating Optional

import java.util.Optional;

// Create with value
Optional<String> opt1 = Optional.of("Hello");
Optional<String> opt2 = Optional.ofNullable("Hello");

// Create empty
Optional<String> empty = Optional.empty();

// Create from nullable (allows null)
Optional<String> opt3 = Optional.ofNullable(null);

Using Optional

Optional<String> opt = Optional.ofNullable("Hello");

// Check if present
if (opt.isPresent()) {
    System.out.println(opt.get());  // Hello
}

// orElse - default if empty
String result = opt.orElse("Default");
System.out.println(result);  // Hello

// orElseGet - supplier for default
String result2 = opt.orElseGet(() -> "Computed Default");

// orElseThrow - throw if empty
String result3 = opt.orElseThrow(() -> new IllegalArgumentException("Empty!"));

// ifPresent - do if present
opt.ifPresent(s -> System.out.println("Value: " + s));

// map - transform if present
Optional<Integer> len = opt.map(s -> s.length());

Multithreading Basics

Multithreading allows your program to do multiple things at the same time.

Creating Threads

// Method 1: Extend Thread
class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("Thread: " + i);
        }
    }
}

// Method 2: Implement Runnable
class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println("Runnable: " + i);
        }
    }
}

public class Main {
    public static void main(String[] args) {
        // Start thread
        MyThread thread1 = new MyThread();
        thread1.start();
        
        // Using Runnable
        Thread thread2 = new Thread(new MyRunnable());
        thread2.start();
        
        // Lambda (simplest)
        Thread thread3 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("Lambda: " + i);
            }
        });
        thread3.start();
    }
}

Thread Synchronization

class Counter {
    private int count = 0;
    
    // Synchronized method - only one thread at a time
    public synchronized void increment() {
        count++;
    }
    
    public int getCount() {
        return count;
    }
}

public class Main {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });
        
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter.increment();
            }
        });
        
        t1.start();
        t2.start();
        
        t1.join();
        t2.join();
        
        System.out.println(counter.getCount());  // 2000
    }
}

Working with Files

Java provides easy ways to read and write files.

Reading Files

import java.io.*;
import java.nio.file.*;

// Using Scanner
Scanner scanner = new Scanner(new File("input.txt"));
while (scanner.hasNextLine()) {
    System.out.println(scanner.nextLine());
}
scanner.close();

// Using BufferedReader
try (BufferedReader reader = new BufferedReader(new FileReader("input.txt"))) {
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
}

// Using Files (Java 7+)
List<String> lines = Files.readAllLines(Paths.get("input.txt"));
for (String line : lines) {
    System.out.println(line);
}

Writing Files

import java.io.*;
import java.nio.file.*;

// Using BufferedWriter
try (BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {
    writer.write("Hello, World!");
    writer.newLine();
    writer.write("Second line");
}

// Using Files (Java 7+)
String content = "Hello, World!\nSecond line";
Files.write(Paths.get("output.txt"), content.getBytes());

Date and Time API

Java 8 introduced a new date/time API that's much easier to use.

Creating Dates and Times

import java.time.*;

// Current date
LocalDate today = LocalDate.now();
System.out.println(today);  // 2024-01-15

// Specific date
LocalDate birthday = LocalDate.of(2000, 5, 15);
System.out.println(birthday);  // 2000-05-15

// Current time
LocalTime now = LocalTime.now();
System.out.println(now);  // 14:30:45.123

// Specific time
LocalTime meeting = LocalTime.of(14, 30);
System.out.println(meeting);  // 14:30

// Date and time together
LocalDateTime dateTime = LocalDateTime.now();
System.out.println(dateTime);  // 2024-01-15T14:30:45

Date Operations

LocalDate date = LocalDate.now();

// Add/subtract
LocalDate nextWeek = date.plusWeeks(1);
LocalDate lastMonth = date.minusMonths(1);

// Get parts
int year = date.getYear();
int month = date.getMonthValue();
int day = date.getDayOfMonth();

// Compare
boolean isAfter = date.isAfter(LocalDate.of(2020, 1, 1));
boolean isBefore = date.isBefore(LocalDate.of(2030, 1, 1));

Formatting

import java.time.format.*;

LocalDate date = LocalDate.of(2024, 1, 15);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");
String formatted = date.format(formatter);
System.out.println(formatted);  // 15/01/2024

// Parse back
LocalDate parsed = LocalDate.parse("15/01/2024", formatter);

Annotations

Annotations add metadata to your code.

Built-in Annotations

// @Override - marks method override
@Override
public void doSomething() { }

// @Deprecated - marks deprecated code
@Deprecated
public void oldMethod() { }

// @SuppressWarnings - suppresses warnings
@SuppressWarnings("unchecked")
public void rawType() { }

// @FunctionalInterface - marks functional interface
@FunctionalInterface
interface MyFunction {
    void apply();
}

Custom Annotations

// Define annotation
@interface Author {
    String name();
    String date();
}

// Use annotation
@Author(name = "John", date = "2024-01-15")
public class MyClass {
    // class body
}

Practice Exercises

Exercise 1: Stream Operations

Use streams to filter and transform a list.

import java.util.*;
import java.util.stream.*;

public class Exercise1 {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
        
        // Filter even numbers and double them
        List<Integer> result = numbers.stream()
            .filter(n -> n % 2 == 0)
            .map(n -> n * 2)
            .collect(Collectors.toList());
        
        System.out.println(result);  // [4, 8, 12, 16, 20]
    }
}

Exercise 2: Optional

Safely handle nullable values.

import java.util.Optional;

public class Exercise2 {
    public static String getName(Optional<String> name) {
        return name.orElse("Unknown");
    }
    
    public static void main(String[] args) {
        Optional<String> name1 = Optional.of("Alice");
        Optional<String> name2 = Optional.empty();
        
        System.out.println(getName(name1));   // Alice
        System.out.println(getName(name2));   // Unknown
    }
}

Exercise 3: Thread Creation

Create threads using lambdas.

public class Exercise3 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                System.out.println("Thread A: " + i);
            }
        });
        
        Thread t2 = new Thread(() -> {
            for (int i = 1; i <= 5; i++) {
                System.out.println("Thread B: " + i);
            }
        });
        
        t1.start();
        t2.start();
    }
}

Exercise 4: Generic Method

Create a method that finds the maximum.

public class Exercise4 {
    public static <T extends Comparable<T>> T findMax(T[] array) {
        if (array == null || array.length == 0) {
            return null;
        }
        
        T max = array[0];
        for (T item : array) {
            if (item.compareTo(max) > 0) {
                max = item;
            }
        }
        return max;
    }
    
    public static void main(String[] args) {
        Integer[] nums = {3, 5, 2, 8, 1};
        System.out.println(findMax(nums));  // 8
        
        String[] words = {"apple", "banana", "cherry"};
        System.out.println(findMax(words));  // cherry
    }
}

Exercise 5: Date/Time

Work with dates and times.

import java.time.*;

public class Exercise5 {
    public static void main(String[] args) {
        LocalDate today = LocalDate.now();
        LocalDate birthday = LocalDate.of(2000, 6, 15);
        
        // Calculate age
        Period age = Period.between(birthday, today);
        System.out.println("Age: " + age.getYears());
        
        // Add 10 years
        LocalDate tenYearsLater = today.plusYears(10);
        System.out.println("In 10 years: " + tenYearsLater);
    }
}

Summary

In this guide, you learned:

  • ✅ The Collections framework (List, Set, Map, Queue)
  • ✅ Generics for type-safe code
  • ✅ Lambda expressions for concise code
  • ✅ Stream API for functional processing
  • ✅ Functional interfaces
  • ✅ Method references
  • ✅ Optional to handle nulls safely
  • ✅ Multithreading basics
  • ✅ File I/O operations
  • ✅ Date and Time API
  • ✅ Annotations

Next Steps: Move on to Java Complete Reference for a comprehensive reference guide!


You're now a Java expert! Congratulations! 🎉

Clone this wiki locally