Table of Contents
Modern Java Features
Java 8+ Features
// Lambda Expressions (Java 8)
// Before Java 8
Comparator<String> comparator = new Comparator<String>() {
@Override
public int compare(String s1, String s2) {
return s1.compareTo(s2);
}
};
// With lambda
Comparator<String> comparator = (s1, s2) -> s1.compareTo(s2);
// Even shorter with method reference
Comparator<String> comparator = String::compareTo;
// Functional Interfaces
@FunctionalInterface
interface Calculator {
int calculate(int a, int b);
}
Calculator add = (a, b) -> a + b;
Calculator multiply = (a, b) -> a * b;
System.out.println(add.calculate(5, 3)); // 8
System.out.println(multiply.calculate(5, 3)); // 15
// Built-in Functional Interfaces
import java.util.function.*;
// Predicate<T> - boolean test
Predicate<String> isEmpty = String::isEmpty;
Predicate<Integer> isPositive = n -> n > 0;
// Function<T, R> - transform T to R
Function<String, Integer> length = String::length;
Function<Integer, String> toString = Object::toString;
// Consumer<T> - accept T, return void
Consumer<String> print = System.out::println;
// Supplier<T> - supply T
Supplier<Double> random = Math::random;
// BiFunction<T, U, R> - two inputs
BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
// Optional (Java 8)
Optional<String> optional = Optional.of("Hello");
Optional<String> empty = Optional.empty();
// Check if present
if (optional.isPresent()) {
System.out.println(optional.get());
}
// Better: use ifPresent
optional.ifPresent(System.out::println);
// OrElse
String value = optional.orElse("default");
String value2 = optional.orElseGet(() -> "default");
String value3 = optional.orElseThrow(() -> new RuntimeException());
// Map and flatMap
Optional<Integer> length = optional.map(String::length);
Optional<String> upper = optional.map(String::toUpperCase);
// Filter
Optional<String> filtered = optional.filter(s -> s.length() > 3);
// Records (Java 14+)
// Immutable data class
public record Person(String name, int age) {
// Compact constructor
public Person {
if (age < 0) {
throw new IllegalArgumentException("Age cannot be negative");
}
}
// Additional methods
public boolean isAdult() {
return age >= 18;
}
}
Person person = new Person("Alice", 30);
System.out.println(person.name()); // Alice
System.out.println(person.age()); // 30
// Sealed Classes (Java 17)
public sealed interface Shape
permits Circle, Rectangle, Triangle {
double area();
}
public final class Circle implements Shape {
private final double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public double area() {
return Math.PI * radius * radius;
}
}
// Pattern matching with sealed classes
double calculateArea(Shape shape) {
return switch (shape) {
case Circle c -> c.area();
case Rectangle r -> r.area();
case Triangle t -> t.area();
};
}
// Pattern Matching for instanceof (Java 16)
// Before
if (obj instanceof String) {
String s = (String) obj;
System.out.println(s.toUpperCase());
}
// After
if (obj instanceof String s) {
System.out.println(s.toUpperCase());
}
// Switch Expressions (Java 14)
// Old switch
String result;
switch (day) {
case MONDAY:
case FRIDAY:
result = "Good day";
break;
case TUESDAY:
result = "Bad day";
break;
default:
result = "Meh";
break;
}
// New switch expression
String result = switch (day) {
case MONDAY, FRIDAY -> "Good day";
case TUESDAY -> "Bad day";
default -> "Meh";
};
// With yield
String result = switch (day) {
case MONDAY, FRIDAY -> {
System.out.println("Calculating...");
yield "Good day";
}
case TUESDAY -> "Bad day";
default -> "Meh";
};
// Text Blocks (Java 15)
// Before
String json = "{\n" +
" \"name\": \"Alice\",\n" +
" \"age\": 30\n" +
"}";
// After
String json = """
{
"name": "Alice",
"age": 30
}
""";
// var keyword (Java 10)
// Type inference for local variables
var list = new ArrayList<String>();
var map = new HashMap<String, Integer>();
var name = "Alice";
// Works with generics
var result = map.entrySet().stream()
.filter(e -> e.getValue() > 10)
.map(Map.Entry::getKey)
.collect(Collectors.toList());
// Private methods in interfaces (Java 9)
interface MyInterface {
default void publicMethod() {
privateMethod();
}
private void privateMethod() {
System.out.println("Private helper method");
}
}
// Try with resources improvements (Java 9)
// Java 7-8
BufferedReader reader = new BufferedReader(new FileReader("file.txt"));
try (BufferedReader br = reader) {
return br.readLine();
}
// Java 9+
BufferedReader reader = new BufferedReader(new FileReader("file.txt"));
try (reader) {
return reader.readLine();
}
Streams & Functional Programming
Stream API
// Creating Streams
// From collection
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> stream = list.stream();
// From array
String[] array = {"a", "b", "c"};
Stream<String> stream = Arrays.stream(array);
// From values
Stream<String> stream = Stream.of("a", "b", "c");
// Infinite streams
Stream<Integer> infinite = Stream.iterate(0, n -> n + 1);
Stream<Double> random = Stream.generate(Math::random);
// Range
IntStream range = IntStream.range(0, 10); // 0-9
IntStream rangeClosed = IntStream.rangeClosed(0, 10); // 0-10
// Intermediate Operations (lazy)
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Dave");
// filter
Stream<String> filtered = names.stream()
.filter(name -> name.length() > 3);
// map
Stream<Integer> lengths = names.stream()
.map(String::length);
// flatMap
List<List<Integer>> nested = Arrays.asList(
Arrays.asList(1, 2),
Arrays.asList(3, 4)
);
Stream<Integer> flat = nested.stream()
.flatMap(List::stream); // [1, 2, 3, 4]
// distinct
Stream<Integer> unique = Stream.of(1, 2, 2, 3, 3, 3)
.distinct(); // [1, 2, 3]
// sorted
Stream<String> sorted = names.stream()
.sorted();
Stream<String> sortedReverse = names.stream()
.sorted(Comparator.reverseOrder());
// limit & skip
Stream<String> limited = names.stream().limit(2); // First 2
Stream<String> skipped = names.stream().skip(2); // Skip first 2
// peek (debugging)
names.stream()
.peek(name -> System.out.println("Processing: " + name))
.map(String::toUpperCase)
.collect(Collectors.toList());
// Terminal Operations (eager)
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// collect
List<Integer> list = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
Set<Integer> set = numbers.stream()
.collect(Collectors.toSet());
Map<Integer, String> map = numbers.stream()
.collect(Collectors.toMap(
n -> n,
n -> "Number " + n
));
// forEach
numbers.stream().forEach(System.out::println);
// reduce
Optional<Integer> sum = numbers.stream()
.reduce((a, b) -> a + b);
int sum = numbers.stream()
.reduce(0, (a, b) -> a + b);
// count
long count = numbers.stream()
.filter(n -> n > 2)
.count();
// anyMatch, allMatch, noneMatch
boolean hasEven = numbers.stream()
.anyMatch(n -> n % 2 == 0);
boolean allPositive = numbers.stream()
.allMatch(n -> n > 0);
boolean noneNegative = numbers.stream()
.noneMatch(n -> n < 0);
// findFirst, findAny
Optional<Integer> first = numbers.stream()
.filter(n -> n > 3)
.findFirst();
Optional<Integer> any = numbers.stream()
.filter(n -> n > 3)
.findAny();
// min, max
Optional<Integer> min = numbers.stream().min(Integer::compareTo);
Optional<Integer> max = numbers.stream().max(Integer::compareTo);
// Advanced Collectors
List<Person> people = Arrays.asList(
new Person("Alice", 30),
new Person("Bob", 25),
new Person("Charlie", 30)
);
// Grouping
Map<Integer, List<Person>> byAge = people.stream()
.collect(Collectors.groupingBy(Person::age));
// Counting
Map<Integer, Long> countByAge = people.stream()
.collect(Collectors.groupingBy(
Person::age,
Collectors.counting()
));
// Summing
Map<Integer, Integer> sumByAge = people.stream()
.collect(Collectors.groupingBy(
Person::age,
Collectors.summingInt(Person::age)
));
// Partitioning
Map<Boolean, List<Person>> partitioned = people.stream()
.collect(Collectors.partitioningBy(p -> p.age() >= 30));
// Joining strings
String names = people.stream()
.map(Person::name)
.collect(Collectors.joining(", "));
String namesWithDelimiters = people.stream()
.map(Person::name)
.collect(Collectors.joining(", ", "[", "]"));
// Statistics
IntSummaryStatistics stats = people.stream()
.collect(Collectors.summarizingInt(Person::age));
System.out.println("Average: " + stats.getAverage());
System.out.println("Max: " + stats.getMax());
System.out.println("Min: " + stats.getMin());
System.out.println("Sum: " + stats.getSum());
System.out.println("Count: " + stats.getCount());
// Parallel Streams
// Parallel processing
long count = numbers.parallelStream()
.filter(n -> n % 2 == 0)
.count();
// Convert to parallel
Stream<Integer> parallel = numbers.stream().parallel();
// Check if parallel
boolean isParallel = parallel.isParallel();
// Performance consideration
List<Integer> large = IntStream.range(0, 1000000)
.boxed()
.collect(Collectors.toList());
// Sequential
long start = System.currentTimeMillis();
long sum1 = large.stream()
.mapToInt(Integer::intValue)
.sum();
System.out.println("Sequential: " + (System.currentTimeMillis() - start));
// Parallel
start = System.currentTimeMillis();
long sum2 = large.parallelStream()
.mapToInt(Integer::intValue)
.sum();
System.out.println("Parallel: " + (System.currentTimeMillis() - start));
// Custom Collector
Collector<Person, ?, String> personNameCollector = Collector.of(
StringBuilder::new,
(sb, person) -> {
if (sb.length() > 0) sb.append(", ");
sb.append(person.name());
},
StringBuilder::append,
StringBuilder::toString
);
String allNames = people.stream()
.collect(personNameCollector);
Concurrency & Threading
Concurrency Patterns
// ExecutorService
import java.util.concurrent.*;
// Fixed thread pool
ExecutorService executor = Executors.newFixedThreadPool(5);
// Cached thread pool (grows as needed)
ExecutorService executor = Executors.newCachedThreadPool();
// Single thread executor
ExecutorService executor = Executors.newSingleThreadExecutor();
// Scheduled executor
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(5);
// Submit tasks
// Runnable (no return value)
executor.submit(() -> {
System.out.println("Task executed");
});
// Callable (returns value)
Future<Integer> future = executor.submit(() -> {
return 42;
});
// Get result
try {
Integer result = future.get(); // Blocks until complete
Integer result = future.get(5, TimeUnit.SECONDS); // With timeout
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
// Check status
boolean isDone = future.isDone();
boolean isCancelled = future.isCancelled();
// Cancel task
future.cancel(true); // mayInterruptIfRunning
// Shutdown executor
executor.shutdown(); // No new tasks, wait for existing
executor.shutdownNow(); // Stop all tasks immediately
// Wait for termination
executor.awaitTermination(1, TimeUnit.MINUTES);
// CompletableFuture (Java 8)
// Async execution
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
// Long running task
return "Result";
});
// Then apply (transform result)
CompletableFuture<Integer> length = future.thenApply(String::length);
// Then accept (consume result)
future.thenAccept(result -> {
System.out.println("Result: " + result);
});
// Then run (no input, no output)
future.thenRun(() -> {
System.out.println("Task complete");
});
// Chaining
CompletableFuture<String> result = CompletableFuture.supplyAsync(() -> "Hello")
.thenApply(s -> s + " World")
.thenApply(String::toUpperCase);
// Combining futures
CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "World");
CompletableFuture<String> combined = future1.thenCombine(
future2,
(s1, s2) -> s1 + " " + s2
);
// All of (wait for all)
CompletableFuture<Void> all = CompletableFuture.allOf(future1, future2);
all.thenRun(() -> System.out.println("All complete"));
// Any of (wait for first)
CompletableFuture<Object> any = CompletableFuture.anyOf(future1, future2);
// Exception handling
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
if (Math.random() > 0.5) {
throw new RuntimeException("Error!");
}
return "Success";
}).exceptionally(ex -> {
return "Error: " + ex.getMessage();
});
// Handle (both success and error)
future.handle((result, ex) -> {
if (ex != null) {
return "Error: " + ex.getMessage();
}
return result;
});
// CountDownLatch
// Wait for N tasks to complete
CountDownLatch latch = new CountDownLatch(3);
for (int i = 0; i < 3; i++) {
executor.submit(() -> {
// Do work
latch.countDown();
});
}
latch.await(); // Wait for all tasks
System.out.println("All tasks complete");
// CyclicBarrier
// Wait for N threads at barrier
CyclicBarrier barrier = new CyclicBarrier(3, () -> {
System.out.println("All threads reached barrier");
});
for (int i = 0; i < 3; i++) {
executor.submit(() -> {
// Phase 1
barrier.await();
// Phase 2
barrier.await();
});
}
// Semaphore
// Limit concurrent access
Semaphore semaphore = new Semaphore(3); // 3 permits
executor.submit(() -> {
try {
semaphore.acquire();
// Critical section (max 3 threads)
} finally {
semaphore.release();
}
});
// Locks
import java.util.concurrent.locks.*;
// ReentrantLock
Lock lock = new ReentrantLock();
lock.lock();
try {
// Critical section
} finally {
lock.unlock();
}
// Try lock with timeout
if (lock.tryLock(5, TimeUnit.SECONDS)) {
try {
// Critical section
} finally {
lock.unlock();
}
}
// ReadWriteLock
ReadWriteLock rwLock = new ReentrantReadWriteLock();
Lock readLock = rwLock.readLock();
Lock writeLock = rwLock.writeLock();
// Multiple readers
readLock.lock();
try {
// Read data
} finally {
readLock.unlock();
}
// Single writer
writeLock.lock();
try {
// Write data
} finally {
writeLock.unlock();
}
// Atomic classes
import java.util.concurrent.atomic.*;
AtomicInteger counter = new AtomicInteger(0);
counter.incrementAndGet();
counter.decrementAndGet();
counter.addAndGet(5);
counter.compareAndSet(5, 10);
AtomicReference<String> ref = new AtomicReference<>("initial");
ref.set("new value");
ref.compareAndSet("new value", "updated");
// Concurrent Collections
// Thread-safe map
ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();
map.put("key", 1);
map.putIfAbsent("key", 2);
map.computeIfAbsent("key", k -> 42);
// Thread-safe queue
BlockingQueue<String> queue = new LinkedBlockingQueue<>();
queue.put("item"); // Blocks if full
String item = queue.take(); // Blocks if empty
// Thread-safe list
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("item");
// Virtual Threads (Java 21)
// Create virtual thread
Thread vThread = Thread.ofVirtual().start(() -> {
System.out.println("Virtual thread");
});
// Executor with virtual threads
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
Resources & Learning Path
Learning Progression
Phase 1: Java Fundamentals (2-3 weeks) □ Core syntax and OOP □ Collections framework □ Exception handling □ I/O and file operations □ Basic multithreading Phase 2: Advanced Java (4-6 weeks) □ Streams and lambda expressions □ Concurrency utilities □ JDBC and database access □ Network programming □ Design patterns Phase 3: Enterprise Java (6-8 weeks) □ Spring Framework/Boot □ JPA and Hibernate □ REST API development □ Microservices architecture □ Security (Spring Security) Phase 4: Production Java (Ongoing) □ JVM tuning and monitoring □ Performance optimization □ Testing strategies □ CI/CD pipelines □ Cloud deployment
Pro Tips Summary
Performance ✓ Use streams for readability, loops for performance ✓ Profile before optimizing ✓ Use StringBuilder for string concatenation ✓ Avoid creating unnecessary objects ✓ Use primitive types when possible ✓ Enable JIT compiler optimizations Concurrency ✓ Use thread pools, not raw threads ✓ Prefer immutable objects ✓ Use concurrent collections ✓ Avoid blocking operations ✓ Handle interrupts properly ✓ Use CompletableFuture for async Spring Boot ✓ Use constructor injection ✓ Keep controllers thin ✓ Use @Transactional wisely ✓ Configure connection pools ✓ Enable caching strategically ✓ Use profiles for environments Best Practices ✓ Follow naming conventions ✓ Use Optional to avoid null ✓ Implement equals/hashCode correctly ✓ Close resources with try-with-resources ✓ Use enums instead of constants ✓ Write meaningful comments ✓ Keep methods small and focused Testing ✓ Write unit tests first ✓ Use Mockito for mocking ✓ Test edge cases ✓ Use @SpringBootTest for integration ✓ Measure code coverage ✓ Use TestContainers for databases