There are three components to a template expression:
A processor
STR Template Processor
A template which contains the data with the embedded expressions
e,g, “\{var_name}“
A dot (.) character
String interpolationUsingSTRProcessor(String feelsLike, String temperature, String unit) {
return STR
. "Today's weather is \{ feelsLike }, with a temperature of \{ temperature } degrees \{ unit }" ;
}
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 Stream obtained from a collection produces a new Stream without 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 String with 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) or findFirst() 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.
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.forEach or IntStream.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 operations iterator() and spliterator() 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 filter and map, 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 as distinct and sorted, 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.
stream.filter(x -> x > 5).forEach(System.out::println);
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.
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);
}
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)
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:
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;
}
}
Q-What do you understand by Executor Framework in Java
Executor Framework in java has been introduced in JDK 5. Executor Framework handles creation of thread, creating the thread pool and checking health while running and also terminates if needed.
2: What is the role of ExecutorService in Java?
ExecutorService provides different methods to start and terminate thread. There are two methods execute() and submit() in ExecutorService. Execute() method is used for threads which is Runnable and submit() method is used for Callable threads.
3: What is Executors in java Executor Framework?
Ans: Executors is a factory that provides the methods to return ExecutorService, ScheduledExecutorService, ThreadFactory. Find some method details.
newFixedThreadPool(): It returns the pool with fixed number of size. We need to pass the number of threads to this method. If concurrently task are submitted more than the pool size, then rest of task need to wait in queue. It returns ExecutorService. newScheduledThreadPool: This also creates a fixed size pool but it can schedule the thread to run after some defined delay. It is useful to schedule the task. It returns ScheduledExecutorService. newCachedThreadPool(): There is no fixed size of this pool. Thread will be created at run time and if there is no task it will alive for 60 second and then die. For short lived threads this pool works good. It returns ExecutorService.
4: What is the role of FutureTask and Future in java?
Ans: FutureTask is a cancellable asynchronous computation in java. It can cancel the task which is running. Once the FutureTask will be cancelled, it cannot be restarted. Future is result of asynchronous computation. Future checks if task is complete and if completed it gets the output.
5: What is difference between shutdownNow() and shutdown() in Executor Framework in java?
shutdown() and shutdownNow() methods belongs to ExecutorService. shutdown() method tries to stop the threads and do not accept new task to execute but it completes the execution which has been submitted. shutdownNow() methods also tries to stop the running threads and will not execute any task which has been submitted but not started.
6: How to terminate a thread in Executor Framework in java?
ExecutorService provides a method awaitTermination(long timeout, TimeUnit unit) that takes time and unit of time as an arguments. After that time thread pool is terminated. Suppose we need to terminate a task just now, then we can do as
Modern Java syntax (records, sealed classes), security tightening, pattern matching,
5
Java-21
Sept2023
LTS
Major LTS with virtual threads, structured concurrency, foreign memory API, and pattern matching finalization—a powerful upgrade.
6
Java-25
Sept2025
LTS
– Full support for generics over primitives (Valhalla) – Finalized string templates – Universal generics – Foreign function and memory API (Final) – Pattern matching (Final)
Features in a version are release under one of following category:
GA (General release)
production-ready versions containing all the finalized features
Preview Features:These features are not considered final and could be changed, removed, or have their APIs modified in future releases.