Java 8 Features
Lambda Expressions
Core Understanding
Lambda expressions in Java 8 allow treating functionality as a method argument, or code as data. They enable writing concise, functional-style code by representing behaviors inline, without the need for verbose anonymous class syntax.
✅ Good Example
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.forEach(name -> System.out.println(name));
❌ Bad Example
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
for (String name : names) {
System.out.println(name);
}
The bad example uses a traditional for loop instead of a lambda expression, leading to more verbose and less expressive code.
Best Practices
- Use lambda expressions to implement functional interfaces
- Keep lambda expressions concise and focused on a single responsibility
- Use method references (
::
) when appropriate for better readability - Avoid side effects in lambda expressions to maintain referential transparency
Use Cases
- Implementing event listeners and callbacks
- Defining behaviors for functional-style operations (e.g., map, filter, reduce)
- Creating fluent and expressive APIs
Streams API
Core Understanding
The Streams API, introduced in Java 8, provides a declarative and functional approach to processing collections of data. It allows chaining multiple operations to express complex data processing pipelines in a readable and concise manner.
✅ Good Example
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
long count = names.stream()
.filter(name -> name.length() > 4)
.map(String::toUpperCase)
.sorted()
.count();
❌ Bad Example
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
int count = 0;
List<String> filteredNames = new ArrayList<>();
for (String name : names) {
if (name.length() > 4) {
filteredNames.add(name.toUpperCase());
}
}
Collections.sort(filteredNames);
count = filteredNames.size();
The bad example uses imperative-style code with explicit loops and temporary collections, making it harder to understand and maintain compared to the declarative streams approach.
Best Practices
- Prefer using streams over explicit loops for data processing
- Leverage built-in stream operations for common tasks (e.g., filtering, mapping, reducing)
- Use parallel streams judiciously, considering the overhead and potential performance impact
- Avoid stateful operations and side effects within stream pipelines
Use Cases
- Filtering, mapping, and reducing collections
- Implementing data processing pipelines
- Performing bulk operations on data
- Enabling parallel processing of large datasets
Date and Time API
Core Understanding
The Date and Time API, introduced in Java 8, provides a comprehensive and user-friendly library for handling dates, times, and durations. It addresses the shortcomings of the old java.util.Date
and java.util.Calendar
classes by offering a more intuitive and immutable approach.
✅ Good Example
LocalDate today = LocalDate.now();
LocalDate birthday = LocalDate.of(1990, Month.JANUARY, 1);
Period age = Period.between(birthday, today);
System.out.println("Age: " + age.getYears() + " years");
❌ Bad Example
Date today = new Date();
Calendar birthday = Calendar.getInstance();
birthday.set(1990, Calendar.JANUARY, 1);
int age = today.getYear() - birthday.get(Calendar.YEAR);
System.out.println("Age: " + age + " years");
The bad example uses the old Date
and Calendar
classes, which are mutable, error-prone, and less expressive compared to the new Date and Time API.
Best Practices
- Use the appropriate classes for different use cases (e.g.,
LocalDate
,LocalTime
,LocalDateTime
,ZonedDateTime
) - Prefer immutable objects and avoid mutating date and time instances
- Use
Duration
andPeriod
for representing time-based and date-based amounts, respectively - Leverage the rich set of operations provided by the API for date and time manipulations
Use Cases
- Handling dates, times, and durations in application logic
- Performing date and time calculations and comparisons
- Formatting and parsing dates and times
- Working with time zones and calendar systems
Interview Questions
Q1: What is the difference between a lambda expression and an anonymous inner class? A1: Lambda expressions provide a concise way to represent a single abstract method interface (functional interface) inline, without the need for explicit class declaration. Anonymous inner classes, on the other hand, allow implementing any interface or extending any class, but with more verbose syntax. Lambda expressions are more suitable for simple, single-method interfaces, while anonymous inner classes offer more flexibility for complex implementations.
Q2: How do you create a parallel stream from a collection?
A2: To create a parallel stream from a collection, you can call the parallelStream()
method on the collection. For example:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> parallelStream = numbers.parallelStream();
Alternatively, you can convert a sequential stream to a parallel stream using the parallel()
method:
Stream<Integer> parallelStream = numbers.stream().parallel();
Q3: What is the difference between LocalDate
, LocalTime
, and LocalDateTime
in the Java 8 Date and Time API?
A3:
LocalDate
represents a date without a time or time zone. It is suitable for representing dates like birthdays or anniversaries.LocalTime
represents a time without a date or time zone. It is useful for representing times like "12:30 PM" or "09:45:30".LocalDateTime
represents a combination of date and time, without a time zone. It is used when you need to represent a specific moment in time, like "2023-05-18T14:30:00".
These classes are part of the java.time
package and offer various methods for creating, manipulating, and formatting dates and times.