6.12 Final Tutorial
- Why we can use lambda interface?
Because we need to implement functional interface and it only has one abstract method needed to be implement. Thus it's easy to know which method are you implementing - Can we use lambda expression to do recursion?
No you don't have reference to lambda - A const at the end of a member function (a function of a class), will only prohibit changes to the state of the object (making *this constant).
It will not affect the returned value, so function T& at(int i) const
in List (and LinkedList), will not make the returned reference to be const.
- The keywords inline
and constexpr
in C++ are suggestions to the compiler, both have several conditions for them to be considered, such as: if a function is recursive, a pointer of a function is used, the compiler noticed that inlining would be worse than just calling the function, the configuration of the compiler (flags and environment variables) are not enabling the required optimizations.
Generic, operator overloading of C++
#include <iostream>
class WrapperOfInt {
private:
int value;
public:
WrapperOfInt(): value(0) {}
WrapperOfInt(int v): value(v) {}
WrapperOfInt(const WrapperOfInt& other) {
this->value = other.value;
}
~WrapperOfInt() {}
int getValue() const {
return value;
}
friend WrapperOfInt operator+ (const WrapperOfInt& a, const WrapperOfInt& b) {
return WrapperOfInt(a.value + b.value);
}
friend std::ostream& operator<< (std::ostream& os, const WrapperOfInt& w);
};
class Foo {
private:
std::string msg;
public:
Foo(): msg("no msg") {}
Foo(std::string m): msg(m) {}
Foo(const Foo& other) {
this->msg = other.msg;
}
~Foo() {}
std::string getMessage() const {
return msg;
}
};
std::ostream& operator<< (std::ostream& os, const WrapperOfInt& w) {
os << "WrapperOfInt(" << w.value << ")";
return os;
}
template<typename T>
concept Addition = requires(T a, T b) {
{a+ b} -> std :: same_as<T>;
};
template<Addition T>
T add(int size, T neutral, T values[]) {
T result = neutral;
for (int i = 0; i < size; i++) {
result = result + values[i];
}
return result;
}
int main() {
Foo foo1 = Foo("It's a foo!");
Foo foo2 = Foo("Mamma Mia!");
Foo foo3 = Foo("Course is almost over");
Foo foos[] = {foo1, foo2, foo3};
Foo result = add(3, Foo(""), foos);
std::cout << "The sum of " << foo1 << ", " << foo2 << ", and " << foo3;
std::cout << "\nis: " << add(3, Foo(""), foos) << std::endl;
WrapperOfInt w1 = WrapperOfInt(39);
WrapperOfInt w2 = WrapperOfInt(2);
WrapperOfInt w3 = WrapperOfInt(1);
WrapperOfInt values[] = {w1, w2, w3};
std::cout << "The sum of " << w1 << ", " << w2 << ", and " << w3;
std::cout << "\nis: " << add(3, WrapperOfInt(0), values) << std::endl;
return 0;
}
Java lambda, anonymous class, stream example
Function.java
/**
* Describes a Function that goes from a type {@code A} to a type {@code B}. This is
* a functional interface, and it only has one abstract method.
* @param A the type of the elements that this function takes
* @param B the type of the elements that this function returns
* @version 0.1
*/
@FunctionalInterface
public interface Function<A, B> {
/**
* Applies this function to its arguments.
* @param source the argument for this function
* @return the result of applying this function
*/
public B apply(A source);
}
FunctionExamples.java
import java.util.List;
import java.util.Arrays;
/**
* Demonstrates different ways to implement the Function interface for various tasks.
* Each task is implemented using: Classes, Anonymous Classes, Lambda expressions, and Method references.
*/
public class FunctionExamples {
// ========== TASK 1: Absolute Value Function ==========
// 1.1 Using Class
static class AbsoluteValueFunction implements Function<Integer, Integer> {
@Override
public Integer apply(Integer source) {
return Math.abs(source);
}
}
// 1.2 Anonymous Class (defined in main method)
// 1.3 Lambda Expression (defined in main method)
// 1.4 Method Reference (defined in main method)
// ========== TASK 2: Palindrome Check Function ==========
// 2.1 Using Class
static class PalindromeFunction implements Function<String, Boolean> {
@Override
public Boolean apply(String source) {
return isPalindrome(source);
}
private boolean isPalindrome(String str) {
if (str == null) return false;
str = str.toLowerCase().replaceAll("[^a-zA-Z0-9]", "");
int left = 0, right = str.length() - 1;
while (left < right) {
if (str.charAt(left) != str.charAt(right)) {
return false;
}
left++;
right--;
}
return true;
}
}
// ========== TASK 3: Addition of Two Integers ==========
// Helper class to hold two integers
static class IntegerPair {
public final Integer first;
public final Integer second;
public IntegerPair(Integer first, Integer second) {
this.first = first;
this.second = second;
}
}
// 3.1 Using Class
static class AdditionFunction implements Function<IntegerPair, Integer> {
@Override
public Integer apply(IntegerPair source) {
return source.first + source.second;
}
}
// ========== TASK 4: Maximum of Three Integers ==========
// Helper class to hold three integers
static class IntegerTriple {
public final Integer first;
public final Integer second;
public final Integer third;
public IntegerTriple(Integer first, Integer second, Integer third) {
this.first = first;
this.second = second;
this.third = third;
}
}
// 4.1 Using Class
static class MaximumFunction implements Function<IntegerTriple, Integer> {
@Override
public Integer apply(IntegerTriple source) {
return Math.max(Math.max(source.first, source.second), source.third);
}
}
// ========== TASK 5: List Element Access ==========
// Helper class to hold list and index
static class ListIndexPair {
public final List<Integer> list;
public final Integer index;
public ListIndexPair(List<Integer> list, Integer index) {
this.list = list;
this.index = index;
}
}
// 5.1 Using Class
static class ListAccessFunction implements Function<ListIndexPair, Integer> {
@Override
public Integer apply(ListIndexPair source) {
if (source.index >= 0 && source.index < source.list.size()) {
return source.list.get(source.index);
}
throw new IndexOutOfBoundsException("Index out of bounds: " + source.index);
}
}
// ========== AUXILIARY METHODS ==========
public static boolean isPalindrome(String str) {
if (str == null) return false;
str = str.toLowerCase().replaceAll("[^a-zA-Z0-9]", "");
int left = 0, right = str.length() - 1;
while (left < right) {
if (str.charAt(left) != str.charAt(right)) {
return false;
}
left++;
right--;
}
return true;
}
public static Integer addTwo(IntegerPair pair) {
return pair.first + pair.second;
}
public static Integer maxOfThree(IntegerTriple triple) {
return Math.max(Math.max(triple.first, triple.second), triple.third);
}
public static Integer getListElement(ListIndexPair pair) {
if (pair.index >= 0 && pair.index < pair.list.size()) {
return pair.list.get(pair.index);
}
throw new IndexOutOfBoundsException("Index out of bounds: " + pair.index);
}
// ========== MAIN METHOD WITH DEMONSTRATIONS ==========
public static void main(String[] args) {
System.out.println("=== Function Interface Implementation Examples ===\n");
// ========== TASK 1: Absolute Value ==========
System.out.println("TASK 1: Absolute Value Function");
// 1.1 Class
Function<Integer, Integer> abs1 = new AbsoluteValueFunction();
// 1.2 Anonymous Class
Function<Integer, Integer> abs2 = new Function<Integer, Integer>() {
@Override
public Integer apply(Integer source) {
return Math.abs(source);
}
};
// 1.3 Lambda Expression
Function<Integer, Integer> abs3 = source -> Math.abs(source);
// 1.4 Method Reference
Function<Integer, Integer> abs4 = Math::abs;
// Test all implementations
Integer testValue1 = -15;
System.out.println("Input: " + testValue1);
System.out.println("Class: " + abs1.apply(testValue1));
System.out.println("Anonymous: " + abs2.apply(testValue1));
System.out.println("Lambda: " + abs3.apply(testValue1));
System.out.println("Method Reference: " + abs4.apply(testValue1));
System.out.println();
// ========== TASK 2: Palindrome Check ==========
System.out.println("TASK 2: Palindrome Check Function");
// 2.1 Class
Function<String, Boolean> palindrome1 = new PalindromeFunction();
// 2.2 Anonymous Class
Function<String, Boolean> palindrome2 = new Function<String, Boolean>() {
@Override
public Boolean apply(String source) {
return isPalindrome(source);
}
};
// 2.3 Lambda Expression
Function<String, Boolean> palindrome3 = source -> isPalindrome(source);
// 2.4 Method Reference
Function<String, Boolean> palindrome4 = FunctionExamples::isPalindrome;
// Test all implementations
String testValue2 = "A man a plan a canal Panama";
System.out.println("Input: \"" + testValue2 + "\"");
System.out.println("Class: " + palindrome1.apply(testValue2));
System.out.println("Anonymous: " + palindrome2.apply(testValue2));
System.out.println("Lambda: " + palindrome3.apply(testValue2));
System.out.println("Method Reference: " + palindrome4.apply(testValue2));
System.out.println();
// ========== TASK 3: Addition of Two Integers ==========
System.out.println("TASK 3: Addition Function");
// 3.1 Class
Function<IntegerPair, Integer> add1 = new AdditionFunction();
// 3.2 Anonymous Class
Function<IntegerPair, Integer> add2 = new Function<IntegerPair, Integer>() {
@Override
public Integer apply(IntegerPair source) {
return source.first + source.second;
}
};
// 3.3 Lambda Expression
Function<IntegerPair, Integer> add3 = pair -> pair.first + pair.second;
// 3.4 Method Reference
Function<IntegerPair, Integer> add4 = FunctionExamples::addTwo;
// Test all implementations
IntegerPair testValue3 = new IntegerPair(25, 17);
System.out.println("Input: (" + testValue3.first + ", " + testValue3.second + ")");
System.out.println("Class: " + add1.apply(testValue3));
System.out.println("Anonymous: " + add2.apply(testValue3));
System.out.println("Lambda: " + add3.apply(testValue3));
System.out.println("Method Reference: " + add4.apply(testValue3));
System.out.println();
// ========== TASK 4: Maximum of Three Integers ==========
System.out.println("TASK 4: Maximum Function");
// 4.1 Class
Function<IntegerTriple, Integer> max1 = new MaximumFunction();
// 4.2 Anonymous Class
Function<IntegerTriple, Integer> max2 = new Function<IntegerTriple, Integer>() {
@Override
public Integer apply(IntegerTriple source) {
return Math.max(Math.max(source.first, source.second), source.third);
}
};
// 4.3 Lambda Expression
Function<IntegerTriple, Integer> max3 = triple ->
Math.max(Math.max(triple.first, triple.second), triple.third);
// 4.4 Method Reference
Function<IntegerTriple, Integer> max4 = FunctionExamples::maxOfThree;
// Test all implementations
IntegerTriple testValue4 = new IntegerTriple(12, 45, 23);
System.out.println("Input: (" + testValue4.first + ", " + testValue4.second + ", " + testValue4.third + ")");
System.out.println("Class: " + max1.apply(testValue4));
System.out.println("Anonymous: " + max2.apply(testValue4));
System.out.println("Lambda: " + max3.apply(testValue4));
System.out.println("Method Reference: " + max4.apply(testValue4));
System.out.println();
// ========== TASK 5: List Element Access ==========
System.out.println("TASK 5: List Element Access Function");
// 5.1 Class
Function<ListIndexPair, Integer> listAccess1 = new ListAccessFunction();
// 5.2 Anonymous Class
Function<ListIndexPair, Integer> listAccess2 = new Function<ListIndexPair, Integer>() {
@Override
public Integer apply(ListIndexPair source) {
if (source.index >= 0 && source.index < source.list.size()) {
return source.list.get(source.index);
}
throw new IndexOutOfBoundsException("Index out of bounds: " + source.index);
}
};
// 5.3 Lambda Expression
Function<ListIndexPair, Integer> listAccess3 = pair -> {
if (pair.index >= 0 && pair.index < pair.list.size()) {
return pair.list.get(pair.index);
}
throw new IndexOutOfBoundsException("Index out of bounds: " + pair.index);
};
// 5.4 Method Reference
Function<ListIndexPair, Integer> listAccess4 = FunctionExamples::getListElement;
// Test all implementations
List<Integer> testList = Arrays.asList(10, 20, 30, 40, 50);
ListIndexPair testValue5 = new ListIndexPair(testList, 2);
System.out.println("Input: List" + testList + ", Index: " + testValue5.index);
System.out.println("Class: " + listAccess1.apply(testValue5));
System.out.println("Anonymous: " + listAccess2.apply(testValue5));
System.out.println("Lambda: " + listAccess3.apply(testValue5));
System.out.println("Method Reference: " + listAccess4.apply(testValue5));
System.out.println();
System.out.println("=== All Function implementations completed successfully! ===");
}
}
FactorialDemo.java
/**
* Demonstrates Task 2: Creating a variable and value for a Function that calculates factorial of an Integer
* Shows different ways to implement factorial calculation using the Function interface
*/
public class FactorialDemo {
public static void main(String[] args) {
System.out.println("=== Task 2: Factorial Function Implementations ===\n");
// Variable and value declarations for factorial Function
// 1. Using Lambda Expression (most concise)
Function<Integer, Integer> factorialLambda = n -> {
if (n < 0) throw new IllegalArgumentException("Factorial not defined for negative numbers");
if (n == 0 || n == 1) return 1;
Integer result = 1;
for (int i = 2; i <= n; i++) {
result *= i;
}
return result;
};
// 2. Using Method Reference to helper method
Function<Integer, Integer> factorialMethodRef = FactorialDemo::calculateFactorial;
// 3. Using Anonymous Class
Function<Integer, Integer> factorialAnonymous = new Function<Integer, Integer>() {
@Override
public Integer apply(Integer source) {
if (source < 0) throw new IllegalArgumentException("Factorial not defined for negative numbers");
if (source == 0 || source == 1) return 1;
Integer result = 1;
for (int i = 2; i <= source; i++) {
result *= i;
}
return result;
}
};
// Testing the factorial functions
Integer[] testValues = {0, 1, 5, 7};
for (Integer testValue : testValues) {
System.out.println("Factorial of " + testValue + ":");
System.out.println(" Lambda Expression: " + factorialLambda.apply(testValue));
System.out.println(" Method Reference: " + factorialMethodRef.apply(testValue));
System.out.println(" Anonymous Class: " + factorialAnonymous.apply(testValue));
System.out.println();
}
}
// Helper method for method reference
public static Integer calculateFactorial(Integer n) {
if (n < 0) throw new IllegalArgumentException("Factorial not defined for negative numbers");
if (n == 0 || n == 1) return 1;
Integer result = 1;
for (int i = 2; i <= n; i++) {
result *= i;
}
return result;
}
}
Task 3: Can a Lambda expression be used? Justify your answer.
Answer: YES, a Lambda expression can be used.
Justification:
-
Functional Interface Requirement: The
Function<A, B>
interface is annotated with@FunctionalInterface
, which means:- It has exactly one abstract method (
apply
) - Lambda expressions can be used wherever functional interfaces are expected
- It has exactly one abstract method (
-
Single Abstract Method (SAM) : The Function interface has only one abstract method:
public B apply(A source);
This satisfies the requirement for lambda expression usage.
-
Type Compatibility:
- Lambda expressions can be automatically converted to functional interface types
- The compiler can infer the parameter types from the Function's generic types
-
Function<Integer, Integer>
means a function that takes an Integer and returns an Integer
-
Syntax Advantages:
- Concise:
n -> factorial_logic
instead of full anonymous class syntax - Readable: Focuses on the logic rather than boilerplate code
- Type inference: Compiler automatically determines parameter types
- Concise:
-
Example Proof:
// This works perfectly: Function<Integer, Integer> factorial = n -> { // factorial calculation logic };
Conclusion: Lambda expressions are not only possible but are the preferred approach for implementing functional interfaces like Function due to their conciseness and readability.
Streams vs Collections: Key Mechanical Difference
Answer: Lazy Evaluation vs Eager Evaluation
Key Mechanical Difference:
Collections (Eager Evaluation):
- Immediate Processing: Operations are executed immediately when called
- Data Storage: Collections store all elements in memory
- Multiple Iterations: Can be iterated multiple times
- Concrete Data Structure: Physically holds data
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> doubled = numbers.stream()
.map(x -> x * 2)
.collect(Collectors.toList()); // Executes immediately, stores result
Streams (Lazy Evaluation):
- Deferred Processing: Operations are not executed until a terminal operation is called
- No Data Storage: Streams don't store elements; they process data on-demand
- Single Use: Can only be consumed once
- Pipeline of Operations: Describes what should be done, not when
Stream<Integer> numberStream = Arrays.asList(1, 2, 3, 4, 5).stream();
Stream<Integer> doubledStream = numberStream.map(x -> x * 2); // No execution yet!
// Only when we call a terminal operation like .collect() does processing happen
Additional Mechanical Differences:
-
Memory Usage:
- Collections: Store all data in memory
- Streams: Process elements one-by-one, minimal memory footprint
-
Iteration Model:
- Collections: External iteration (explicit loops)
- Streams: Internal iteration (framework controls the loop)
-
Mutability:
- Collections: Can be modified (add/remove elements)
- Streams: Immutable pipeline of operations
-
Performance:
- Collections: Good for small datasets, random access
- Streams: Better for large datasets, parallel processing, complex transformations
Example Demonstrating the Difference:
// Collection approach (eager)
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> result = new ArrayList<>();
for (Integer num : list) {
if (num % 2 == 0) {
result.add(num * 2);
}
}
// Stream approach (lazy)
List<Integer> result = Arrays.asList(1, 2, 3, 4, 5)
.stream()
.filter(num -> num % 2 == 0) // Not executed yet
.map(num -> num * 2) // Not executed yet
.collect(Collectors.toList()); // NOW everything executes
The fundamental difference is that streams represent a recipe for computation while collections represent actual data storage.