-
-
Notifications
You must be signed in to change notification settings - Fork 2
Java Advanced Guide
- Introduction
- Collections Framework
- Generics
- Lambda Expressions
- Streams API
- Functional Interfaces
- Method References
- Optional
- Multithreading Basics
- Working with Files
- Date and Time API
- Annotations
- Practice Exercises
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.
The Collections framework provides data structures to store and manipulate groups of objects.
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);
}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]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());
}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 allow you to create classes, interfaces, and methods that work with any data type.
// 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
}
}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()); // 25public 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
}
}Lambdas are concise way to represent anonymous functions (methods without names).
// Traditional way
Runnable r1 = new Runnable() {
@Override
public void run() {
System.out.println("Running!");
}
};
// Lambda way
Runnable r2 = () -> System.out.println("Running!");// 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;
}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 provide a functional approach to processing collections.
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);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());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 have exactly one abstract method. They're used with lambdas.
| 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 |
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)); // trueMethod references are a shorthand way to write lambdas that call methods.
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();// 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 is a container that may or may not contain a value. It helps avoid NullPointerException.
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);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 allows your program to do multiple things at the same time.
// 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();
}
}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
}
}Java provides easy ways to read and write 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);
}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());Java 8 introduced a new date/time API that's much easier to use.
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:45LocalDate 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));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 add metadata to your code.
// @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();
}// Define annotation
@interface Author {
String name();
String date();
}
// Use annotation
@Author(name = "John", date = "2024-01-15")
public class MyClass {
// class body
}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]
}
}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
}
}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();
}
}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
}
}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);
}
}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! 🎉