Unit 10¶
Project Recursion Game¶
Project Instructions - Click Here
AP CSA Recursion Project:¶
Project Overview
You will choose ONE of the following recursive problem-solving challenges:
Option A: Maze Solver
Option B: Sudoku Solver
Your goal is to design and implement a recursive backtracking algorithm that solves your chosen problem.
Important Expectation
You are not being graded on finishing fast—you are being graded on having a deep understanding of recursion.
OPTION A: Maze Solver¶
Problem: Find a path from Begin (B) to End (E) in a maze.
REQUIREMENTS
You must:
Minimum size of the maze must be 5 x 5
Use recursion with backtracking
Write clean, organized methods
Include at least 2 test cases
Explain your algorithm clearly using comments
Use Java Time API
Project Block Header
Test Cases
Use a 2D array
Represent maze as
char[][]Symbols:
'B'= Begin'E'= End'|'= Wall'-'= Open space
Mark solution path (e.g.,
'*')
Required Method
public static boolean solveMaze(char[][] maze, int row, int col)
Optional Extensions for Maze
Count number of possible paths
Shortest path only
GUI Extension
Use
JPanelgrid
Color path vs walls
Example
Input
B - |
- - |
| - E
Output
B * |
- * |
| * E
Key Ideas
Try moving (up/down/left/right)
If blocked → return false
If correct path → continue
If dead end → backtrack (undo move)
OPTION B: Sudoku Solver¶
Problem Fill a 9×9 grid so that:
Each row has 1–9
Each column has 1–9
Each 3×3 box has 1–9
Requirements
Use a 2D array
Use
int[][]
Empty cells =
0Must validate placement
Use Java Time API
Project Block Header
Test Cases
Optional Extensions for Sudoku
Count all solutions
Generate a puzzle
GUI Extension
Use
JTextField[][]Button to trigger solver
Required Method
public static boolean solveSudoku(int[][] board)
Example
Input
5 3 0
6 0 0
0 9 8
Output
5 3 4
6 7 2
1 9 8
Key Ideas
Find empty cell
Try numbers 1–9
If valid → recurse
If it fails later → backtrack
Regardless of the Program that you chose, you must do the following:
REQUIRED EXPLANATION
What is your base case?
Where does backtracking happen?
What would cause your algorithm to fail?
How does the call stack behave?
TESTING REQUIREMENT
Minimum 2 different inputs
One must be more complex
Show before + after output
Project Rubric
Category |
Points |
Description |
|---|---|---|
Recursive Logic |
3 |
Correct base case + backtracking |
Program Comments |
1 |
Appropriate comments on key concepts |
Correctness |
1 |
Produces valid solution |
2D Array Use |
1 |
Proper traversal/manipulation |
Code Organization |
1 |
Clean structure, readable |
Testing |
1 |
Multiple test cases |
Explanation |
2 |
Clear understanding of recursion |
Total Points: |
10 |
Note: Common Problems
“It runs forever” → you are missing base case
“It almost works” → you are not backtracking properly
“It skips spots” → Your program is not written correctly – bad indexing
“It works once only” → You are modifying shared data incorrectly
10.1 Recursion¶
Goals
Learn about methods that call themselves.
Trace the results of a recursive call to determine the results.
Rewrite a recursive algorithm as an interactive method.
What is Recursion?
Recursion is when a method calls itself to solve a problem. A helpful way to think about recursion is to imagine a task that repeats in smaller and smaller versions of itself. Each step solves a piece of the problem and passes the rest along. A classic analogy is Russian nesting dolls. You open one doll to find a smaller one inside, and keep going until you reach the smallest doll that cannot be opened. That smallest doll is where the process stops. In recursion, each “smaller doll” is a recursive call. The smallest doll is the base case.
The Two Key Parts that every recursive method must have:
Base Case (Stopping Point) This is the condition where the method stops calling itself. Without it, the program would run forever (and crash).
Recursive Case (The Work) This is where the method calls a small part of the problem within method itself with a smaller input.
The basic structure of a recursive program will look something like this:
public static void Main()
// base case
if (baseCaseCondition) {
baseCaseSteps
}
else {
do something
// recursive call
recursiveMethod();
}
}
Recursion works because:
Each step makes the problem smaller
Eventually, it reaches a case we already know how to solve
Then the answers “stack back up”
When Should You Use Recursion?
Can be broken into smaller identical problems
Have a natural “divide and conquer” structure
Involve trees, hierarchies, or nested data
Examples:
Factorials
Searching (Binary Search)
Sorting (Merge Sort)
File systems and organization charts
Example: Factorial
The factorial of a number means multiplying it by all smaller positive integers.
5! = 5 × 4 × 3 × 2 × 1 = 120
Recursive Thinking
Instead of doing it all at once, think like this:
5! = 5 × 4!
4! = 4 × 3!
3! = 3 × 2!
2! = 2 × 1!
1! = 1 ← base case
Using recursion:
public class Factorial {
public static int factorial(int n) {
if (n == 1) {
return 1; // Base case
} else {
return n * factorial(n - 1); // Recursive step
}
}
public static void main(String[] args) {
int number = 5;
int result = factorial(number);
System.out.println("Factorial of " + number + " is: " + result);
}
}
Output
Factorial of 5 is 120
How the Calls Work (Step-by-Step)
When you call factorial(5), here’s what happens:
factorial(5) → 5 × factorial(4)
factorial(4) → 4 × factorial(3)
factorial(3) → 3 × factorial(2)
factorial(2) → 2 × factorial(1)
factorial(1) → 1 (base case)
Now the program builds the answer back up:
factorial(2) = 2 × 1 = 2
factorial(3) = 3 × 2 = 6
factorial(4) = 4 × 6 = 24
factorial(5) = 5 × 24 = 120
In Java, multiple inheritance can be achieved through interfaces, and we can use an ArrayList along with recursion to demonstrate the concept. Below is a program that models the hierarchy of employees in a company using multiple inheritance through interfaces. We’ll also use recursion to calculate the total salary of all employees in the hierarchy.
import java.util.ArrayList;
interface Employee {
double getSalary();
}
class Manager implements Employee {
private double baseSalary;
private ArrayList<Employee> subordinates;
public Manager(double baseSalary) {
this.baseSalary = baseSalary;
this.subordinates = new ArrayList<>();
}
public void addSubordinate(Employee employee) {
subordinates.add(employee);
}
public double getSalary() {
double totalSalary = baseSalary;
for (Employee subordinate : subordinates) {
totalSalary += subordinate.getSalary();
}
return totalSalary;
}
}
class RegularEmployee implements Employee {
private double salary;
public RegularEmployee(double salary) {
this.salary = salary;
}
public double getSalary() {
return salary;
}
}
public class MultipleInheritanceWithArrayListAndRecursion {
public static void main(String[] args) {
RegularEmployee employee1 = new RegularEmployee(3000);
RegularEmployee employee2 = new RegularEmployee(2500);
RegularEmployee employee3 = new RegularEmployee(2000);
Manager manager1 = new Manager(5000);
manager1.addSubordinate(employee1);
manager1.addSubordinate(employee2);
Manager manager2 = new Manager(4500);
manager2.addSubordinate(manager1);
manager2.addSubordinate(employee3);
System.out.println("Total salary for manager1 and his subordinates: $" + manager1.getSalary());
System.out.println("Total salary for manager2 and his subordinates: $" + manager2.getSalary());
}
}
Sample Output:
Total salary for manager1 and his subordinates: $10500.0
Total salary for manager2 and his subordinates: $17000.0
In this program, we have three classes: Employee, Manager, and RegularEmployee. The Employee interface ensures that both Manager and RegularEmployee classes have a getSalary() method. The Manager class contains an ArrayList of subordinates, and the addSubordinate() method is used to add employees to the manager’s team.
The main method creates instances of employees and managers, arranges them in a hierarchy, and then calculates the total salary using recursion through the getSalary() method of the Manager class. The program outputs the total salaries for the managers and their subordinates.
Let's look at this in another way!
Manager:
Has a base salary.
Has a list of subordinates (other Employee objects — either RegularEmployee’s or other Manager’s).
When
getSalary()is called, it returns:Its own base salary
Plus, the sum of all subordinates’ salaries (recursively).
manager1: 5000 (own) + 3000 (employee1) + 2500 (employee2) = 10500
manager2: 4500 (own) + 10500 (manager1 + his subs) + 2000 (employee3) = 17000
public double getSalary() {
double totalSalary = baseSalary;
for (Employee subordinate : subordinates) {
totalSalary += subordinate.getSalary(); // Recursive call happens here
}
return totalSalary;
}
Another way to look at this:
RegularEmployee e1 = new RegularEmployee(3000);
RegularEmployee e2 = new RegularEmployee(2500);
RegularEmployee e3 = new RegularEmployee(2000);
Manager m1 = new Manager(5000);
m1.addSubordinate(e1);
m1.addSubordinate(e2);
Manager m2 = new Manager(4500);
m2.addSubordinate(m1);
m2.addSubordinate(e3);
Now, calling m2.getSalary() triggers this:
m2.getSalary()totalSalary = 4500
Loop through subordinates:
First subordinate is m1 →
m1.getSalary()totalSalary = 5000
Loop through subordinates:
e1.getSalary()= 3000e2.getSalary()= 2500
m1.getSalary()returns 10500
Add 10500 to m2’s & e3’s salary → 4500 + 10500 + 2000 = 17000
A recursive method is like saying:“I’ll solve a small part of the problem, and trust that the rest will be solved the same way.”
Recursion is not just a programming trick, it is a way of thinking. Instead of trying to solve a large problem all at once, recursion teaches you to:
Break problems into smaller, manageable pieces
Clearly define when to stop
Trust a repeating process to build the final solution
At first, recursion can feel confusing because the method calls itself. But once you learn to identify the base case and the recursive step, it becomes a powerful and elegant tool. As you continue practicing, you’ll start to recognize patterns where recursion is the natural solution—and in many of those cases, it leads to code that is simpler, cleaner, and easier to understand than traditional loops. The real goal is not just to write recursive programs, but to think recursively—because that skill applies far beyond programming.
10.2 Recursive Searching and Sorting¶
Recursive searching and sorting are techniques in Java where the searching or sorting algorithm calls itself repeatedly on smaller portions of the data until the desired result is found or the data is sorted. Here are explanations and examples of recursive searching and sorting in Java:
Recursive Searching (Binary Search): Binary search is a commonly used searching algorithm that works efficiently on sorted arrays. It divides the array into two halves and compares the target element with the middle element. Based on the comparison, it either continues searching in the left half or the right half of the array.
Here’s an example of recursive binary search in Java:
public class BinarySearch {
public static int binarySearch(int[] array, int target, int low, int high) {
if (low > high) {
return -1; // target element not found
}
int mid = (low + high) / 2;
if (array[mid] == target) {
return mid; // target element found at mid index
} else if (array[mid] > target) {
return binarySearch(array, target, low, mid - 1); // search in the left half
} else {
return binarySearch(array, target, mid + 1, high); // search in the right half
}
}
public static void main(String[] args) {
int[] array = { 2, 4, 6, 8, 10, 12, 14, 16, 18, 20 };
int target = 12;
int index = binarySearch(array, target, 0, array.length - 1);
if (index != -1) {
System.out.println("Element found at index " + index);
} else {
System.out.println("Element not found");
}
}
}
Output
Element found at index 5
In this example, the binarySearch() method performs a recursive binary search on the sorted array to find the target element. It takes the array, target element, low index, and high index as parameters. If the low index becomes greater than the high index, it means the target element is not present in the array, so it returns -1. Otherwise, it calculates the mid index and compares the element at that index with the target element. If they match, it returns the index. If the element at mid is greater than the target element, it recursively calls binarySearch() on the left half of the array, otherwise on the right half.
Recursive Sorting (Merge Sort): Merge sort is a popular sorting algorithm that uses a divide-and-conquer approach. It divides the array into two halves, recursively sorts each half, and then merges the two sorted halves into a single sorted array. Here’s an example of recursive merge sort in Java:
public class MergeSort {
public static void mergeSort(int[] array, int left, int right) {
if (left < right) {
int mid = (left + right) / 2;
mergeSort(array, left, mid); // sort left half
mergeSort(array, mid + 1, right); // sort right half
merge(array, left, mid, right); // merge the two sorted halves
}
}
public static void merge(int[] array, int left, int mid, int right) {
int n1 = mid - left + 1;
int n2 = right - mid;
int[] leftArray = new int[n1];
int[] rightArray = new int[n2];
for (int i = 0; i < n1; i++) {
leftArray[i] = array[left + i];
}
for (int j = 0; j < n2; j++) {
rightArray[j] = array[mid + 1 + j];
}
int i = 0, j = 0, k = left;
while (i < n1 && j < n2) {
if (leftArray[i] <= rightArray[j]) {
array[k] = leftArray[i];
i++;
} else {
array[k] = rightArray[j];
j++;
}
k++;
}
while (i < n1) {
array[k] = leftArray[i];
i++;
k++;
}
while (j < n2) {
array[k] = rightArray[j];
j++;
k++;
}
}
public static void main(String[] args) {
int[] array = { 8, 3, 1, 5, 9, 2, 6, 4, 7 };
int n = array.length;
System.out.println("Original Array: " + Arrays.toString(array));
mergeSort(array, 0, n - 1);
System.out.println("Sorted Array: " + Arrays.toString(array));
}
}
Output
Original Array: [8, 3, 1, 5, 9, 2, 6, 4, 7]
Sorted Array: [1, 2, 3, 4, 5, 6, 7, 8, 9]
In this example, the mergeSort() method performs recursive merge sort on the array. It takes the array, left index, and right index as parameters. It recursively calls itself to sort the left half and the right half of the array, and then merges the two sorted halves using the merge() method. The merge() method merges the two sorted halves into a single sorted array.
Both examples demonstrate the power of recursion in searching and sorting algorithms. Recursive searching allows for efficient retrieval of elements from sorted data, while recursive sorting algorithms provide efficient ways to sort arrays or collections.
10.3 Lambda Expressions¶
AP CSA and Oracle Foundations
Learning Goals
Foundations: Lambda syntax, functional interfaces (Predicate, Consumer, Function), and Stream basics.
Application: “Galactic Zoo” (Static data analysis).
Simulation: “Eco-Economy” (Dynamic state mutation & market logic).
Extension: Advanced OOP patterns (MarketEvent enum).
Part 1: Lambda Fundamentals
A lambda expression in Java is a shortcut for writing a small method without naming it. You use it when you need to pass behavior (what to do) into another method.
Old Way (loop) |
lambda + stream |
|---|---|
Below are methods that are defined in the Java Stream API:
filter()needs a rule → you give it a lambdamap()needs a transformation → you give it a lambdaforEach()needs an action → you give it a lambda
How it Works The syntax consists of three main parts:
Parameters: The input variables
Arrow Token: The
->operatorBody: The code or expression
Basic Syntax
Zero parameters: ( ) -> System.out.println(“Hello”); //parentheses with no method was intentional
One parameter: (x) -> x * x;
Multiple parameters: (x, y) -> x + y;
// using a list
List<String> names = Arrays.asList("Zebra", "Apple", "Mango");
// Using the arrow operator to define the sorting logic
names.sort((a, b) -> a.compareTo(b));
// using switch case
int days = switch (month) {
case "FEBRUARY" -> 28;
case "APRIL", "JUNE" -> 30;
default -> 31;
};
Key Rules:
Must implement a Functional Interface (an interface with exactly one abstract method).
The compiler infers types from context.
Syntax: (parameters) -> { body }
Common Functional Interfaces
Interface |
Translation |
Example |
|---|---|---|
Predicate |
“Ask a YES/NO question” |
|
Consumer |
“Do something (no return)” |
|
Function |
“Change data into new data” |
|
Comparator |
“Decide order” |
|
Concept 1: Lambdas WITHOUT streams
Predicate<Alien> isSafe = a -> a.getDangerLevel() < 3;
System.out.println(isSafe.test(alien));
Concept 2: Lambdas WITH simple collections
aliens.forEach(a -> System.out.println(a.getName()));
Concept 3: Streams
aliens.stream().filter(...).collect(...)
When NOT to Use Lambdas:
Logic is long or complex
You need multiple steps or variables
Readability gets worse
Project 1 – The “Galactic Zoo” Manager¶
Theme: Analyzing alien populations on Space Station Alpha. Constraint: NO for, while, or do-while loops for data processing.
Requirements
Data Structure: class Alien (name, species, intelligenceLevel, dangerLevel, homePlanet, hasWings).
Population: Generate a list of 20+ unique aliens.
Features to Implement:
The Danger Filter (Predicate): Identify “Safe Visitors” (Danger < 3 AND Intelligence > 5).
The Intelligence Sort (Comparator): Sort by intelligence (desc), then name (asc).
Hint: Comparator.comparing(…).thenComparing(…)
The Planet Report (Collectors.groupingBy): Group by planet and find the one with the highest average intelligence.
The “Wing” Transformation (Function): Map to “Can Fly” or “Grounded”.
Commander’s Summary (reduce): Calculate total Threat Score (Sum of danger * intelligence).
Visual Model
List → Stream → Filter → Map → Result
Aliens
↓
.filter(a -> safe)
↓
.map(a -> name)
↓
.collect()
Step 1:
aliens.stream()
.filter(a -> ???) // What rule do you want to apply here?
.forEach(a -> System.out.println(a));
a -> a.getDangerLevel() < 3
Step 2:
Write full filter + forEach
Step 3:
Build full features
Level Up Concept: Mutation vs. Immutability Teacher Note: In pure functional programming, we avoid changing data. However, real-world simulations (like our Eco-Economy) often require updating object state (e.g., changing a price). The ‘Hybrid’ Approach: We use
streamsto traverse data efficiently, but we intentionally useforEachto mutate object fields. This is a pragmatic pattern used in industry for simulations, even if it breaks ‘pure’ functional rules. Rule of Thumb: Usemap()for creating new data. UseforEach()with setters only when you must update existing objects.
Project 2 – The “Eco-Economy” Simulator¶
Theme: Managing a dynamic renewable energy market. Goal: Simulate market fluctuations (Weather, Policy, Demand) and trigger alerts. This project is about applying lambdas inside a larger system.
Setup: The Data Factory
Generate realistic initial data.
public class EnergyDataFactory {
public static double randomInRange(double min, double max) {
return min + (Math.random() * (max - min));
}
public static List<EnergyResource> generateInitialMarket(int count) {
List<EnergyResource> market = new ArrayList<>();
String[] types = {"Solar", "Wind", "Hydro", "Nuclear"};
String[] names = {"SunRay", "Zephyr", "RiverFlow", "AtomCore", "Breeze", "Tide", "GeoThermal"};
for (int i = 0; i < count; i++) {
String type = types[(int)(Math.random() * types.length)];
// Set realistic BASE ranges based on research
double baseCost = 0;
double baseEfficiency = 0;
if (type.equals("Solar")) {
baseCost = randomInRange(40, 60);
baseEfficiency = randomInRange(0.6, 0.9);
} else if (type.equals("Wind")) {
baseCost = randomInRange(30, 50);
baseEfficiency = randomInRange(0.5, 0.85);
} else if (type.equals("Hydro")) {
baseCost = randomInRange(80, 120);
baseEfficiency = randomInRange(0.8, 0.95);
} else { // Nuclear
baseCost = randomInRange(200, 300);
baseEfficiency = randomInRange(0.9, 0.98);
}
EnergyResource res = new EnergyResource(
names[(int)(Math.random() * names.length)] + "_" + i,
type,
baseCost,
baseEfficiency,
true, // Initially available
(int)randomInRange(10, 100) // Carbon footprint
);
market.add(res);
}
return market;
}
}
The Simulation Loop
Run this loop for 10 “Days” to simulate market volatility.
List<EnergyResource> resources = EnergyDataFactory.generateInitialMarket(15);
// Print Initial State
System.out.println("=== INITIAL MARKET STATE ===");
resources.forEach(r -> System.out.println(r));
for (int day = 1; day <= 10; day++) {
System.out.println("\n========== DAY " + day + " ==========");
// Step A: Weather Fluctuations (Call your existing method)
applyWeatherFluctuations(resources);
// Step B: Policy Shocks
applyPolicyShocks(resources);
// Step C: Demand Surge
applyDemandSurge(resources);
// Step D: Alerts
generateAlerts(resources);
}
Step B: Apply Policy Shocks (Consumer)
Scenario: The government subsidizes a random energy type, lowering its cost.
public static void applyPolicyShocks(List<EnergyResource> resources) {
String[] policyTypes = {"Solar", "Wind", "Hydro", "Nuclear"};
String targetResource = policyTypes[(int)(Math.random() * policyTypes.length)];
double discountRate = 0.20; // 20% discount
System.out.println("POLICY UPDATE: Government subsidizes " + targetResource + "!");
// Use a Consumer to mutate state
resources.forEach(resource -> {
if (resource.getResourceType().equals(targetResource)) {
double newCost = resource.getCostPerUnit() * (1.0 - discountRate);
resource.setCostPerUnit(newCost);
System.out.println(" -> " + resource.getName() + " cost dropped to $" + String.format("%.2f", newCost));
}
});
}
Step C: Apply Demand Surge (Function)
Scenario: Market demand fluctuates, changing the “Market Price” for all resources.
public static void applyDemandSurge(List<EnergyResource> resources) {
// Simulate market condition (0.8 to 1.5)
double demandMultiplier = 0.8 + (Math.random() * 0.7);
String marketCondition = (demandMultiplier > 1.2) ? "HIGH DEMAND" : "LOW DEMAND";
System.out.println("MARKET STATUS: " + marketCondition + " (Multiplier: " + String.format("%.2f", demandMultiplier) + ")");
// Calculate new prices using a Function
// NOTE: We handle division by zero to prevent crashes!
List<Double> newPrices = resources.stream()
.map(resource -> {
double eff = resource.getEfficiencyRating();
// Safety check: if efficiency is 0, treat as 1 to avoid ArithmeticException
double safeEff = (eff == 0) ? 1 : eff;
double baseValue = resource.getCostPerUnit() / safeEff;
return baseValue * demandMultiplier;
})
.collect(Collectors.toList());
// Sync the calculated prices back to the objects (Mutation!)
for (int i = 0; i < resources.size(); i++) {
resources.get(i).setCurrentMarketPrice(newPrices.get(i));
}
System.out.println("Prices updated for all resources.");
}
Step D: Alert Generation (Predicate + Consumer)
Scenario: Identify profitable trades (Market Price < 110% of Base Cost).
public static void generateAlerts(List<EnergyResource> resources) {
// Predicate: Is this a good deal?
Predicate<EnergyResource> isProfitableTrade = resource -> {
return resource.getCurrentMarketPrice() < (resource.getCostPerUnit() * 1.1);
};
System.out.println("SCANNING FOR PROFITABLE TRADES...");
// Filter and Alert
long count = resources.stream()
.filter(isProfitableTrade)
.peek(r -> { // Debugging: see what's being processed
double profit = r.getCostPerUnit() - r.getCurrentMarketPrice();
System.out.println("ALERT: Buy " + r.getName() + "! " +
"Price: $" + String.format("%.2f", r.getCurrentMarketPrice()) +
" | Profit: $" + String.format("%.2f", profit));
})
.count(); // Count triggers the stream
if (count == 0) {
System.out.println("No profitable trades found today. Wait for the next cycle.");
}
}
Debugging Checklist for Students
Before submitting, check these common pitfalls:
Division by Zero: Did you handle efficiencyRating == 0 in your math?
Fix: double safeEff = (eff == 0) ? 1 : eff;
Null Pointers: Are you checking for null before calling .equals()?
Fix: “Solar”.equals(resource.getType()) (Put string literal first).
State Sync: Did you remember to update the object after mapping?
Reminder: map() creates a new list of numbers. You must loop to set them back into the objects.
Side Effects: Are you using forEach only for printing or setting values?
Check: Don’t use forEach to build a new list; use collect().
Lambda Project Rubric (10 Points)
Criteria |
Excellent(2) |
Proficient(1) |
Developing(0) |
|---|---|---|---|
Lambda Syntax |
Correct -> usage, method references where appropriate. |
Minor syntax errors or overuse of anonymous classes. |
Relies on loops or anonymous classes; syntax errors. |
Functional Interfaces |
Distinct use of Predicate, Function, Consumer. |
Confuses roles (e.g., using Consumer for filtering). |
Logic embedded directly in loops without abstraction. |
Stream Operations |
Logical chain: filter → map → collect/reduce. |
Misses a step or chains incorrectly. |
Uses streams only for forEach; procedural logic. |
Logic & Accuracy |
Business logic (math, sorting) is accurate. |
Logic mostly correct but fails edge cases. |
Results do not match requirements. |
Code Quality |
Clean, commented, meaningful variable names. |
Readable but lacks comments. |
Messy, hard to read, no comments. |
Conceptual Understanding |
Can clearly explain what a lambda does and why it is used |
Is able to explain general lambda functionality |
Misuses concepts or misstates functionality |
Bonus Challenge (Extra Credit)
Method References: Replace
x -> System.out.println(x)withSystem.out::println.Advanced OOP: Refactor Steps B, C, and D into a MarketEvent enum with an apply() method.
Research: To make the simulation realistic, research:
Solar: How much does efficiency drop at night? (Code: if (night) efficiency = 0;)
Wind: Do turbines work better in storms? (Code: if (storm) efficiency *= 1.5;)
Nuclear: What is the typical cost vs. Solar? (Set baseCost accordingly).
Common Student Misconceptions
“Lambdas replace all loops” → Correction: Lambdas replace data processing loops. You still need loops for initialization or non-stream logic.
“Streams are immutable” → Correction: Streams process data immutably, but the objects inside can be mutated if the logic requires it (as seen in the Eco-Economy project).
“Lambdas are slower” → Correction: Performance is comparable to anonymous classes; readability is the primary benefit.
This unit is designed to bridge the gap between abstract syntax and real-world software engineering, preparing students for both the AP CSA exam and future professional development.