Lesson 3
Mastering Data Aggregation and Data Streams in Java
Mastering Data Aggregation and Data Streams in Java

Welcome to our lesson on mastering data aggregation and data streams in Java. In this lesson, we'll start by building a basic sales records aggregator. Then, we'll extend its functionality to handle more complex operations such as filtering, data aggregation, and formatted outputs. By the end of this session, you'll be able to manage and format data streams efficiently using Java.

Starter Task Methods

To begin, we'll implement a basic sales record aggregator. Here are the methods we'll be focusing on:

  • addSale(int saleId, double amount) - Adds a sale record with a unique identifier saleId and an amount. If a sale with the same saleId already exists, it updates the amount.
  • getSale(int saleId) - Retrieves the sale amount associated with the saleId. If the sale does not exist, it returns null.
  • deleteSale(int saleId) - Deletes the sale record with the given saleId. Returns true if the sale was deleted and false if the sale does not exist.

Are these methods clear so far? Great! Let's now look at how we would implement them.

Starter Task Implementation

Here is the complete code for the starter task:

Java
1import java.util.HashMap; 2 3public class SalesAggregator { 4 private HashMap<Integer, Double> sales; 5 6 public SalesAggregator() { 7 sales = new HashMap<>(); // Initialize an empty HashMap to store sales records. 8 } 9 10 public void addSale(int saleId, double amount) { 11 sales.put(saleId, amount); // Add or update the sale with the provided saleId and amount. 12 } 13 14 public Double getSale(int saleId) { 15 return sales.getOrDefault(saleId, null); // Retrieve and return the sale amount for the given saleId, or null if it doesn't exist. 16 } 17 18 public boolean deleteSale(int saleId) { 19 return sales.remove(saleId) != null; // Remove the sale record and return true if it existed, false otherwise. 20 } 21 22 public static void main(String[] args) { 23 SalesAggregator aggregator = new SalesAggregator(); 24 25 // Add sales 26 aggregator.addSale(1, 100.50); 27 aggregator.addSale(2, 200.75); 28 29 // Get sale 30 System.out.println(aggregator.getSale(1)); // Output: 100.5 31 32 // Delete sale 33 System.out.println(aggregator.deleteSale(2)); // Output: true 34 System.out.println(aggregator.getSale(2)); // Output: null 35 } 36}

Explanation:

  • The SalesAggregator class initializes an empty HashMap to store sales records.
  • The addSale method adds a new sale or updates the amount for an existing sale ID.
  • The getSale method retrieves the amount for a given sale ID or returns null if the sale does not exist.
  • The deleteSale method removes the sale record for the given sale ID or returns false if the sale does not exist.

Now that we have our basic aggregator, let's extend it to include more advanced functionalities.

New Methods and Their Definitions

To increase the complexity and usefulness of our sales aggregator, we'll adjust some existing methods and introduce some new methods and functionalities involving formatted outputs.

  • addSale(int saleId, double amount, LocalDateTime date) - Adds or updates a sale record with a unique identifier saleId, amount, and a date.
  • aggregateSales(double minAmount) - Returns an object with the total number of sales and the total amount of sales where the sale amount is above minAmount. The object format looks like this:
    Java
    1public class SalesStatistics { 2 private int totalSales; 3 private double totalAmount; 4 5 public SalesStatistics(int totalSales, double totalAmount) { 6 this.totalSales = totalSales; 7 this.totalAmount = totalAmount; 8 } 9 10 // Getter methods 11 public int getTotalSales() { 12 return totalSales; 13 } 14 15 public double getTotalAmount() { 16 return totalAmount; 17 } 18}
  • formatSales(double minAmount) - Returns the sales data, filtered by minAmount, formatted as JSON. Includes aggregated sales statistics in the output.
  • getSalesInDateRange(LocalDateTime startDate, LocalDateTime endDate) - Retrieves all sales that occurred within the given date range, inclusive. Each sale includes saleId, amount, and date.

Let's implement these methods step-by-step.

Step 1: Enhancing the addSale Method to Include Date

We'll first modify the addSale method to accept a date.

Java
1import java.time.LocalDateTime; 2 3public void addSale(int saleId, double amount, LocalDateTime date) { 4 // Add or update the sale with the provided saleId, amount, and date. 5 sales.put(saleId, new SaleRecord(amount, date)); 6} 7 8private class SaleRecord { 9 private double amount; 10 private LocalDateTime date; 11 12 public SaleRecord(double amount, LocalDateTime date) { 13 this.amount = amount; 14 this.date = date; 15 } 16 17 // Getter methods 18 public double getAmount() { 19 return amount; 20 } 21 22 public LocalDateTime getDate() { 23 return date; 24 } 25}

This ensures that each sale record includes a date in addition to the amount.

Step 2: Implementing the aggregateSales Method

Now, we create the aggregateSales method:

Java
1public SalesStatistics aggregateSales(double minAmount) { 2 int totalSales = 0; 3 double totalAmount = 0.0; 4 for (SaleRecord sale : sales.values()) { 5 if (sale.getAmount() > minAmount) { 6 totalSales += 1; // Increment the total sales count. 7 totalAmount += sale.getAmount(); // Add to the total amount. 8 } 9 } 10 return new SalesStatistics(totalSales, totalAmount); // Return the aggregated results. 11} 12 13// Define SalesStatistics class 14public class SalesStatistics { 15 private int totalSales; 16 private double totalAmount; 17 18 public SalesStatistics(int totalSales, double totalAmount) { 19 this.totalSales = totalSales; 20 this.totalAmount = totalAmount; 21 } 22 23 // Getter methods 24 public int getTotalSales() { 25 return totalSales; 26 } 27 28 public double getTotalAmount() { 29 return totalAmount; 30 } 31} 32 33public static void main(String[] args) { 34 SalesAggregator aggregator = new SalesAggregator(); 35 36 // Add sales with date 37 aggregator.addSale(1, 100.50, LocalDateTime.of(2023, 1, 1, 0, 0)); 38 aggregator.addSale(2, 200.75, LocalDateTime.of(2023, 1, 15, 0, 0)); 39 40 // Aggregate sales 41 SalesStatistics stats = aggregator.aggregateSales(50); 42 System.out.println("Total Sales: " + stats.getTotalSales() + ", Total Amount: " + stats.getTotalAmount()); 43 // Output: Total Sales: 2, Total Amount: 301.25 44}

This method iterates through the sales and sums up those that exceed the minAmount.

Step 3: Implementing the formatSales Method

Next, we'll create the formatSales method to output data in JSON format using Gson.

Java
1import com.google.gson.Gson; 2import java.util.ArrayList; 3import java.util.List; 4 5public String formatSales(double minAmount) { 6 // Filter and format sales data based on minAmount. 7 List<Object> filteredSales = new ArrayList<>(); 8 for (Integer saleId : sales.keySet()) { 9 SaleRecord sale = sales.get(saleId); 10 if (sale.getAmount() > minAmount) { 11 filteredSales.add(new SaleDetails(saleId, sale.getAmount(), sale.getDate())); 12 } 13 } 14 15 // Aggregate sales statistics. 16 SalesStatistics statistics = aggregateSales(minAmount); 17 18 // Combine sales data and statistics into the final result. 19 SalesReport result = new SalesReport(filteredSales, statistics); 20 21 Gson gson = new Gson(); 22 return gson.toJson(result); // Convert the result to JSON. 23} 24 25private class SaleDetails { 26 private int saleId; 27 private double amount; 28 private LocalDateTime date; 29 30 public SaleDetails(int saleId, double amount, LocalDateTime date) { 31 this.saleId = saleId; 32 this.amount = amount; 33 this.date = date; 34 } 35} 36 37private class SalesReport { 38 private List<Object> Sales; 39 private SalesStatistics Statistics; 40 41 public SalesReport(List<Object> sales, SalesStatistics statistics) { 42 this.Sales = sales; 43 this.Statistics = statistics; 44 } 45} 46 47public static void main(String[] args) { 48 SalesAggregator aggregator = new SalesAggregator(); 49 50 // Add sales with date 51 aggregator.addSale(1, 100.50, LocalDateTime.of(2023, 1, 1, 0, 0)); 52 aggregator.addSale(2, 200.75, LocalDateTime.of(2023, 1, 15, 0, 0)); 53 54 // Format sales to JSON 55 String formattedSales = aggregator.formatSales(50); 56 System.out.println(formattedSales); 57 // Output: {"Sales":[{"saleId":1,"amount":100.5,"date":"2023-01-01T00:00"},{"saleId":2,"amount":200.75,"date":"2023-01-15T00:00"}],"Statistics":{"totalSales":2,"totalAmount":301.25}} 58}

This function utilizes Gson, a Java library from Google that converts Java objects to JSON representations, to format sales data as JSON, and includes aggregated statistics.

Step 4: Implementing the getSalesInDateRange Method

Finally, let's implement the getSalesInDateRange method:

Java
1import java.time.LocalDateTime; 2import java.util.ArrayList; 3import java.util.List; 4 5public List<SaleDetails> getSalesInDateRange(LocalDateTime startDate, LocalDateTime endDate) { 6 List<SaleDetails> result = new ArrayList<>(); 7 for (Integer saleId : sales.keySet()) { 8 SaleRecord sale = sales.get(saleId); 9 if ((sale.getDate().isEqual(startDate) || sale.getDate().isAfter(startDate)) && 10 (sale.getDate().isEqual(endDate) || sale.getDate().isBefore(endDate))) { 11 result.add(new SaleDetails(saleId, sale.getAmount(), sale.getDate())); 12 } 13 } 14 return result; 15} 16 17public static void main(String[] args) { 18 SalesAggregator aggregator = new SalesAggregator(); 19 20 // Add sales with date 21 aggregator.addSale(1, 100.50, LocalDateTime.of(2023, 1, 1, 0, 0)); 22 aggregator.addSale(2, 200.75, LocalDateTime.of(2023, 1, 15, 0, 0)); 23 24 // Get sales in date range 25 List<SaleDetails> salesInRange = aggregator.getSalesInDateRange(LocalDateTime.of(2023, 1, 1, 0, 0), LocalDateTime.of(2023, 12, 31, 0, 0)); 26 for (SaleDetails sale : salesInRange) { 27 System.out.println(sale); 28 } 29 // Output: SaleDetails[saleId=1, amount=100.5, date=2023-01-01T00:00] 30 // SaleDetails[saleId=2, amount=200.75, date=2023-01-15T00:00] 31}

This method retrieves all sales within the specified date range.

Summary

Congratulations! You've now extended a basic sales aggregator to an advanced one capable of filtering, aggregating, and providing formatted outputs in Java. These skills are crucial for handling data streams efficiently, especially when dealing with large datasets. Feel free to experiment with similar challenges to reinforce your understanding. Well done, and see you in the next lesson!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.