LESSER KNOWN FEATURES OF JAVA 8
Java 8 introduced a plethora of features that changed the way we write Java code, such as Streams, Lambdas, and the new Date-Time API. While these are widely recognized, there are several hidden gems that many developers may not be aware of. This article dives into ten such lesser-known Java 8 features with examples.
1. Collectors.teeing
The Collectors.teeing method allows us to perform two separate stream operations and combine their results. This is particularly useful when we need to calculate two different pieces of information from the same stream.
Example:
import java.util.stream.Collectors;
import java.util.List;
import java.util.stream.Stream;
public class TeeingExample {
public static void main(String[] args) {
Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5);
var result = numbers.collect(Collectors.teeing(
Collectors.summingInt(Integer::intValue),
Collectors.counting(),
(sum, count) -> "Sum: " + sum + ", Count: " + count
));
System.out.println(result); // Output: Sum: 15, Count: 5
}
}
?
2. Optional with or and ifPresentOrElse
Java 8's Optional API is well-known, but the addition of methods like or and ifPresentOrElse makes it more powerful for conditional logic.
Example:
?
import java.util.Optional;
public class OptionalEnhancements {
public static void main(String[] args) {
Optional<String> optionalValue = Optional.ofNullable(null);
optionalValue.ifPresentOrElse(
value -> System.out.println("Value: " + value),
() -> System.out.println("Value is absent")
);
String fallback = optionalValue.or(() -> Optional.of("Fallback value")).get();
System.out.println(fallback); // Output: Fallback value
}
}
3. Stream.generate with iterate
Many developers use Stream.generate for infinite streams, but pairing it with iterate allows for more control, especially with predicates for termination.
Example:
?
import java.util.stream.Stream;
public class StreamGenerateExample {
public static void main(String[] args) {
Stream.iterate(1, n -> n <= 10, n -> n + 1)
.forEach(System.out::println);
// Output: 1 2 3 4 5 6 7 8 9 10
}
}
?
4. Method References with Constructors
While method references for static and instance methods are commonly used, constructor references are less well-known. These allow us to create new objects in a concise way.
Example:
领英推荐
import java.util.List;
import java.util.stream.Collectors;
class Person {
String name;
public Person(String name) {
this.name = name;
}
@Override
public String toString() {
return name;
}
}
public class ConstructorReferenceExample {
public static void main(String[] args) {
List<String> names = List.of("Alice", "Bob", "Charlie");
List<Person> people = names.stream()
.map(Person::new)
.collect(Collectors.toList());
people.forEach(System.out::println);
// Output: Alice, Bob, Charlie
}
}
?
5. StringJoiner for Flexible String Joining
While String.join() is widely known, StringJoiner offers additional flexibility for joining strings with optional prefixes and suffixes.
Example:
import java.util.StringJoiner;
public class StringJoinerExample {
public static void main(String[] args) {
StringJoiner joiner = new StringJoiner(", ", "[", "]");
joiner.add("Java");
joiner.add("Python");
joiner.add("C++");
System.out.println(joiner); // Output: [Java, Python, C++]
}
}
6. Custom Predicate Combinators
Java 8 allows us to combine Predicates using methods like and, or, and negate to build complex conditions in a readable way.
Example:
import java.util.function.Predicate;
public class PredicateExample {
public static void main(String[] args) {
Predicate<String> isNotEmpty = s -> s != null && !s.isEmpty();
Predicate<String> isLongerThan5 = s -> s.length() > 5;
Predicate<String> complexCondition = isNotEmpty.and(isLongerThan5);
System.out.println(complexCondition.test("HelloWorld")); // Output: true
System.out.println(complexCondition.test("Hi")); // Output: false
}
}?
7. Using Spliterator
The Spliterator is a Java 8 interface for splitting elements in a stream, enabling parallel computation and fine-grained control over iteration.
Example:
import java.util.Spliterator;
import java.util.stream.Stream;
public class SpliteratorExample {
public static void main(String[] args) {
Stream<String> stream = Stream.of("Java", "Python", "C++", "Go");
Spliterator<String> spliterator = stream.spliterator();
spliterator.forEachRemaining(System.out::println);
// Output: Java Python C++ Go
}
}?
8. Grouping with Collectors.partitioningBy
While Collectors.groupingBy is well-known, partitioningBy is useful for dividing elements into two groups based on a predicate.
Example:
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class PartitioningExample {
public static void main(String[] args) {
List<Integer> numbers = List.of(1, 2, 3, 4, 5, 6);
Map<Boolean, List<Integer>> partitioned = numbers.stream()
.collect(Collectors.partitioningBy(n -> n % 2 == 0));
System.out.println(partitioned);
// Output: {false=[1, 3, 5], true=[2, 4, 6]}
}
}
?