Java Advanced

Complete guide to enterprise Java development and JVM optimization

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