Topics to learn Today

  • Method Overloading, Static & Instance Methods
  • Classes and Objects
  • Oops Concepts
  • Java Collections – The Basics
  • java.time
  • File I/O and Streams
  • Exception Classes
  • System & Runtime
  • Problem

Method Overloading, Static & Instance Methods

Method Overloading

Overloading means you can have multiple methods with the same name but different parameters.

Java uses the number and type of parameters to decide which version to run.

public static int square(int x) {
    return x * x;
}
 
public static double square(double x) {
    return x * x;
}

Usage:

System.out.println(square(5));     // 25 (int version)
System.out.println(square(3.5));   // 12.25 (double version)

✅ Same name
✅ Different parameter type or count
❌ Cannot differ by return type only


Static Methods

  • Belong to the class itself
  • Can be called without creating an object
  • Common for utility/helper methods
public class MathUtil {
    public static int square(int x) {
        return x * x;
    }
}

Usage:

int result = MathUtil.square(4);  // No object needed

The main() method is also static because the JVM calls it without creating an object.


Instance Methods

  • Belong to an object
  • You need to create an instance to call them
public class Person {
    String name;
 
    public void greet() {
        System.out.println("Hello, my name is " + name);
    }
}

Usage:

Person p = new Person();
p.name = "Alice";
p.greet();  // Hello, my name is Alice

Instance methods can access non-static fields and other instance methods.


Quick Comparison

FeatureStatic MethodInstance Method
Belongs toClassObject
Accessed byClassName.method()object.method()
Can use this
Common use caseUtility, main()Behavior tied to object state


Classes & Objects – The Basics

A class is like a blueprint.
An object is a real-world thing built from that blueprint.


🔧 What is a Class?

A class defines the structure and behavior of an object — like a blueprint or template.

public class Person {
    String name;
    int age;
 
    void sayHello() {
        System.out.println("Hi, I’m " + name + " and I’m " + age + " years old.");
    }
}
  • name and age are fields (aka instance variables)
  • sayHello() is a method

🧍 What is an Object?

An object is a real instance of a class, created in memory.

Person p = new Person(); // Create an object
p.name = "Alice";
p.age = 25;
p.sayHello();  // Hi, I’m Alice and I’m 25 years old.
  • new Person() creates a new object from the Person class

  • p is a reference to that object


🛠 Constructors

A constructor is a special method used to initialize an object when it’s created.

public class Person {
    String name;
    int age;
 
    // Constructor
    public Person(String n, int a) {
        name = n;
        age = a;
    }
 
    void sayHello() {
        System.out.println("Hi, I’m " + name);
    }
}

Usage:

Person p = new Person("Bob", 30);
p.sayHello();  // Hi, I’m Bob

If you don’t define a constructor, Java provides a default constructor.


🎯 The this Keyword

Used to refer to the current object — often used in constructors or methods.

public Person(String name, int age) {
    this.name = name;  // refers to the object's field
    this.age = age;
}

✅ TL;DR Summary Table

ConceptExampleNotes
Classclass Car { ... }Blueprint for objects
ObjectCar c = new Car();Real instance of a class
FieldString name;Variables inside the class
Methodvoid drive() { ... }Behavior of the class
Constructorpublic Car(...)Initializes an object
thisthis.name = name;Refers to current object’s field


OOPs Concepts – The Core of Java

The Idea is that Java is not just a procedural language — it’s object-oriented. Everything revolves around classes, objects, and the principles of OOP.

OOP = Object-Oriented Programming
It helps organise code using real-world thinking: objects, properties, behaviors, and relationships.


1. Encapsulation – Hiding Data

Bind data (fields) and methods in one unit, and restrict direct access.

  • Keeps data safe from outside interference

  • Achieved using private fields + public getters/setters

public class BankAccount {
    private double balance;
 
    public void deposit(double amount) {
        if (amount > 0) balance += amount;
    }
 
    public double getBalance() {
        return balance;
    }
}
  • balance is encapsulated — cannot be accessed directly

  • Only accessible through methods


2. Inheritance – Reuse Behaviour

One class inherits properties and methods from another.

  • Promotes code reuse

  • Use extends keyword

public class Animal {
    void eat() {
        System.out.println("Eating...");
    }
}
 
public class Dog extends Animal {
    void bark() {
        System.out.println("Barking...");
    }
}

Usage:

Dog d = new Dog();
d.eat();   // inherited
d.bark();  // own method

3. Polymorphism – Many Forms

One interface, many implementations.

a) Compile-Time Polymorphism (Method Overloading)

public class MathUtils {
    int add(int a, int b) { return a + b; }
    double add(double a, double b) { return a + b; }
}

b) Runtime Polymorphism (Method Overriding)

class Animal {
    void speak() { System.out.println("Animal sound"); }
}
 
class Cat extends Animal {
    @Override
    void speak() { System.out.println("Meow"); }
}
Animal a = new Cat();
a.speak();  // Meow (runtime decision)

4. Abstraction – Hiding Implementation Details

Show only essential features, hide the rest.

  • Use abstract classes or interfaces
  • Forces subclasses to implement required behaviour

Abstract Class Example

abstract class Shape {
    abstract void draw();  // no body
}
 
class Circle extends Shape {
    void draw() {
        System.out.println("Drawing circle");
    }
}

Interface Example

interface Vehicle {
    void drive();  // implicitly public and abstract
}
 
class Car implements Vehicle {
    public void drive() {
        System.out.println("Driving car");
    }
}

✅ TL;DR OOPs Summary Table

OOP ConceptMeaningJava Keyword/Feature
EncapsulationHiding data inside a classprivate, getters/setters
InheritanceOne class inherits from anotherextends
PolymorphismOne name, many behaviorsOverloading, Overriding
AbstractionHiding complex logic from the userabstract, interface

Java Collections – The Basics

Java Collections are a framework of interfaces and classes for storing and working with groups of objects. Instead of manually building arrays or linked lists, Java provides built-in, optimized structures.

Collections are part of the java.util package.

1. ArrayList – Resizable Array

  • Fast random access

  • Slow for frequent inserts/removals in the middle

import java.util.ArrayList;
 
ArrayList<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add(1, "C");
list.remove("B");
System.out.println(list.get(0));  // A

Common methods: add(), get(), remove(), set(), contains(), size()


2. LinkedList – Doubly Linked List

  • Good for fast insert/delete at start or end

  • Implements both List and Deque

import java.util.LinkedList;
 
LinkedList<String> list = new LinkedList<>();
list.add("X");
list.addFirst("A");
list.addLast("B");
list.removeFirst();
System.out.println(list.getLast());  // B

Common methods: addFirst(), addLast(), removeFirst(), peek(), poll(), offer()


3. HashMap – Key-Value Storage

  • Stores data in key-value pairs

  • Keys must be unique

  • No order guaranteed

import java.util.HashMap;
 
HashMap<String, Integer> map = new HashMap<>();
map.put("a", 1);
map.put("b", 2);
System.out.println(map.get("a"));  // 1
map.remove("a");

Common methods: put(), get(), remove(), containsKey(), keySet(), entrySet()


4. HashSet – Unique Elements

  • No duplicates allowed
  • Fast lookup and insertion
  • Unordered
import java.util.HashSet;
 
HashSet<String> set = new HashSet<>();
set.add("one");
set.add("two");
set.add("one");  // Ignored
System.out.println(set.contains("two"));  // true

Common methods: add(), remove(), contains(), size(), isEmpty()


5. TreeMap – Sorted Key-Value Map

  • Keys are sorted

  • Backed by a Red-Black Tree

  • Slower than HashMap, but ordered

import java.util.TreeMap;
 
TreeMap<Integer, String> treeMap = new TreeMap<>();
treeMap.put(2, "B");
treeMap.put(1, "A");
System.out.println(treeMap.firstKey());  // 1

Common methods: put(), get(), firstKey(), lastKey(), subMap()


6. TreeSet – Sorted Set

  • No duplicates

  • Elements are sorted

  • Slower than HashSet

import java.util.TreeSet;
 
TreeSet<String> set = new TreeSet<>();
set.add("B");
set.add("A");
System.out.println(set.first());  // A

Common methods: add(), remove(), first(), last(), higher(), lower()


7. Queue and Deque – FIFO and Double-Ended

Queue (First-In-First-Out)

import java.util.Queue;
import java.util.ArrayDeque;
 
Queue<String> queue = new ArrayDeque<>();
queue.offer("A");
queue.offer("B");
System.out.println(queue.poll());  // A

Deque (Double-Ended Queue)

import java.util.Deque;
import java.util.ArrayDeque;
 
Deque<String> deque = new ArrayDeque<>();
deque.addFirst("X");
deque.addLast("Y");
System.out.println(deque.pollFirst());  // X

Common methods:

  • Queue: offer(), poll(), peek()
  • Deque: addFirst(), addLast(), pollFirst(), pollLast()

8. Collections Utility Class

A helper class with static methods to work with lists, sets, etc.

import java.util.Collections;
import java.util.ArrayList;
 
ArrayList<Integer> nums = new ArrayList<>();
Collections.addAll(nums, 3, 1, 2);
 
Collections.sort(nums);       // [1, 2, 3]
Collections.reverse(nums);    // [3, 2, 1]
Collections.shuffle(nums);    // Random order

Useful methods:

  • sort(), reverse(), shuffle()
  • max(), min(), frequency()
  • binarySearch()
  • synchronizedList() for thread safety

Summary Table

Collection TypeOrdered?Allows Duplicates?Unique Features
ArrayListFast access
LinkedListFast insertion/removal
HashMapKeys ❌Key-value pairs
HashSetUnique, unordered elements
TreeMapKeys ❌Sorted key-value
TreeSetSorted unique elements
Queue/DequePartiallyFIFO / double-ended operations
CollectionsN/AN/AUtility methods


java.time – Working with Dates and Time in Java

The Idea is to learn how to work with dates, times, durations, and formatting in Java using the modern java.time package (introduced in Java 8).

Old date APIs like Date and Calendar were clunky and error-prone.
java.time is immutable, thread-safe, and much more readable.

🧱 Core Classes in java.time


1. LocalDate – Date only (no time)

import java.time.LocalDate;
 
LocalDate date = LocalDate.now();             // Today
LocalDate dob = LocalDate.of(1990, 5, 15);    // 1990-05-15

Common methods:

date.getYear();
date.getMonth();       // MAY
date.getDayOfWeek();   // MONDAY
date.plusDays(5);      // add days
date.minusYears(1);    // subtract years

2. LocalTime – Time only (no date)

import java.time.LocalTime;
 
LocalTime time = LocalTime.now();             // Current time
LocalTime fixed = LocalTime.of(14, 30);       // 14:30

Common methods:

time.getHour();
time.plusMinutes(15);

3. LocalDateTime – Date + Time (no time zone)

import java.time.LocalDateTime;
 
LocalDateTime dt = LocalDateTime.now();
LocalDateTime meeting = LocalDateTime.of(2025, 5, 15, 10, 0);

Combine date and time:

LocalDate date = LocalDate.of(2025, 5, 15);
LocalTime time = LocalTime.of(9, 30);
LocalDateTime combined = LocalDateTime.of(date, time);

4. Instant – Timestamp (machine time)

  • Represents a specific moment in UTC

  • Often used for logging, timestamps, APIs

import java.time.Instant;
 
Instant now = Instant.now();  // e.g., 2025-05-15T10:45:30Z

🔄 Operations with Dates

LocalDate today = LocalDate.now();
LocalDate tomorrow = today.plusDays(1);
LocalDate lastWeek = today.minusWeeks(1);
boolean isBefore = today.isBefore(tomorrow);  // true

⏳ Durations and Periods

Period – Date-based (years, months, days)

import java.time.Period;
 
Period age = Period.between(LocalDate.of(2000, 1, 1), LocalDate.now());
System.out.println(age.getYears());  // e.g., 25

Duration – Time-based (hours, minutes, seconds)

import java.time.Duration;
 
Duration d = Duration.between(LocalTime.of(10, 0), LocalTime.of(12, 30));
System.out.println(d.toMinutes());  // 150

🧾 Formatting & Parsing

import java.time.format.DateTimeFormatter;
import java.time.LocalDate;
 
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd-MM-yyyy");
LocalDate date = LocalDate.now();
String formatted = date.format(formatter);  // "15-05-2025"

Parsing:

LocalDate parsedDate = LocalDate.parse("31-12-2025", formatter);

🌍 Time Zones with ZonedDateTime

import java.time.ZonedDateTime;
import java.time.ZoneId;
 
ZonedDateTime nowInParis = ZonedDateTime.now(ZoneId.of("Europe/Paris"));
ZonedDateTime nowInIST = ZonedDateTime.now(ZoneId.of("Asia/Kolkata"));

Convert between zones:

ZonedDateTime inUTC = nowInParis.withZoneSameInstant(ZoneId.of("UTC"));

✅ TL;DR Summary Table

ClassDescriptionExample
LocalDateDate onlyLocalDate.of(2025, 5, 15)
LocalTimeTime onlyLocalTime.of(14, 30)
LocalDateTimeDate + timeLocalDateTime.now()
InstantTimestamp in UTCInstant.now()
PeriodDifference in datesPeriod.between(d1, d2)
DurationDifference in timeDuration.between(t1, t2)
ZonedDateTimeDate-time with time zone infoZonedDateTime.now(ZoneId)
DateTimeFormatterFormat/parse datesformat(), parse()

Let me know if you’d like this expanded with time arithmetic problems, ISO format quirks, or legacy date conversion examples.

Here’s your Java: File I/O and Streams topic — clean, structured, and in the same note style you’ve been using for foundational learning.



📂 Java File I/O & Streams – Read, Write, Process Files

The Idea is to learn how Java handles file reading, writing, and data streams, using the java.io and java.nio.file packages.

You’ve written data in variables. Now, let’s persist it — save to disk, read from files, and process content line-by-line or byte-by-byte.

📄 1. File Class – Represent a File or Directory

import java.io.File;
 
File file = new File("example.txt");
 
System.out.println(file.exists());      // true or false
System.out.println(file.getAbsolutePath());

You can check, create, or delete files/directories:

file.createNewFile();    // Creates new file if it doesn’t exist
file.delete();           // Deletes the file

📝 2. Writing to a File (FileWriter, BufferedWriter)

import java.io.FileWriter;
import java.io.BufferedWriter;
 
BufferedWriter writer = new BufferedWriter(new FileWriter("data.txt"));
writer.write("Hello, file!");
writer.newLine();
writer.write("Another line");
writer.close();  // Always close!

📖 3. Reading from a File (FileReader, BufferedReader)

import java.io.FileReader;
import java.io.BufferedReader;
 
BufferedReader reader = new BufferedReader(new FileReader("data.txt"));
String line;
while ((line = reader.readLine()) != null) {
    System.out.println(line);
}
reader.close();

BufferedReader is preferred over raw FileReader because it’s more efficient (reads in chunks, not character-by-character).


🧹 4. Try-with-Resources (Auto Close)

Since Java 7, you can auto-close files safely:

try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
    String line;
    while ((line = reader.readLine()) != null) {
        System.out.println(line);
    }
} catch (IOException e) {
    e.printStackTrace();
}

⚡ 5. Files Utility (from java.nio.file)

import java.nio.file.Files;
import java.nio.file.Paths;
 
List<String> lines = Files.readAllLines(Paths.get("data.txt"));
Files.write(Paths.get("output.txt"), lines);

Very concise, useful for small files.


🔁 6. InputStream / OutputStream – For Binary Data

Used to read/write binary data (e.g. images, PDFs, etc.)

Write Bytes

import java.io.FileOutputStream;
 
FileOutputStream out = new FileOutputStream("binary.dat");
out.write(65);  // writes ASCII 'A'
out.close();

Read Bytes

import java.io.FileInputStream;
 
FileInputStream in = new FileInputStream("binary.dat");
int data = in.read();
System.out.println(data);  // 65
in.close();

Use BufferedInputStream / BufferedOutputStream for performance


✅ TL;DR Summary Table

TaskClass / ToolNotes
File referenceFileCheck, create, delete files
Write textFileWriter, BufferedWriterFor writing strings to file
Read textFileReader, BufferedReaderEfficient line-by-line reading
Short file I/OFiles.readAllLines(), Files.write()Java NIO utility (since Java 7)
Binary dataInputStream, OutputStreamFor images, audio, etc.
Safe closingtry-with-resourcesAutomatically closes streams


🚨 Java Exception Classes – Handle Errors Gracefully

The Idea is to understand how Java handles errors using exceptions — instead of crashing the program, you can catch and recover from problems.

Exceptions are objects that represent runtime problems — and Java gives you a full hierarchy of exception classes to work with.

⚙️ What is an Exception?

An exception is an event that disrupts normal flow of the program.

  • It can be caught and handled

  • It is represented as an object of a subclass of Throwable

try {
    int result = 10 / 0;
} catch (ArithmeticException e) {
    System.out.println("Cannot divide by zero");
}

🧬 Exception Class Hierarchy

Throwable
├── Exception
│   ├── IOException
│   ├── SQLException
│   └── ...
├── RuntimeException
│   ├── NullPointerException
│   ├── IndexOutOfBoundsException
│   ├── ArithmeticException
│   └── ...
└── Error (don't handle this)

🧾 Checked vs Unchecked Exceptions

TypeInherits FromMust Handle?Examples
CheckedException✅ YesIOException, SQLException
UncheckedRuntimeException❌ NoNullPointerException, IndexOutOfBoundsException
  • Checked exceptions: Compiler forces you to handle them (try-catch or throws)

  • Unchecked exceptions: Optional to handle, but can still crash program


📦 Common Exception Classes

✅ Unchecked (Runtime)

ClassCause
NullPointerExceptionAccessing method/field on null
ArrayIndexOutOfBoundsExceptionInvalid array index
ArithmeticExceptionDivision by zero
IllegalArgumentExceptionBad arguments to a method

✅ Checked

ClassCause
IOExceptionFile or I/O errors
SQLExceptionDatabase access issues
FileNotFoundExceptionFile not found
ParseExceptionString parsing failed

🧰 How to Handle Exceptions

try {
    int[] arr = {1, 2};
    System.out.println(arr[3]);
} catch (ArrayIndexOutOfBoundsException e) {
    System.out.println("Index out of bounds!");
} finally {
    System.out.println("Always runs");
}
  • try: Code that might fail

  • catch: Handle specific exception

  • finally: Always runs, for cleanup


🎯 Throwing Exceptions Yourself

public void setAge(int age) {
    if (age < 0) {
        throw new IllegalArgumentException("Age can't be negative");
    }
    this.age = age;
}

Use throw to create and send an exception


🧱 Creating Custom Exception

public class InvalidInputException extends Exception {
    public InvalidInputException(String message) {
        super(message);
    }
}
throw new InvalidInputException("Invalid value provided");

🕵️ Catching Unknown Exceptions

Yes — you can encounter unknown exceptions. These are runtime issues you didn’t explicitly handle, like:

  • Unexpected null values

  • Invalid input

  • Logic bugs

  • Library exceptions

✅ Catching All Exceptions

try {
    // risky code
} catch (Exception e) {
    System.out.println("Something went wrong: " + e.getMessage());
    e.printStackTrace();
}
  • Catches any checked or unchecked exception

  • Good for fallback logging or debugging


try {
    // risky code
} catch (Throwable t) {
    System.out.println("Unexpected fatal error: " + t.getClass());
}
  • Throwable is the superclass of everything (Exception + Error)

  • Only use this for logging or diagnostics — don’t try to recover from OutOfMemoryError, etc.


👍 Best Practice

try {
    riskyOperation();
} catch (IOException e) {
    // handle known issue
} catch (Exception e) {
    // catch any unknown issues
    logError(e);
}
  • Always catch specific exceptions first

  • Add a general catch block if needed for unknowns or unexpected failures


✅ TL;DR Summary Table

ConceptKeyword / ClassNotes
Throw exceptionthrow new Exception()Create and send error
Catch exceptioncatch (Exception e)Handle gracefully
Always run blockfinallyCleanup or final actions
Custom exceptionclass MyException extends ExceptionYour own logic
Must handle?Checked: ✅ Yes, Unchecked: ❌ NoCompiler enforces for checked
Unknown exceptionscatch (Exception e) or catch (Throwable t)For unexpected issues



System & Runtime – Interacting with the JVM and OS


🖥️ 1. System Class – Static Environment Tools

  • Contains only static methods and fields

  • No need to create an object

  • Utility class to deal with:

    • I/O streams

    • Time

    • Environment variables

    • Memory info

    • Exiting JVM


🔹 Common System Uses

✅ Standard Output and Error

System.out.println("Normal output");
System.err.println("This is an error");

✅ Input via System.in

Scanner sc = new Scanner(System.in);  // uses System.in internally

✅ Current Time

long time = System.currentTimeMillis();   // milliseconds since epoch
long nano = System.nanoTime();            // high-res time for benchmarking

✅ Environment and Properties

System.getenv("PATH");                     // OS environment variable
System.getProperty("os.name");             // JVM system property
System.getProperties();                    // All system properties

✅ Exit Program

System.exit(0);     // Exit normally
System.exit(1);     // Exit with error code

✅ Array Copying (Faster than loops)

int[] src = {1, 2, 3};
int[] dest = new int[3];
System.arraycopy(src, 0, dest, 0, 3);

🚀 2. Runtime Class – Managing the Running JVM

  • Runtime is a singleton (only one per JVM)

  • Use Runtime.getRuntime() to access it

  • Lets you:

    • Monitor memory

    • Run external processes

    • Add shutdown hooks

    • Suggest garbage collection


🔹 Common Runtime Uses

✅ Memory Information

Runtime r = Runtime.getRuntime();
long max = r.maxMemory();
long used = r.totalMemory() - r.freeMemory();
System.out.println("Used memory: " + used);

✅ Garbage Collection (Suggestion)

System.gc();             // Static (System calls Runtime under the hood)
Runtime.getRuntime().gc(); // Same effect

Note: This suggests, not forces, garbage collection.

✅ Run External Commands

Runtime.getRuntime().exec("notepad");  // On Windows

You can launch external programs (be cautious, OS-dependent)

✅ Add Shutdown Hook

Runtime.getRuntime().addShutdownHook(new Thread(() -> {
    System.out.println("Program is shutting down...");
}));

🧠 System vs Runtime

FeatureSystemRuntime
TypeStatic utility classSingleton object per JVM
AccessDirect (System.out, etc.)Via Runtime.getRuntime()
Controls JVM?IndirectYes (memory, shutdown, exec)
Good forI/O, env, time, exitMemory control, external processes
Thread-safe?YesMostly, but manage with care

✅ TL;DR Summary Table

TaskUse
Print outputSystem.out.println()
Print errorSystem.err.println()
Read inputSystem.in (via Scanner)
Exit programSystem.exit(code)
Get timeSystem.currentTimeMillis()
Get environment varSystem.getenv("KEY")
Run external processRuntime.getRuntime().exec("cmd")
Get memory infoRuntime.getRuntime().totalMemory()
Suggest GCSystem.gc() or Runtime.gc()
Shutdown hookRuntime.addShutdownHook()

Today’s Problem

Multi-File Analyser with Benchmarking and Report Generation

Objective:

Build a Java application that:

  • Analyzes multiple text files

  • Extracts detailed statistics (word count, frequency, unique words)

  • Benchmarks performance (time + memory)

  • Generates a structured report as output

  • Uses clean OOP design, exception handling, collections, file I/O, and a middleware for benchmarking


✅ Requirements

📥 Input

  • A directory path provided by the user (e.g. "./texts/")

  • The directory contains multiple .txt files (assume UTF-8 text)

  • Your program must read all .txt files


⚙️ For Each File

  • Read the file

  • Count:

    • Number of lines

    • Number of words

    • Number of unique words

  • Identify top 5 most frequent words

  • Ignore punctuation and case


📤 Output Report (as file report.txt)

For each file:

File: file1.txt
Lines: 120
Words: 945
Unique Words: 348
Top 5 Words:
1. the – 52
2. java – 41
3. and – 35
4. you – 33
5. code – 31

At the end of the report:

=== Benchmark: Analysis Completed ===
Time: 1,238,912 ns
Memory Used: 1,045,672 bytes
Files Processed: 5

🧱 Technical Constraints

You must use:

  • File, BufferedReader, Files.walk() or Files.list()

  • HashMap, TreeMap, PriorityQueue (for top words)

  • OOP: at least two classes (FileAnalyzer, ReportGenerator, etc.)

  • Exception handling (invalid paths, read errors, empty files)

  • Your own BenchmarkUtil middleware to time and profile the entire operation

  • String and Pattern or split() for tokenizing words

  • Optional: Use concurrency (ExecutorService) to process files in parallel

🔁 Bonus Features (Optional)

  • Use ExecutorService to analyze files concurrently

  • Use java.time to timestamp start/end time in the report

  • Compress the report as a .zip file after generation

  • Track words across all files and print the global top 10