This collection of 25 Java exercises takes you from your very first lambda expression through the full set of built-in functional interfaces and their use inside the Stream API.
- Early exercises cover writing lambdas for custom and built-in interfaces like
RunnableandComparator - The middle set works through
Predicate,Function,Supplier, andConsumer, including composing and chaining them withand(),negate(), andandThen(). - The later exercises apply all of this inside stream pipelines:
map(),filter(),reduce(),flatMap(),partitioningBy(), andBiConsumerfor iterating maps.
Each exercise includes a Problem Statement, Purpose, Hint, and a Solution with Explanation that breaks down exactly what each lambda is doing and why.
- Also, See: Java Exercises with over 20+ topic-wise sets and 575+ coding questions to practice.
- Practice questions using our Online Java Compiler
+ Table of Contents (25 Exercises)
Table of contents
- Exercise 1: Basic Lambda Implementation
- Exercise 2: Sorting Strings
- Exercise 3: Runnable Lambda
- Exercise 4: Math Operation Interface
- Exercise 5: Check for Empty
- Exercise 6: List Iteration
- Exercise 7: Custom Comparator
- Exercise 8: Filtering via Lambda
- Exercise 9: Map to Lengths
- Exercise 10: Filter Even Numbers
- Exercise 11: Sum of Squares (Reduce)
- Exercise 12: Collect to Map
- Exercise 13: Distinct and Sorted
- Exercise 14: String Joining
- Exercise 15: FlatMap Example
- Exercise 16: Find First Match
- Exercise 17: Partitioning Data
- Exercise 18: Predicate Filtering
- Exercise 19: Chained Predicates
- Exercise 20: Function Transformation
- Exercise 21: Function Chaining
- Exercise 22: Supplier Factory
- Exercise 23: Supplier Randomizer
- Exercise 24: Consumer Printing
- Exercise 25: BiConsumer Mapping
Exercise 1: Basic Lambda Implementation
Problem Statement: Write a lambda expression that implements a custom functional interface StringProcessor with a method String process(String s), which converts a given string to uppercase.
Purpose: This exercise helps you practice writing a lambda expression that targets a custom functional interface, the foundation every other lambda exercise builds on.
Given Input: "hello world"
Expected Output: HELLO WORLD
▼ Hint
@FunctionalInterface marks an interface with exactly one abstract method, so a lambda can supply the body for process() directly, taking a String parameter and returning it converted with toUpperCase().
▼ Solution & Explanation
Explanation:
@FunctionalInterface: Marks the interface as having exactly one abstract method, so the compiler can verify it is a valid target for a lambda.s -> s.toUpperCase(): Lambda expression implementingprocess(), taking aStringparameter and returning it converted to uppercase.toUpperCase.process("hello world"): Invokes the lambda body just like calling any interface method.- Alternative: You could write the lambda with an explicit block body,
s -> { return s.toUpperCase(); }, though the expression form is shorter for a single statement.
Exercise 2: Sorting Strings
Problem Statement: Given a list of names, use List.sort() and a lambda expression to sort them in reverse alphabetical order.
Purpose: This exercise helps you practice supplying a Comparator as a lambda instead of writing a separate class, a pattern used constantly when sorting collections.
Given Input: ["Charlie", "Alice", "Bob", "Diana"]
Expected Output: [Diana, Charlie, Bob, Alice]
▼ Hint
List.sort()takes aComparator, and a lambda can supply that comparator directly without a separate class.compareTo()returns a negative, zero, or positive value depending on order.- Swapping the arguments,
b.compareTo(a)instead ofa.compareTo(b), flips ascending order into descending order.
▼ Solution & Explanation
Explanation:
names.sort((a, b) -> b.compareTo(a)): Supplies aComparator<String>as a lambda, whereaandbrepresent any two elements being compared.b.compareTo(a): Reverses the natural ordering ofString.compareTo(), producing descending alphabetical order.- in-place sorting:
List.sort()mutates the original list directly rather than returning a new one. - Alternative: You could use
names.sort(Comparator.reverseOrder())for the same result without writing the comparison logic yourself.
Exercise 3: Runnable Lambda
Problem Statement: Create and start a new thread using a lambda expression instead of an anonymous Runnable class.
Purpose: This exercise helps you practice recognizing that Runnable is already a functional interface, so its usual anonymous class implementation can be replaced with a lambda.
Expected Output: Running in a separate thread
▼ Hint
Runnable has a single abstract method, run(), which takes no arguments and returns nothing, so a no-argument lambda like () -> ... can be passed directly to the Thread constructor.
▼ Solution & Explanation
Explanation:
() -> System.out.println(...): Lambda implementingRunnable‘s single abstract methodrun(), taking no arguments sincerun()itself takes none.new Thread(() -> ...): Passes the lambda directly to theThreadconstructor, which normally expects aRunnableobject.thread.join(): Waits for the thread to finish beforemain()exits, guaranteeing the output is printed.- Alternative: You could still write out an anonymous
Runnableclass withnew Runnable() { public void run() {...} }, but the lambda form removes that boilerplate entirely.
Exercise 4: Math Operation Interface
Problem Statement: Create a functional interface MathOperation with int operate(int a, int b). Use lambdas to implement addition, subtraction, and multiplication.
Purpose: This exercise helps you practice assigning several different lambdas to variables of the same functional interface type, then invoking them interchangeably.
Given Input: a = 10, b = 5
Expected Output:
Addition: 15 Subtraction: 5 Multiplication: 50
▼ Hint
- Declare
operate(int a, int b)as the single abstract method insideMathOperation. - Assign a different lambda to a
MathOperationvariable for each arithmetic operation. - Call
operate()on each variable the same way, regardless of which operation it holds.
▼ Solution & Explanation
Explanation:
(a, b) -> a + b: Lambda implementingoperate()to return the sum of its two parameters.MathOperation addition = ...: Assigns the lambda to a variable typed as the functional interface, so it can be passed around and invoked like any object.addition.operate(10, 5): Calls the lambda body with the given arguments, exactly as ifoperate()had been implemented by a full class.- Alternative: You could write a single method that accepts a
MathOperationparameter and applies it, letting the caller decide which lambda to pass in at call time.
Exercise 5: Check for Empty
Problem Statement: Write a lambda expression to check if a given string is empty or null.
Purpose: This exercise helps you practice using the built-in Predicate functional interface, which is designed exactly for a method that takes one argument and returns a boolean.
Given Input: "", null, "hello"
Expected Output:
true true false
▼ Hint
Use Predicate<String> and check s == null || s.isEmpty(), relying on short-circuit evaluation so isEmpty() is never called on a null reference.
▼ Solution & Explanation
Explanation:
Predicate<String>: A built-in functional interface with a single abstract methodtest(T t)that returns a boolean.s == null || s.isEmpty(): Checks fornullfirst using short-circuit evaluation, soisEmpty()is never called on anullreference.isEmpty.test(...): Invokes the lambda for each input string, returningtruefor both an empty string and anullreference.- Alternative: You could use
s == null || s.isBlank()instead, which also treats a string containing only whitespace as empty.
Exercise 6: List Iteration
Problem Statement: Use the forEach() method and a lambda expression to print all elements of an ArrayList<Integer> multiplied by 2.
Purpose: This exercise helps you practice using forEach() with a Consumer lambda, a shorter alternative to writing an explicit for loop when you only need to act on each element.
Given Input: [1, 2, 3, 4, 5]
Expected Output:
2 4 6 8 10
▼ Hint
forEach() takes a Consumer<T> lambda that receives each element in turn, so multiply the value by 2 inside the lambda body right before printing it.
▼ Solution & Explanation
Explanation:
numbers.forEach(...): Calls the given lambda once for every element in the list, in iteration order.n -> System.out.println(n * 2): Lambda implementingConsumer<Integer>, doubling each element before printing it.- no return value:
forEach()expects aConsumer, whose single abstract methodaccept()returns void, matching the lambda’s void body. - Alternative: You could use a stream instead,
numbers.stream().map(n -> n * 2).forEach(System.out::println), which separates the doubling step from the printing step.
Exercise 7: Custom Comparator
Problem Statement: Given a list of Employee objects (with name and salary attributes), write a lambda expression to sort them by salary in ascending order.
Purpose: This exercise helps you practice writing a Comparator lambda that compares a specific field of a custom object, rather than the object’s natural ordering.
Expected Output: [Bob: 55000.0, Charlie: 65000.0, Alice: 75000.0]
▼ Hint
- Implement
Comparator<Employee>as a lambda taking twoEmployeeparameters,e1ande2. - Use
Double.compare(e1.getSalary(), e2.getSalary())instead of subtracting the salaries directly, since subtraction can lose precision with doubles. - Pass the lambda straight into
employees.sort()to reorder the list in place.
▼ Solution & Explanation
Explanation:
(e1, e2) -> Double.compare(...): Lambda implementingComparator<Employee>‘scompare()method, comparing two employees by their salary field.Double.compare(a, b): Returns a negative, zero, or positive value based on the natural ordering of the two doubles, exactly whatsort()needs.employees.sort(...): Reorders the list in place using the comparator, from lowest salary to highest.- Alternative: You could use
employees.sort(Comparator.comparingDouble(Employee::getSalary))for a shorter, method reference based version of the same comparator.
Exercise 8: Filtering via Lambda
Problem Statement: Write a method that takes a list of integers and a custom lambda condition to filter out numbers that don’t match the condition (e.g., filter out odd numbers).
Purpose: This exercise helps you practice accepting a Predicate as a method parameter, which lets the caller supply any filtering rule without the method needing to know it in advance.
Given Input: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Expected Output: Even numbers: [2, 4, 6, 8, 10]
▼ Hint
- Accept a
Predicate<Integer>parameter in the method signature so the caller can supply any condition. - Loop through the input list and call
condition.test(number)for each element. - Add the element to the result list only when the condition returns
true.
▼ Solution & Explanation
Explanation:
Predicate<Integer> condition: Lets the method accept any single-argument boolean check as an argument, without knowing in advance what that check will be.condition.test(number): Evaluates the caller’s lambda against the current element.n -> n % 2 == 0: The specific lambda passed in at the call site, keeping only even numbers.- Alternative: You could rewrite
filterNumbers()using a stream,numbers.stream().filter(condition).collect(Collectors.toList()), which removes the manual loop and result list entirely.
Exercise 9: Map to Lengths
Problem Statement: Given a list of strings, use map() to create a new list containing the length of each string.
Purpose: This exercise helps you practice using the stream map() operation to transform every element of a collection into a different type or value.
Given Input: ["apple", "banana", "kiwi", "grape", "fig"]
Expected Output: Lengths: [5, 6, 4, 5, 3]
▼ Hint
map() transforms each stream element with the given function, so passing the String::length method reference converts every string into its length.
▼ Solution & Explanation
Explanation:
words.stream(): Converts the list into aStream<String>that supports functional style operations..map(String::length): AppliesString‘slength()method to every element, producing a new stream ofIntegervalues..collect(Collectors.toList()): Gathers the transformed stream elements back into a concreteList<Integer>.- Alternative: You could write the mapping step as a lambda instead of a method reference,
.map(s -> s.length()), which behaves identically but is slightly more verbose.
Exercise 10: Filter Even Numbers
Problem Statement: Given a list of integers, use filter() to extract all even numbers into a new list.
Purpose: This exercise helps you practice using the stream filter() operation to keep only the elements that satisfy a given condition.
Given Input: [11, 22, 33, 44, 55, 66, 77, 88]
Expected Output: Even numbers: [22, 44, 66, 88]
▼ Hint
filter() keeps only the elements for which the given predicate returns true, so testing n % 2 == 0 keeps just the even numbers in the resulting stream.
▼ Solution & Explanation
Explanation:
numbers.stream(): Converts the list into a stream so intermediate operations likefilter()can be chained..filter(n -> n % 2 == 0): Keeps only the elements where the modulo check evaluates totrue, discarding everything else..collect(Collectors.toList()): Terminates the stream and gathers the surviving elements into a newList<Integer>.- Alternative: You could use
numbers.stream().filter(n -> n % 2 == 0).toList()(Java 16+) instead ofCollectors.toList(), which returns an unmodifiable list with less boilerplate.
Exercise 11: Sum of Squares (Reduce)
Problem Statement: Given a list of integers, use map() and reduce() to find the sum of the squares of all elements.
Purpose: This exercise helps you practice combining map() with reduce(), transforming each element before folding the whole stream down into a single accumulated value.
Given Input: [1, 2, 3, 4, 5]
Expected Output: Sum of squares: 55
▼ Hint
map(n -> n * n)transforms every element into its square before reduction happens.reduce(identity, accumulator)combines all stream elements into a single value, starting from the identity.Integer::sumis a method reference that adds two integers together, matching the accumulator’s expected signature.
▼ Solution & Explanation
Explanation:
.map(n -> n * n): Squares every element in the stream before reduction takes place..reduce(0, Integer::sum): Starts from an identity value of 0 and repeatedly combines it with each squared value using addition.Integer::sum: Method reference equivalent to the lambda(a, b) -> a + b.- Alternative: You could use
.mapToInt(n -> n * n).sum()instead, which avoids boxing toIntegerand returns a primitiveintdirectly.
Exercise 12: Collect to Map
Problem Statement: Given a list of Person objects (with ID and Name), use collect() and Collectors.toMap() to convert the list into a Map<Integer, String>.
Purpose: This exercise helps you practice converting a stream of objects directly into a Map, choosing which field becomes the key and which becomes the value.
Expected Output: {1=Alice, 2=Bob, 3=Charlie}
▼ Hint
Collectors.toMap() takes two functions, one to derive the key from each element and one to derive the value, and returns a Map built from the stream.
▼ Solution & Explanation
Explanation:
Collectors.toMap(Person::getId, Person::getName): Builds a map by applying the key function and value function to every stream element.Person::getId/Person::getName: Method references supplying the key and value extraction logic without writing explicit lambdas.- resulting
Map<Integer, String>: Keys come from each person’s ID, values come from their name. - Alternative: You could pass a third merge function argument to
toMap(),(existing, replacement) -> existing, to handle the case where two people share the same ID instead of throwing an exception.
Exercise 13: Distinct and Sorted
Problem Statement: Given a list of numbers with duplicates, use a stream to remove duplicates, sort them in ascending order, and collect them into a list.
Purpose: This exercise helps you practice chaining multiple intermediate stream operations, distinct() and sorted(), before collecting the final result.
Given Input: [5, 3, 8, 3, 1, 5, 9, 1]
Expected Output: [1, 3, 5, 8, 9]
▼ Hint
distinct()removes duplicate values by relying onequals(), keeping only the first occurrence of each value.sorted()with no arguments orders the remaining elements using their natural ordering.- Chain both operations before
collect()so duplicates are removed first and the smaller remaining set is then sorted.
▼ Solution & Explanation
Explanation:
.distinct(): Removes duplicate elements, keeping just one copy of each distinct value..sorted(): Arranges the remaining elements in ascending natural order, sinceIntegerimplementsComparable.- operation order: Removing duplicates before sorting means
sorted()has fewer elements to process. - Alternative: You could reverse the order and call
.sorted().distinct(), which produces the same final result here, though sorting first can be less efficient on very large inputs since it still has to process every duplicate.
Exercise 14: String Joining
Problem Statement: Given a list of programming languages (e.g., ["Java", "Python", "C++"]), use a stream to join them into a single string separated by commas (e.g., "Java, Python, C++").
Purpose: This exercise helps you practice using Collectors.joining(), a purpose-built collector for combining stream elements into a single delimited string.
Given Input: ["Java", "Python", "C++"]
Expected Output: Java, Python, C++
▼ Hint
Collectors.joining(delimiter) concatenates every stream element into a single String, inserting the given delimiter between elements without adding one after the last.
▼ Solution & Explanation
Explanation:
Collectors.joining(", "): Concatenates the stream elements into oneString, placing", "between each pair of adjacent elements.- no trailing delimiter:
joining()only inserts the delimiter between elements, so the final string never ends with one. languages.stream().collect(...): The standard pattern for turning a stream back into a single collected result.- Alternative: You could pass a prefix and suffix too,
Collectors.joining(", ", "[", "]"), to wrap the joined result in brackets like a formatted list.
Exercise 15: FlatMap Example
Problem Statement: Given a list of lists of integers List<List<Integer>>, use flatMap() to flatten the structure into a single List<Integer>.
Purpose: This exercise helps you practice using flatMap() to merge several nested streams into one continuous stream, instead of ending up with a stream of streams.
Given Input: [[1, 2, 3], [4, 5], [6, 7, 8, 9]]
Expected Output: [1, 2, 3, 4, 5, 6, 7, 8, 9]
▼ Hint
flatMap() converts each inner list into its own stream with List::stream, then merges all of those streams into one continuous stream of individual elements.
▼ Solution & Explanation
Explanation:
nestedNumbers.stream(): Produces aStream<List<Integer>>, where each element is itself a list..flatMap(List::stream): Converts each inner list into a stream of its elements, then flattens all of those streams into a single stream.- difference from
map():map()would produce aStream<Stream<Integer>>, whileflatMap()merges the nested streams into one flat stream automatically. - Alternative: You could achieve the same flattening with a nested
forloop that adds every inner element to a newArrayList, though that removes the declarative, single-pipeline style of the stream version.
Exercise 16: Find First Match
Problem Statement: Given a list of words, find the first word that starts with the letter ‘J’ using streams. Return an Optional<String>.
Purpose: This exercise helps you practice using a short-circuiting terminal operation, findFirst(), and handling its result safely with Optional instead of risking a null.
Given Input: ["Apple", "Banana", "Java", "Jasmine", "Kiwi"]
Expected Output: Java
▼ Hint
filter(word -> word.startsWith("J"))keeps only the words that begin with the target letter.findFirst()is a short-circuiting terminal operation that stops as soon as it finds the first matching element, returning it wrapped in anOptional.Optional.orElse(...)safely unwraps the result, supplying a fallback value if no word matched at all.
▼ Solution & Explanation
Explanation:
.filter(word -> word.startsWith("J")): Narrows the stream down to only the words starting with “J”..findFirst(): Returns anOptional<String>containing the first surviving element, or an emptyOptionalif none matched.firstJWord.orElse("No match found"): Safely reads the value out of theOptionalwithout risking aNullPointerException.- Alternative: You could use
.anyMatch(word -> word.startsWith("J"))if you only needed to know whether a match exists, without needing the actual matching word.
Exercise 17: Partitioning Data
Problem Statement: Use Collectors.partitioningBy() to split a list of integers based on whether each number is even or odd.
Purpose: This exercise helps you practice using partitioningBy(), which always produces a two-way split keyed by true and false, unlike the open-ended grouping of groupingBy().
Given Input: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Expected Output:
Even: [2, 4, 6, 8, 10] Odd: [1, 3, 5, 7, 9]
▼ Hint
Collectors.partitioningBy() takes a Predicate and returns a single Map<Boolean, List<T>>, where the true key holds elements matching the predicate and the false key holds everything else.
▼ Solution & Explanation
Explanation:
Collectors.partitioningBy(n -> n % 2 == 0): Splits the stream into exactly two groups based on whether each element satisfies the predicate.partitioned.get(true): Retrieves the list of elements for which the predicate returnedtrue, in this case the even numbers.- always two keys: Unlike
groupingBy(),partitioningBy()always produces both atrueand afalseentry, even if one of the lists ends up empty. - Alternative: You could use
Collectors.groupingBy(n -> n % 2 == 0)instead, which behaves similarly here but is meant for grouping by an arbitrary classifier rather than a strict two-way split.
Exercise 18: Predicate Filtering
Problem Statement: Use a Predicate<String> to filter out strings from a list that start with the letter “A”.
Purpose: This exercise helps you practice negating a Predicate with negate() instead of rewriting the condition from scratch.
Given Input: ["Apple", "Banana", "Avocado", "Cherry", "Almond"]
Expected Output: [Banana, Cherry]
▼ Hint
Define the predicate to check for a match with startsWith("A"), then call negate() on it inside filter() to keep only the strings that do not match.
▼ Solution & Explanation
Explanation:
Predicate<String> startsWithA = word -> word.startsWith("A"): Stores a reusable rule for matching strings that start with “A”.startsWithA.negate(): Returns a new predicate that flips the result of the original, sofilter()keeps only strings the original predicate rejects..filter(startsWithA.negate()): Removes every string that starts with “A”, leaving the rest.- Alternative: You could write the negated logic directly as a new lambda,
word -> !word.startsWith("A"), instead of callingnegate()on the original predicate.
Exercise 19: Chained Predicates
Problem Statement: Create two predicates, one checking if a number is greater than 10, and another checking if it’s even. Chain them using and() to filter a list.
Purpose: This exercise helps you practice combining two independent Predicate conditions into one using and(), instead of writing a single combined lambda from scratch.
Given Input: [4, 8, 11, 12, 15, 18, 20, 25]
Expected Output: [12, 18, 20]
▼ Hint
- Store each condition as a separate
Predicate<Integer>so they can be reused and combined independently. - Combine them with
isGreaterThanTen.and(isEven), which returns a new predicate that only matches when both conditions are true. - Pass the combined predicate directly into
filter().
▼ Solution & Explanation
Explanation:
isGreaterThanTen.and(isEven): Builds a newPredicatethat only returnstruewhen both the original predicate andisEvenboth returntruefor the same element.- short-circuit evaluation:
and()stops checking as soon as the first predicate returnsfalse, soisEvenis never evaluated for numbers 10 or below. .filter(isGreaterThanTen.and(isEven)): Applies the combined condition directly inside the stream pipeline.- Alternative: You could combine the two conditions inside a single lambda instead,
n -> n > 10 && n % 2 == 0, though keeping them as separate named predicates makes each rule easier to reuse and test on its own.
Exercise 20: Function Transformation
Problem Statement: Use a Function<String, Integer> to parse a list of numeric strings (e.g., "123") into a list of integers.
Purpose: This exercise helps you practice using the general purpose Function<T, R> interface to represent a single-argument transformation, and applying it inside map().
Given Input: ["123", "45", "678", "9"]
Expected Output: [123, 45, 678, 9]
▼ Hint
Function<String, Integer> represents any single-argument transformation from a String to an Integer, so assigning Integer::parseInt to it lets map() use it directly to convert every string in the list.
▼ Solution & Explanation
Explanation:
Function<String, Integer> parseToInt = Integer::parseInt: Stores a reusable transformation rule using a method reference instead of a lambda..map(parseToInt): Applies that transformation to every element in the stream, converting each numeric string into itsIntegervalue.Function<T, R>: The general purpose functional interface for any single-argument transformation, withapply()as its single abstract method.- Alternative: You could inline the transformation directly as
.map(Integer::parseInt)without a separate named variable, which works identically here since it is only used once.
Exercise 21: Function Chaining
Problem Statement: Create one Function that doubles an integer, and another that adds 3 to it. Chain them using andThen() and apply them to a number.
Purpose: This exercise helps you practice composing two separate Function instances into one pipeline using andThen(), instead of writing a single combined lambda.
Given Input: 5
Expected Output: Result: 13
▼ Hint
doubleValue.andThen(addThree)builds a newFunctionthat runsdoubleValuefirst, then feeds its result intoaddThree.- The order matters:
andThen()always runs the original function before the one passed as the argument. - Call
combined.apply(5)just like any otherFunctionto run both steps in sequence.
▼ Solution & Explanation
Explanation:
doubleValue.andThen(addThree): Returns a composedFunctionthat first appliesdoubleValue, then passes that output intoaddThree.combined.apply(5): RunsdoubleValue.apply(5)to get 10, thenaddThree.apply(10)to get 13.- execution order:
andThen()always executes the calling function first and the argument function second, the opposite ofcompose(). - Alternative: You could use
addThree.compose(doubleValue)instead, which produces the exact same composed behavior sincecompose()runs its argument first and the calling function second.
Exercise 22: Supplier Factory
Problem Statement: Use a Supplier<List<String>> to instantly instantiate and return a new ArrayList.
Purpose: This exercise helps you practice using Supplier, the functional interface for producing a value with no input arguments, a common pattern for lazy or on-demand object creation.
Expected Output: [first item]
▼ Hint
Supplier<T> takes no arguments and simply produces a value when get() is called, so a constructor reference like ArrayList::new can be assigned to it directly to build a new list on demand.
▼ Solution & Explanation
Explanation:
Supplier<List<String>> listFactory = ArrayList::new: Stores a constructor reference that produces a brand newArrayListeach time it runs.listFactory.get(): Calls theSupplier‘s single abstract method, creating and returning a fresh, empty list.- no input arguments: Unlike
FunctionorPredicate,Suppliertakes nothing in and only produces an output, matching what a factory method needs. - Alternative: You could write the lambda explicitly as
() -> new ArrayList<>(), which behaves identically to theArrayList::newconstructor reference.
Exercise 23: Supplier Randomizer
Problem Statement: Write a Supplier<Integer> that generates a random number between 1 and 100 every time it is called.
Purpose: This exercise helps you practice writing a Supplier whose body produces a fresh value on every call, rather than one that always returns the same fixed result.
Expected Output:
42 7 95
(the exact numbers vary on every run)
▼ Hint
A Supplier<Integer> lambda can wrap random.nextInt(100) + 1 so calling get() runs that expression fresh each time, producing a new random value between 1 and 100 on every call.
▼ Solution & Explanation
Explanation:
() -> random.nextInt(100) + 1: Lambda implementingSupplier<Integer>, generating a new random value between 1 and 100 every time it runs.randomNumber.get(): Triggers the lambda body again on each call, so the returned value is never cached or reused.- shared
Randominstance: Reusing the sameRandomobject across calls avoids the overhead of creating a new one every time. - Alternative: You could use
ThreadLocalRandom.current().nextInt(1, 101)inside the lambda instead of a sharedRandomfield, which avoids needing to manage theRandominstance yourself.
Exercise 24: Consumer Printing
Problem Statement: Use a Consumer<String> to print a string prefixed with "Logged: ". Pass this consumer into a forEach loop on a list of messages.
Purpose: This exercise helps you practice writing a Consumer, the functional interface for an action that takes one argument and returns nothing, and reusing it across every element in a collection.
Given Input: ["Server started", "User logged in", "Cache cleared"]
Expected Output:
Logged: Server started Logged: User logged in Logged: Cache cleared
▼ Hint
Consumer<String> takes a single argument and returns nothing, which matches exactly what forEach() expects, so the same Consumer lambda can be defined once and reused for every element in the list.
▼ Solution & Explanation
Explanation:
Consumer<String> logMessage = message -> System.out.println("Logged: " + message): Stores a reusable action that prefixes and prints any given string.messages.forEach(logMessage): Calls theConsumer‘saccept()method once for every message in the list.- separation of concerns: Defining the
Consumerseparately fromforEach()lets the same logging behavior be reused elsewhere without duplicating the print logic. - Alternative: You could inline the lambda directly inside
forEach(),messages.forEach(message -> System.out.println("Logged: " + message)), which skips the named variable if theConsumeris only used once.
Exercise 25: BiConsumer Mapping
Problem Statement: Use a BiConsumer<String, Integer> to iterate through a Map<String, Integer> and print out key-value pairs formatted as "Key directly maps to Value".
Purpose: This exercise helps you practice using BiConsumer, the two-argument counterpart to Consumer, which matches exactly what Map.forEach() passes to its callback.
Given Input: {"Alice": 85, "Bob": 92, "Charlie": 78}
Expected Output:
Alice directly maps to 85 Bob directly maps to 92 Charlie directly maps to 78
▼ Hint
BiConsumer<T, U>takes two arguments and returns nothing, matching exactly whatMap.forEach()passes to its callback: a key and a value.- Define the
BiConsumerlambda with two parameters,keyandvalue, then build the formatted message inside its body. - Pass the
BiConsumerstraight intoMap.forEach(), which calls it once per entry in the map.
▼ Solution & Explanation
Explanation:
BiConsumer<String, Integer> printMapping: Stores a two-argument action, matching the key and value types stored in the map.(key, value) -> System.out.println(...): Lambda implementingaccept(), receiving both the key and the value for a single map entry.scores.forEach(printMapping): Iterates the map and calls theBiConsumeronce for every key-value pair, in insertion order since aLinkedHashMapis used.- Alternative: You could iterate manually with a
forloop overscores.entrySet()and print eachMap.Entrydirectly, thoughMap.forEach()with aBiConsumeravoids handling theMap.Entryobject yourself.

Leave a Reply