Overview
This module covers the revolutionary features introduced in Java 8, including lambda expressions, functional interfaces, Stream API, method references, Optional class, and new Date/Time API.
Learning Objectives
- Master lambda expressions and functional programming
- Understand and use functional interfaces
- Learn Stream API for data processing
- Work with method references
- Use Optional for null safety
- Master new Date/Time API
- Understand default and static methods in interfaces
Topics Covered
16.1 Introduction to Java 8
16.1.1 Java 8 Overview
- Major Features
- Functional Programming Paradigm
- API Enhancements
- Performance Improvements
- Backward Compatibility
16.1.2 Why Java 8?
- Conciseness
- Readability
- Parallel Processing
- Modern Programming Practices
- Industry Adoption
16.2 Lambda Expressions
16.2.1 Understanding Lambda Expressions
- Anonymous Functions
- Functional Programming
- Syntax and Structure
- Replacing Anonymous Classes
16.2.2 Lambda Syntax
- (parameters) -> expression
- (parameters) -> { statements }
- Single Parameter
- No Parameters
- Type Inference
16.2.3 Lambda Examples
- Runnable
- Comparator
- Event Handlers
- Custom Functional Interfaces
16.2.4 Variable Capture
- Effectively Final Variables
- Lexical Scoping
- this Reference
- Closure Concept
16.2.5 Lambda Best Practices
- Keep It Simple
- Prefer Method References
- Side Effects
- Exception Handling
16.3 Functional Interfaces
16.3.1 Understanding Functional Interfaces
- Single Abstract Method (SAM)
- @FunctionalInterface Annotation
- Lambda Target Type
- Default Methods Allowed
16.3.2 Built-in Functional Interfaces
- java.util.function Package
- Common Interfaces Overview
- Use Cases
16.3.3 Predicate
- test() Method
- Boolean Conditions
- Combining Predicates (and, or, negate)
- Filtering Use Cases
16.3.4 Function<T, R>
- apply() Method
- Transformation
- Combining Functions (compose, andThen)
- Mapping Use Cases
16.3.5 Consumer
- accept() Method
- Side Effects
- Chaining Consumers (andThen)
- forEach Use Cases
16.3.6 Supplier
- get() Method
- Lazy Evaluation
- Factory Pattern
- No Input Arguments
16.3.7 UnaryOperator
- extends Function<T, T>
- Same Input/Output Type
- Mathematical Operations
- Transformation
16.3.8 BinaryOperator
- extends BiFunction<T, T, T>
- Two Inputs, Same Type Output
- Reduction Operations
- Accumulators
16.3.9 BiFunction<T, U, R>
- apply() Method
- Two Input Parameters
- Combining Operations
- Use Cases
16.3.10 BiConsumer<T, U>
- accept() Method
- Two Input Parameters
- Side Effects
- Map.forEach Use Cases
16.3.11 BiPredicate<T, U>
- test() Method
- Two Input Parameters
- Boolean Conditions
- Complex Filtering
16.3.12 Primitive Functional Interfaces
- IntPredicate, LongPredicate, DoublePredicate
- IntFunction, LongFunction, DoubleFunction
- IntConsumer, LongConsumer, DoubleConsumer
- IntSupplier, LongSupplier, DoubleSupplier
- Performance Benefits (No Boxing)
16.4 Method References
16.4.1 Understanding Method References
- Shorthand for Lambdas
- Readability
- :: Operator
- Four Types
16.4.2 Static Method References
- ClassName::staticMethod
- Math::max
- Integer::parseInt
- Use Cases
16.4.3 Instance Method References (Specific Object)
- object::instanceMethod
- System.out::println
- str::toLowerCase
- Use Cases
16.4.4 Instance Method References (Arbitrary Object)
- ClassName::instanceMethod
- String::length
- String::compareToIgnoreCase
- Use Cases
16.4.5 Constructor References
- ClassName::new
- ArrayList::new
- Creating Objects
- Factory Pattern
16.4.6 Array Constructor References
- Type[]::new
- String[]::new
- toArray() Method
- Use Cases
16.5 Stream API
16.5.1 Introduction to Streams
- Sequence of Elements
- Declarative Programming
- Pipeline Processing
- Lazy Evaluation
- Not Data Structure
16.5.2 Creating Streams
- Collection.stream()
- Arrays.stream()
- Stream.of()
- Stream.generate()
- Stream.iterate()
- IntStream, LongStream, DoubleStream
16.5.3 Intermediate Operations
- Lazy Operations
- Return Stream
- Chaining
- Stateless vs Stateful
16.5.4 filter()
- Predicate-Based Filtering
- Removing Elements
- Multiple Filters
- Use Cases
16.5.5 map()
- Transformation
- One-to-One Mapping
- Type Conversion
- Use Cases
16.5.6 flatMap()
- Flattening Nested Structures
- One-to-Many Mapping
- Stream of Streams
- Use Cases
16.5.7 distinct()
- Removing Duplicates
- equals() and hashCode()
- Stateful Operation
- Use Cases
16.5.8 sorted()
- Natural Order
- Custom Comparator
- Stateful Operation
- Performance Considerations
16.5.9 peek()
- Debugging
- Side Effects
- Intermediate Inspection
- Use Cases
16.5.10 limit() and skip()
- Limiting Elements
- Skipping Elements
- Pagination
- Use Cases
16.5.11 Terminal Operations
- Trigger Execution
- Consume Stream
- Produce Result
- Stream Closed After
16.5.12 forEach()
- Iteration
- Side Effects
- Consumer Action
- Order Not Guaranteed
16.5.13 collect()
- Mutable Reduction
- Collectors Class
- toList(), toSet(), toMap()
- Joining, Grouping, Partitioning
16.5.14 reduce()
- Reduction Operation
- Combining Elements
- BinaryOperator
- Identity, Accumulator, Combiner
16.5.15 count()
- Counting Elements
- Terminal Operation
- Returns long
16.5.16 anyMatch(), allMatch(), noneMatch()
- Predicate Matching
- Short-Circuiting
- Boolean Result
- Use Cases
16.5.17 findFirst() and findAny()
- Finding Elements
- Optional Result
- Short-Circuiting
- Parallel Streams
16.5.18 min() and max()
- Finding Extremes
- Comparator Required
- Optional Result
- Use Cases
16.6 Collectors
16.6.1 Understanding Collectors
- Mutable Reduction
- Collection Creation
- Statistical Operations
- Grouping and Partitioning
16.6.2 toList(), toSet(), toCollection()
- Collecting to Collections
- Specific Collection Type
- Use Cases
16.6.3 toMap()
- Creating Maps
- Key and Value Mappers
- Merge Function
- Handling Duplicates
16.6.4 joining()
- String Concatenation
- Delimiter, Prefix, Suffix
- CharSequence Streams
- Use Cases
16.6.5 counting()
- Counting Elements
- Returns Long
- Downstream Collector
- Use Cases
16.6.6 summingInt(), summingLong(), summingDouble()
- Sum Calculation
- Primitive Specialization
- Statistical Operations
16.6.7 averagingInt(), averagingLong(), averagingDouble()
- Average Calculation
- Returns Double
- Statistical Operations
16.6.8 summarizingInt(), summarizingLong(), summarizingDouble()
- Statistical Summary
- Count, Sum, Min, Max, Average
- Single Pass
- IntSummaryStatistics
16.6.9 groupingBy()
- Grouping Elements
- Classification Function
- Map<K, List>
- Downstream Collectors
16.6.10 partitioningBy()
- Partitioning by Predicate
- Map<Boolean, List>
- Two Groups
- Downstream Collectors
16.6.11 mapping()
- Mapping Before Collecting
- Downstream Collector
- Transformation + Collection
16.6.12 reducing()
- Custom Reduction
- Similar to Stream.reduce()
- Downstream Collector
16.7 Parallel Streams
16.7.1 Understanding Parallel Streams
- Fork/Join Framework
- Multi-Core Processing
- Performance Benefits
- Thread Safety Concerns
16.7.2 Creating Parallel Streams
- parallelStream()
- parallel()
- Converting Sequential to Parallel
16.7.3 Parallel vs Sequential
- When to Use Parallel
- Performance Considerations
- Overhead
- Data Size
16.7.4 Parallel Stream Pitfalls
- Thread Safety
- Side Effects
- Stateful Operations
- Order Dependency
16.8 Optional Class
16.8.1 Understanding Optional
- Container for Nullable Values
- Avoiding NullPointerException
- Explicit Null Handling
- Functional Approach
16.8.2 Creating Optional
- Optional.of()
- Optional.ofNullable()
- Optional.empty()
16.8.3 Checking for Value
- isPresent()
- isEmpty() (Java 11+)
- Usage Patterns
16.8.4 Retrieving Values
- get()
- orElse()
- orElseGet()
- orElseThrow()
16.8.5 Optional Operations
- map()
- flatMap()
- filter()
- Chaining Operations
16.8.6 Optional Best Practices
- Never Return null for Optional
- Don't Use get() Without Check
- Prefer Functional Methods
- When Not to Use Optional
16.9 Date and Time API (java.time)
16.9.1 Introduction to New Date/Time API
- Problems with java.util.Date
- Immutability
- Thread Safety
- Clear API
16.9.2 LocalDate
- Date Without Time
- Creating Instances
- Date Operations
- Formatting and Parsing
16.9.3 LocalTime
- Time Without Date
- Creating Instances
- Time Operations
- Precision
16.9.4 LocalDateTime
- Date and Time
- Combining LocalDate and LocalTime
- Operations
- Use Cases
16.9.5 ZonedDateTime
- Date/Time with Time Zone
- Time Zone Handling
- Daylight Saving Time
- International Applications
16.9.6 Instant
- Point on Timeline
- Epoch Milliseconds
- Machine Time
- Timestamps
16.9.7 Duration
- Time-Based Amount
- Between Times
- Hours, Minutes, Seconds
- Arithmetic Operations
16.9.8 Period
- Date-Based Amount
- Between Dates
- Years, Months, Days
- Date Arithmetic
16.9.9 DateTimeFormatter
- Formatting Dates/Times
- Parsing Strings
- Predefined Formatters
- Custom Patterns
16.9.10 Temporal Adjusters
- TemporalAdjuster Interface
- Predefined Adjusters
- firstDayOfMonth()
- nextOrSame()
- Custom Adjusters
16.10 Default Methods in Interfaces
16.10.1 Understanding Default Methods
- Interface Evolution
- Backward Compatibility
- default Keyword
- Implementation in Interface
16.10.2 Multiple Inheritance Issues
- Diamond Problem
- Resolution Rules
- Explicit Selection
- super Keyword
16.11 Static Methods in Interfaces
16.11.1 Interface Static Methods
- Utility Methods
- Helper Methods
- Cannot Override
- Access Through Interface
16.12 Other Java 8 Features
16.12.1 Base64 Encoding/Decoding
- java.util.Base64
- Encoder and Decoder
- URL Safe, MIME
- Use Cases
16.12.2 Nashorn JavaScript Engine
- javax.script Package
- Executing JavaScript
- Java-JavaScript Interop
- Deprecated in Java 11+
16.12.3 CompletableFuture
- Asynchronous Programming
- Callback Chains
- Combining Futures
- Exception Handling
16.12.4 Type Annotations
- @NonNull, @Nullable
- Enhanced Type Checking
- Framework Support
Hands-on Exercises
- Implement lambda expressions for common tasks
- Create custom functional interfaces
- Use method references in different scenarios
- Process collections with Stream API
- Implement complex stream pipelines
- Use Collectors for data aggregation
- Work with Optional to avoid null checks
- Implement date/time calculations
- Create parallel processing examples
- Build data processing pipeline
- Implement filtering and mapping operations
- Create reporting system with streams
Key Takeaways
- Lambda expressions enable functional programming
- Streams provide declarative data processing
- Functional interfaces are lambda targets
- Optional handles null safety elegantly
- New Date/Time API is immutable and clear
- Default methods enable interface evolution
- Parallel streams leverage multi-core processors
Common Mistakes to Avoid
- Overusing lambdas for complex logic
- Ignoring parallel stream pitfalls
- Misusing Optional.get()
- Side effects in stream operations
- Not considering stream laziness
- Wrong choice of sequential vs parallel
- Ignoring exception handling in lambdas
Real-World Applications
- Data processing pipelines
- Report generation
- Filtering and transformation
- Parallel data processing
- API responses with Optional
- Modern date/time handling
- Functional programming patterns
Additional Resources
- Java 8 in Action
- Modern Java in Action
- Oracle Java 8 Documentation
- Effective Java (3rd Edition)
Assessment
- Quiz on Java 8 features
- Practical: Stream API exercises
- Lambda expression challenges
- Optional usage scenarios
- Date/Time API problems
Previous Module
Module 15: File Handling and I/O