Index (for more depth refer drive java-8book)
- Lambda Expression / Functional Interfaces
- Why it was introduced in first place
- What problem does it solve
- Lambda expression internal working
- Need of functional interface
- Ever used functional interface
- Streams
- Why it was introduced in first place
- What problem does it solve
- Sorting, filtering, mapping streams
- Map, flatmap usage
- Interface – Default/Static methods
- Why it was introduced in first place
- What problem does it solve
- Method and Constructor References
- Why it was introduced in first place
- What problem does it solve
- Optional
- Why it was introduced in first place
- What problem does it solve
- Functionality added to Hash-map
- Why it was introduced in first place
- What problem does it solve
- Functionality added for Garbage Collection
- Why it was introduced in first place
- What problem does it solve
- Programming Questions – java -8 features
Lambda
Index
- Introduction
- Representation
How does lambda expressions fit into Java’s type system?
Each lambda corresponds to a given type, specified by an interface. A functional interface must contain exactly one abstract method declaration. Each lambda expression of that type will be matched to this abstract method. Since default methods are not abstract you’re free to add default methods to your functional interface.
Representation and usage:
List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");
Collections.sort(names, new Comparator<String>() {
@Override
public int compare(String a, String b) {
return b.compareTo(a);
}
});
The static utility method Collections.sort accepts a list and a comparator in order to sort the elements of the given list. You often find yourself creating anonymous comparators and pass them to the sort method.
Instead of creating anonymous objects all day long, Java 8 comes with a much shorter syntax, lambda expressions:
Collections.sort(names, (String a, String b) -> {
return b.compareTo(a);
});
Streams
Index
- Introduction
- Definition
- How streams differ from collection
- Representation – ways to obtain stream
- Stream Operations and pipeline
- Parallelism
- FAQs
Stream –
Definition – It conveys elements from a source such as a data structure, an array, a generator function, or an I/O channel, through a pipeline of computational operations.
Description in context to java and programming-
The package java.util.stream provides the classes to support functional-style operations on streams of elements, such as map-reduce transformations on collections. For example:
int sum = widgets.stream()
.filter(b -> b.getColor() == RED)
.mapToInt(b -> b.getWeight())
.sum();
Here we use widgets, a Collection<Widget>, as a source for a stream, and then perform a filter-map-reduce on the stream to obtain the sum of the weights of the red widgets.
Streams differ from collections in several ways:
- No storage. A stream is not a data structure that stores elements; instead, it conveys elements from a source such as a data structure, an array, a generator function, or an I/O channel, through a pipeline of computational operations.
- Functional in nature. An operation on a stream produces a result, but does not modify its source. For example, filtering a
Streamobtained from a collection produces a newStreamwithout the filtered elements, rather than removing elements from the source collection.- Q- if it does not modify the source, how is it functional ? Ans- it performs the operation[functions are known for this and not modifying the source])
- Laziness-seeking. Many stream operations, such as filtering, mapping, or duplicate removal, can be implemented lazily, exposing opportunities for optimization. For example, “find the first
Stringwith three consecutive vowels” need not examine all the input strings. Stream operations are divided into intermediate (Stream-producing) operations and terminal (value- or side-effect-producing) operations. Intermediate operations are always lazy.- Q- Meaning of intermediate operations being lazy? Ans – doesn’t go to next operation unless all streams elements consumed(Or intermediate operation task completed e.g find first String with 3 vowels in beginning) (is it – what in case of infinite stream?)[guess based on understanding but doubt based on working senario]
- Q- so how is for loop different from stream pipeline – once we find specific string we can break,
- Ans1- In-build operations are provided, we don’t need to initialise extra variables for looping.
- Ans2- for looping through we need to store the values in DS,in case we have infinite source of data – it will be a challange to store the data in-memory!
- Q2-Is it thread safe-the stream operation? Yes because – Streams are not modifiable i.e one can’t add or remove elements from streams.(ref- gfg) where as once can easily remove/add elements from collection.
- Possibly unbounded. While collections have a finite size, streams need not. Short-circuiting operations such as
limit(n)orfindFirst()can allow computations on infinite streams to complete in finite time. - Consumable. The elements of a stream are only visited once during the life of a stream. Like an
Iterator, a new stream must be generated to revisit the same elements of the source.- E.g https://www.java67.com/2016/03/how-to-use-flatmap-in-java-8-stream.html
- Look for official examples in Oracle website
Streams can be obtained in a number of ways. Some examples include:
- From a
Collectionvia thestream()andparallelStream()methods; - From an array via
Arrays.stream(Object[]); - From static factory methods on the stream classes, such as
Stream.of(Object[]),IntStream.range(int, int)orStream.iterate(Object, UnaryOperator); - The lines of a file can be obtained from
BufferedReader.lines(); - Streams of file paths can be obtained from methods in
Files; - Streams of random numbers can be obtained from
Random.ints(); - Numerous other stream-bearing methods in the JDK, including
BitSet.stream(),Pattern.splitAsStream(java.lang.CharSequence), andJarFile.stream().
Stream Operations and Pipeline
A stream pipeline consists of a source (such as a Collection, an array, a generator function, or an I/O channel); followed by zero or more intermediate operations such as Stream.filter or Stream.map; and a terminal operation such as Stream.forEach or Stream.reduce.
- Intermediate operations return a new stream. They are always lazy; executing an intermediate operation such as
filter()does not actually perform any filtering, but instead creates a new stream that, when traversed, contains the elements of the initial stream that match the given predicate. Traversal of the pipeline source does not begin until the terminal operation of the pipeline is executed. - Terminal operations, such as
Stream.forEachorIntStream.sum, may traverse the stream to produce a result or a side-effect. After the terminal operation is performed, the stream pipeline is considered consumed, and can no longer be used; if you need to traverse the same data source again, you must return to the data source to get a new stream. In almost all cases, terminal operations are eager, completing their traversal of the data source and processing of the pipeline before returning. Only the terminal operationsiterator()andspliterator()are not; these are provided as an “escape hatch” to enable arbitrary client-controlled pipeline traversals in the event that the existing operations are not sufficient to the task. - Processing streams lazily allows for significant efficiencies; in a pipeline such as the filter-map-sum example above, filtering, mapping, and summing can be fused into a single pass on the data, with minimal intermediate state. Laziness also allows avoiding examining all the data when it is not necessary; for operations such as “find the first string longer than 1000 characters”, it is only necessary to examine just enough strings to find one that has the desired characteristics without examining all of the strings available from the source. (This behavior becomes even more important when the input stream is infinite and not merely large.)
- Intermediate operations are further divided into stateless and stateful operations. Stateless operations, such as
filterandmap, retain no state from previously seen element when processing a new element — each element can be processed independently of operations on other elements. Stateful operations, such asdistinctandsorted, may incorporate state from previously seen elements when processing new elements. - Stateful operations may need to process the entire input before producing a result. For example, one cannot produce any results from sorting a stream until one has seen all elements of the stream. As a result, under parallel computation, some pipelines containing stateful intermediate operations may require multiple passes on the data or may need to buffer significant data. Pipelines containing exclusively stateless intermediate operations can be processed in a single pass, whether sequential or parallel, with minimal data buffering.
- some operations are deemed short-circuiting operations. An intermediate operation is short-circuiting if, when presented with infinite input, it may produce a finite stream as a result. A terminal operation is short-circuiting if, when presented with infinite input, it may terminate in finite time. Having a short-circuiting operation in the pipeline is a necessary, but not sufficient, condition for the processing of an infinite stream to terminate normally in finite time.
Parallelism
The stream implementations in the JDK create serial streams unless parallelism is explicitly requested. For example, Collection has methods Collection.stream() and Collection.parallelStream(), which produce sequential and parallel streams respectively; other stream-bearing methods such as IntStream.range(int, int) produce sequential streams but these streams can be efficiently parallelized by invoking their BaseStream.parallel() method. To execute the prior “sum of weights of widgets” query in parallel, we would do:
int sumOfWeights = widgets.parallelStream()
.filter(b -> b.getColor() == RED)mmel, 3. Nginx, 4. Spring Boot, 5. Liel, 3. Nginx, 4. Spring Boot, 5. Li
.mapToInt(b -> b.getWeight())
.sum();
When the terminal operation is initiated, the stream pipeline is executed sequentially or in parallel depending on the orientation of the stream on which it is invoked.
Non-Interference
Streams enable you to execute possibly-parallel aggregate operations over a variety of data sources, including even non-thread-safe collections such as ArrayList. This is possible only if we can prevent interference with the data source during the execution of a stream pipeline. Except for the escape-hatch operations iterator() and spliterator(), execution begins when the terminal operation is invoked, and ends when the terminal operation completes.
List<String> l = new ArrayList(Arrays.asList("one", "two"));
Stream<String> sl = l.stream();
l.add("three");
String s = sl.collect(joining(" "));
First a list is created consisting of two strings: “one”; and “two”. Then a stream is created from that list. Next the list is modified by adding a third string: “three”. Finally the elements of the stream are collected and joined together. Since the list was modified before the terminal collect operation commenced the result will be a string of “one two three”.
Stream Operations summarised:
| Operation | Method Signature | Type | Description | Usage Example |
|---|---|---|---|---|
| filter | Stream<T> filter(Predicate<? super T> p) | Intermediate | Returns a stream of elements that match the given predicate. | |
| map | Stream<R> map(Function<? super T, ? extends R> mapper) | Intermediate | Transforms each element using the provided function. | stream.map(x -> x * 2).collect(Collectors.toList()); |
| flatMap | Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper) | Intermediate | Similar to map, but each input element is mapped to a stream and the resulting streams are concatenated. | stream.flatMap(str -> Arrays.stream(str.split(" "))).distinct().collect(Collectors.toList()); |
| distinct | Stream<T> distinct() | Intermediate | Returns a stream of distinct elements. | stream.distinct().forEach(System.out::println); |
| sorted | Stream<T> sorted() | Intermediate | Returns a stream sorted according to the natural order of elements. | stream.sorted().forEach(System.out::println); |
| peek | Stream<T> peek(Consumer<? super T> action) | Intermediate | Applies the specified action to each element and returns a new stream. | stream.peek(x -> System.out.println("Processing: " + x)).count(); |
| forEach | void forEach(Consumer<? super T> action) | Terminal | Performs an action for each element of the stream. | stream.forEach(System.out::println); |
| toArray | Object[] toArray() | Terminal | Returns an array containing the elements of the stream. | Object[] array = stream.toArray(); |
| collect | R collect(Collector<? super T, A, R> collector) | Terminal | Performs a mutable reduction on the elements of the stream using a Collector. | List<Integer> list = stream.collect(Collectors.toList()); |
| reduce | Optional<T> reduce(BinaryOperator<T> accumulator) | Terminal | Performs a reduction on the elements of the stream using an associative accumulation function and returns an Optional. | Optional<Integer> result = stream.reduce((a, b) -> a * b); |
| count | long count() | Terminal | Returns the count of elements in the stream as a long. | long count = stream.count(); |
| anyMatch | boolean anyMatch(Predicate<? super T> predicate) | Terminal | Returns true if any elements of the stream match the given predicate. | boolean anyMatch = stream.anyMatch(x -> x > 5); |
| allMatch | boolean allMatch(Predicate<? super T> predicate) | Terminal | Returns true if all elements of the stream match the given predicate. | boolean allMatch = stream.allMatch(x -> x > 0); |
| noneMatch | boolean noneMatch(Predicate<? super T> predicate) | Terminal | Returns true if no elements of the stream match the given predicate. | boolean noneMatch = stream.noneMatch(x -> x == 0); |
| findFirst | Optional<T> findFirst() | Terminal | Returns the first element of the stream as an Optional. | Optional<Integer> firstElement = stream.findFirst(); |
| findAny | Optional<T> findAny() | Terminal | Returns any element of the stream as an Optional. | Optional<Integer> anyElement = stream.findAny(); |
Interface Default Methods
Index
- Introduction
- Representation
- Why it was introduced in first place
interface Formula {
double calculate(int a);
default double sqrt(int a) {
return Math.sqrt(a);
}
Why it was introduced in first place
To provide a mechanism for adding new methods to interfaces without breaking existing implementations.
Before Java 8, interfaces in Java could only declare abstract methods, and any class implementing an interface was required to provide concrete implementations for all of those methods. This created challenges when evolving interfaces in existing codebases, as adding a new method to an interface would force all implementing classes to provide an implementation,
Standard library example:
a new method is introduced to a widely used built-in interface like List. Without default methods, every class that implements List would need to be updated to provide an implementation for the new method.
Example
import java.util.Arrays;
import java.util.List;
public class IterableExample {
public static void main(String[] args) {
List<String> myList = Arrays.asList("Java", "is", "powerful", "and", "flexible");
// Using forEach method added to the Iterable interface
myList.forEach(System.out::println);
}
}
//In this example, the forEach method is called on the List class, which implements the Iterable interface.
//The addition of forEach as a default method in the Iterable interface made it possible to perform concise iteration on any class implementing the Iterable interface without the need for boilerplate code.
What key issues it addressed for interface?
- Backward Compatibility: Existing code that implements an interface isn’t required to provide an implementation for new default methods. This ensures backward compatibility with classes that were written before the introduction of the new methods.
- Library Evolution: Developers can add new methods to interfaces in standard Java libraries without breaking existing code that relies on those interfaces.
- Functional Interfaces and Lambda Expressions: Default methods played a crucial role in the adoption of lambda expressions and the functional programming paradigm in Java 8. Functional interfaces are interfaces with a single abstract method, and default methods allow these interfaces to provide additional utility methods without violating their functional nature.
Functional Interface
Index
- Introduction
- Representation
- FunctionalInterfaces
- Predicates
- Function
- Supplier
- Consumer
To ensure that your interface meet the requirements, you should add the @FunctionalInterface annotation. The compiler is aware of this annotation and throws a compiler error as soon as you try to add a second abstract method declaration to the interface.
Example:
@FunctionalInterface
interface Converter {
T convert(F from);
}
Built-in Functional Interfaces (from java.util.function package)
The JDK 1.8 API contains many built-in functional interfaces. Some of them are well known from older versions of Java like Comparator or Runnable. Those existing interfaces are extended to enable Lambda support via the @FunctionalInterface annotation.
Broadly the functional interface can be categorized in following types-
- Predicates
- Functions
- Suppliers
- Consumers
Predicates- Predicates are boolean-valued functions of one argument. The interface contains various default methods for composing predicates to complex logical terms (and, or, negate)
//Syntax : Predicate<TYPE> predicate= [lambda-function with condition on TYPE]
//Example-1
Predicate<String> predicate = (s) -> s.length() > 0;
predicate.test("foo"); // true
predicate.negate().test("foo"); // false
//Example2
Predicate<Boolean> nonNull = Objects::nonNull;
Predicate<Boolean> isNull = Objects::isNull;
Predicate<String> isEmpty = String::isEmpty;
Predicate<String> isNotEmpty = isEmpty.negate();
//Example3 - external Method
import java.util.function.Predicate;
public class PredicateInterfaceExample {
static Boolean checkAge(int age){
if(age>17)
return true;
else return false;
}
public static void main(String[] args){
// Using Predicate interface
Predicate<Integer> predicate = PredicateInterfaceExample::checkAge;
// Calling Predicate method
boolean result = predicate.test(25);
System.out.println(result);
}
} -
Functions-Functions accept one argument and produce a result. Default methods can be used to chain multiple functions together (compose, andThen).
Function<String, Integer> toInteger = Integer::valueOf;
Function<String, String> backToString = toInteger.andThen(String::valueOf);
backToString.apply("123"); // "123"
Suppliers – Suppliers produce a result of a given generic type. Unlike Functions, Suppliers don’t accept arguments.
Supplier<Person> personSupplier = Person::new;
personSupplier.get(); // new Person
Consumers– Consumers represents operations to be performed on a single input argument.
Consumer<Person> greeter = (p) -> System.out.println("Hello, " + p.firstName);
greeter.accept(new Person("Luke", "Skywalker"));
:: Operator – Method and Constructor References
Index
- Introduction
- Representation
Java 8 enables you to pass references of methods or constructors via the :: keyword.
The below example shows how to reference a static method. But we can also reference object methods:
Converter<String, Integer> converter = Integer::parseInt;
Integer converted = converter.convert("123");
System.out.println(converted); // 123
Optional -Handles the Null values
Index
- Introduction
- Representation
private static String someString;// = "someString";
public static void main(String arg[]) {
Optional<String> optinalString = Optional.ofNullable(someString); // of(someString);
if (optinalString.isPresent()) {//If the Optional contains a non-null value, isPresent() returns true.
System.out.println(optinalString.get());
}
// System.out.println(optinalString.isPresent());
System.out.println(optinalString.orElse("DefaultString"));
}
Programming Questions – java -8 features
- Basic Lambda Expression:
- Write a lambda expression that takes two integers and returns their sum.
- Stream Filtering:
- Given a list of strings, use streams to filter out strings that start with the letter “A” and print the remaining strings.
- Mapping with Lambdas:
- Given a list of integers, use streams to square each integer and store the results in a new list.
- Combining Filters and Mapping:
- Given a list of persons (similar to the previous example), use streams to filter out persons with an age less than 25 and print their names.
- Grouping with Streams:
- Given a list of persons, use streams to group them by age and print the groups.
- Sorting with Streams:
- Given a list of strings, use streams to sort them alphabetically and print the sorted list.
- Collectors and Joining:
- Given a list of strings, use streams and collectors to concatenate all the strings into a single comma-separated string.
- Parallel Streams:
- Write a program that demonstrates the use of parallel streams to process a large list of numbers and print the result.
- FlatMap Operation:
- Given a list of lists of integers, use streams and flatMap to flatten the lists and print the unique integers.
- Exception Handling with Streams:
- Write a program using streams that reads a list of numbers as strings, converts them to integers, and handles any NumberFormatException gracefully, printing an error message for invalid input.
Programs:
/**
* 1.Write a lambda expression that takes two integers and returns their sum.
* @return
*/
static int returnSumOFwoInteger(){
int a=5;
int b=10;
SumFunction sumF=(x,y)->x+y;
return sumF.sum(a,b);
}
//Helper Functional-interface
@FunctionalInterface
interface SumFunction{
int sum(int a,int b);
}
/**
* 2.Given a list of strings, use streams to filter out strings that start with the letter "A" and print the remaining strings.
* @param
*/
private static void filterPrefixStingANdPringRest() {
List<String> al= Arrays.asList("Akram","ABhijit","zimba");
al.stream().filter(s->s.startsWith("A")).map(a->a.substring(1)).forEach(System.out::println);
}
/**
* 3.Given a list of integers, use streams to square each integer and store the results in a new list.
* @param
*/
private static void squareListOfIntegers() {
List<Integer> arrINt=Arrays.asList(1,2,3,4,5);
List<Integer> res= arrINt.stream().map(a->a*a).collect(Collectors.toList());
//print
System.out.println(res);
}
/**
* 4.Given a list of persons (similar to the previous example), use streams to filter out persons with an age less than 25 and print their names.
*/
private static void printPersonsNAmeshoseAgeAgreaterThan20() {
List<Person> persons = Arrays.asList(
new Person("Alice", 25),
new Person("Bob", 30),
new Person("Charlie", 28),
new Person("David", 35)
);
persons.stream().filter(p->p.getAge()>26).map(Person::getName).forEach(System.out::println);
}
//helper class
class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
}
/**
* 5.Given a list of persons, use streams to group them by age and print the groups.
*/
private static void printPersonsGroupByAge() {
List<Person> persons = Arrays.asList(
new Person("Alice", 25),
new Person("Bob", 30),
new Person("Charlie", 22),
new Person("David", 35),
new Person("Eva", 30),
new Person("Frank", 22)
);
Map<Integer,List<Person>> groupBYAge=persons.stream().collect(Collectors.groupingBy(Person::getAge));
groupBYAge.forEach((age,group)->{
System.out.println("age::"+age);
group.forEach(p->System.out.println(p.getName()));
});
}
/**
*variation person with - 0-18 in a group - 19-35 in a group
*/
Map<String, List<Person>> pGroupMap=personList.stream().collect(Collectors.groupingBy((p)->{
if(p.getAge()<18) {
return "0-18";
}
if(p.getAge()>18&&p.getAge()<35) {
return "19-35";
}
else {
return "35+";
}
}));
pGroupMap.forEach((group,p)->
System.out.println(group+":"+p));
};
/*by default the value of group is the object itself if none returned in secoind param or else iof returned then that will be the group e.g*/
/*WAP in java 8 streams
class Resource {
int id;
String resourceName;
List skills;
}
count how many resources are their in each skillSet?
--
*/
public class Demo {
public static void main(String[] args) {
List<Resource> resourceList = getListOFResources();
Map<String, Long> mapOFSkills = resourceList.stream().flatMap(r -> r.getSkills().stream()).collect(Collectors.groupingBy(skil -> skil, Collectors.counting()));
mapOFSkills.forEach((group, rl) -> {
System.out.println(group + " Group and resocues " + rl);
});
}
private static List<Resource> getListOFResources() {
Resource r1 = new Resource(1, "Sevaand", Arrays.asList("java", "spring", "springboot"));
Resource r2 = new Resource(2, "aand", Arrays.asList("react", "spring", "springboot"));
Resource r3 = new Resource(3, "Seva", Arrays.asList("typescript", "spring", "springboot"));
Resource r4 = new Resource(4, "Shiva", Arrays.asList("javascript", "spring", "springboot"));
Resource r5 = new Resource(5, "raavan", Arrays.asList("python", "spring", "springboot"));
Resource r6 = new Resource(6, "Rama", Arrays.asList("python", "django"));
Resource r7 = new Resource(7, "laskhamana", Arrays.asList("angular", "javascript", "typescript"));
List<Resource> resourceList = new ArrayList<>();
resourceList.add(r1);
resourceList.add(r2);
resourceList.add(r3);
resourceList.add(r4);
resourceList.add(r5);
resourceList.add(r6);
resourceList.add(r7);
return resourceList;
}
}
/*
output:
spring Group and resocues 5
angular Group and resocues 1
python Group and resocues 2
django Group and resocues 1
java Group and resocues 1
react Group and resocues 1
typescript Group and resocues 2
javascript Group and resocues 2
springboot Group and resocues 5
*/
/**
* 6.Given a list of strings, use streams to sort them alphabetically and print the sorted list.
*/
private static void printSortedListedAlphabetically() {
List<String> arrS=Arrays.asList(
"Bullbog",
"Abra",
"Ka",
"Dabra"
);
arrS.stream().sorted().forEach(System.out::println);
}
//sorting in descending order
//string
arrS.stream().sorted((s1,s2)->s2.compareTo(s1)).forEach(System.out::println);
//objects in descending order
personList.stream().sorted((p1,p2)->p2.getName().compareTo(p1.getName())).forEach(p->System.out.println(p));
/*note :- 1.ByDefault sorted() on stream expects that the class should implement Comparable else you case use overloaded method sorted(Comparator<?> arg)
2.if class overrides comparable -& lambda also mention the sorted order by comparator - lambda's input will be given preference i.e i class has sort by age and lambda has sort by name - result will be sort by name*/
/*Also you can use the overloaded cmethod as follows :
1. personList.stream().sorted(Comparator.comparingInt(Person::getAge).reversed()).forEach(System.out::println);
2. personList.stream().sorted(Comparator.comparing(Person::getName).reversed()).forEach(System.out::println);*/
/**
* 7.Given a list of strings, use streams and collectors to concatenate all the strings into a single comma-separated string.
*/
private static void printListedOfStringAsSIgnleStringSpeatrtedByCOmma() {
List<String> arrS=Arrays.asList(
"Bullbog",
"Abra",
"Ka",
"Dabra"
);
String commnSe=arrS.stream().collect(Collectors.joining(","));
System.out.println(commnSe);
}
/**
* 8.Write a program that demonstrates the use of parallel streams to process a large list of numbers and print the result as sum.
*/
private static void processLargeListedOfIntWithParallelStream() {
List<Integer> numberList = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
int sum= numberList.parallelStream().mapToInt(Integer::intValue).sum();
System.out.println(sum);
}
/**
*9.Given a list of lists of integers, use streams and flatMap to flatten the lists and print the unique integers.
*/
private static void flatListOfLIstAndPrintUniqueValue() {
List<List<Integer>> listOfLists = Arrays.asList(
Arrays.asList(1, 2, 3),
Arrays.asList(4, 5, 6),
Arrays.asList(7, 8, 9)
);
listOfLists.stream().flatMap(List::stream).distinct().forEach(System.out::println);
}
/**
*10.Write a program using streams that reads a list of numbers as strings, converts them to integers, and handles any NumberFormatException gracefully, printing an error message for invalid input..
*/
private static void handleExceptionWIthLambdaFOrStringToIntConversion() {
List<String> numberStrings = Arrays.asList("10", "20", "30", "abc", "40");
numberStrings.stream().map(Java8Features::convertToInt).filter(n -> n != null).forEach(System.out::println);
}
//helper method in same class i.e Java8Features.java
private static Integer convertToInt(String stringInterger) throws RuntimeException {
try {
return Integer.parseInt(stringInterger);
}catch (NumberFormatException nfe ){
System.out.println("nfe"+nfe);
return null;
}
}