# Java Advanced Guide - Modern Java Features ## Table of Contents - [Introduction](#introduction) - [Collections Framework](#collections-framework) - [Generics](#generics) - [Lambda Expressions](#lambda-expressions) - [Streams API](#streams-api) - [Functional Interfaces](#functional-interfaces) - [Method References](#method-references) - [Optional](#optional) - [Multithreading Basics](#multithreading-basics) - [Working with Files](#working-with-files) - [Date and Time API](#date-and-time-api) - [Annotations](#annotations) - [Practice Exercises](#practice-exercises) --- ## 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. ```java import java.util.*; // ArrayList - dynamic array List 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 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. ```java import java.util.*; // HashSet - no specific order Set 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 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. ```java import java.util.*; // HashMap - key-value storage Map 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 entry : ages.entrySet()) { System.out.println(entry.getKey() + ": " + entry.getValue()); } ``` ### Queue Interface For handling collections that act as queues (FIFO). ```java import java.util.*; // PriorityQueue - elements processed by priority Queue 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 ```java // Box can hold any type class Box { 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 stringBox = new Box<>(); stringBox.set("Hello"); System.out.println(stringBox.get()); // Hello Box intBox = new Box<>(); intBox.set(42); System.out.println(intBox.get()); // 42 } } ``` ### Multiple Type Parameters ```java class Pair { 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 pair = new Pair<>("Age", 25); System.out.println(pair.getKey()); // Age System.out.println(pair.getValue()); // 25 ``` ### Generic Methods ```java public class Utils { // Generic method public static 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 ```java // 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 ```java // 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 ```java List 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 ```java import java.util.*; // From collection List list = Arrays.asList(1, 2, 3, 4, 5); Stream stream = list.stream(); // From array String[] arr = {"a", "b", "c"}; Stream stream2 = Arrays.stream(arr); // From values Stream stream3 = Stream.of(1, 2, 3, 4, 5); ``` ### Stream Operations ```java List numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // filter - keep elements that match List 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 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 sorted = names.stream() .sorted() .collect(Collectors.toList()); // distinct - remove duplicates List distinct = numbers.stream() .distinct() .collect(Collectors.toList()); // limit - take first N elements List first5 = numbers.stream() .limit(5) .collect(Collectors.toList()); // skip - skip first N elements List skipFirst5 = numbers.stream() .skip(5) .collect(Collectors.toList()); ``` ### Terminal Operations ```java List 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 list = numbers.stream().collect(Collectors.toList()); Set set = numbers.stream().collect(Collectors.toSet()); // count - count elements long count = numbers.stream().count(); // min/max - find min/max Optional min = numbers.stream().min(Integer::compareTo); Optional max = numbers.stream().max(Integer::compareTo); // reduce - combine elements Optional sum = numbers.stream().reduce((a, b) -> a + b); System.out.println(sum.orElse(0)); // 15 // findFirst/findAny - find element Optional 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` | `test(T t)` | Returns boolean | | `Function` | `apply(T t)` | Transforms T to R | | `Consumer` | `accept(T t)` | Consumes T, returns nothing | | `Supplier` | `get()` | Returns T | | `UnaryOperator` | `apply(T t)` | T → T | | `BinaryOperator` | `apply(T t1, T t2)` | (T, T) → T | ### Using Functional Interfaces ```java import java.util.function.*; // Predicate - returns boolean Predicate isEven = n -> n % 2 == 0; System.out.println(isEven.test(4)); // true // Function - transforms Function stringLength = s -> s.length(); System.out.println(stringLength.apply("Hello")); // 5 // Consumer - uses value Consumer printer = s -> System.out.println(s); printer.accept("Hello!"); // Supplier - provides value Supplier randomValue = () -> Math.random(); System.out.println(randomValue.get()); // Combining Predicate isPositive = n -> n > 0; Predicate isEven2 = n -> n % 2 == 0; Predicate 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 ```java import java.util.*; // 1. Static method: ClassName::staticMethod Function parser = Integer::parseInt; System.out.println(parser.apply("123")); // 123 // 2. Instance method of object: object::instanceMethod String str = "Hello"; Function charAt = str::charAt; System.out.println(charAt.apply(1)); // 'e' // 3. Instance method of type: ClassName::instanceMethod Function upper = String::toUpperCase; System.out.println.upper.apply("hello")); // HELLO // 4. Constructor: ClassName::new Supplier> listSupplier = ArrayList::new; ArrayList list = listSupplier.get(); ``` ### Method Reference vs Lambda ```java // 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 ```java import java.util.Optional; // Create with value Optional opt1 = Optional.of("Hello"); Optional opt2 = Optional.ofNullable("Hello"); // Create empty Optional empty = Optional.empty(); // Create from nullable (allows null) Optional opt3 = Optional.ofNullable(null); ``` ### Using Optional ```java Optional 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 len = opt.map(s -> s.length()); ``` --- ## Multithreading Basics Multithreading allows your program to do multiple things at the same time. ### Creating Threads ```java // 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 ```java 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 ```java 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 lines = Files.readAllLines(Paths.get("input.txt")); for (String line : lines) { System.out.println(line); } ``` ### Writing Files ```java 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 ```java 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 ```java 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 ```java 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 ```java // @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 ```java // 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. ```java import java.util.*; import java.util.stream.*; public class Exercise1 { public static void main(String[] args) { List numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); // Filter even numbers and double them List 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. ```java import java.util.Optional; public class Exercise2 { public static String getName(Optional name) { return name.orElse("Unknown"); } public static void main(String[] args) { Optional name1 = Optional.of("Alice"); Optional name2 = Optional.empty(); System.out.println(getName(name1)); // Alice System.out.println(getName(name2)); // Unknown } } ``` ### Exercise 3: Thread Creation Create threads using lambdas. ```java 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. ```java public class Exercise4 { public static > 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. ```java 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](./Java-Complete-Reference.md) for a comprehensive reference guide! --- *You're now a Java expert! Congratulations! 🎉*