Table of Contents

Passing code as a parameter in Java 8

  • Last Updated: July 21, 2023
  • By: javahandson
  • Series
img

Passing code as a parameter in Java 8

In this article we will learn passing code as a parameter in Java 8, the design idea that turns changing requirements into a one line change. We will start with copy pasted filter methods, move to an interface, then to anonymous classes, and finally see how a lambda finishes the job.

1. Introduction

Think about how you order coffee. You do not walk behind the counter and brew it yourself. You hand the barista a short instruction, and the barista runs it later. Your instruction travelled as a parameter.

Java code can travel the same way. We have always passed numbers, strings and objects into a method. Passing a piece of behaviour felt clumsy until Java 8 arrived, and the whole Stream API later grew on top of that one improvement.

This idea carries a formal name too. People call it behaviour parameterization. The name sounds heavy, yet the meaning stays simple: your method keeps the boring part, and the caller supplies the interesting part.

1.1 What This Article Covers

  • Why copy pasted filter methods fall apart when requirements change
  • What passing code as a parameter actually means in plain words
  • How an interface lets you hand behaviour to a method
  • Where anonymous classes help, and where they hurt
  • How a lambda finished this journey without changing your design
  • A repeatable recipe for spotting the varying part of any algorithm
  • Other shapes of the pattern: actions, transformations, execute around and callbacks
  • How this connects to the Strategy and Template Method patterns

1.2 Who This Is For

You need only basic Java here. If you can write a class, a loop and an if block, you have enough.

This article stays on the design idea. For lambda syntax, scope rules and the meaning of this, our Lambda Expression in Java 8 article covers all of it in depth.

2. The Problem: Requirements That Keep Changing

Requirements never sit still. Your manager asks for one report today and a slightly different one tomorrow. Good code should absorb that without a rewrite.

2.1 The Employee Example

We will use one small class through the whole article. An Employee has a name, a designation and a salary.

package com.javahandson;

public class Employee {
    private String name;
    private String designation;
    private double salary;

    public Employee(String name, String designation, double salary) {
        this.name = name;
        this.designation = designation;
        this.salary = salary;
    }

    public String getName() { return name; }
    public String getDesignation() { return designation; }
    public double getSalary() { return salary; }

    @Override
    public String toString() {
        return "Employee[name=" + name + ", designation=" + designation
                + ", salary=" + salary + "]";
    }
}

Now the requests start arriving. On Monday your manager wants every employee whose designation is Manager. On Tuesday the ask changes to everyone earning more than 50000. By Friday it becomes both conditions together.

2.2 The First Attempt: One Method Per Rule

Most of us solve this the obvious way. We write a filter method for the first rule.

public static List<Employee> filterByDesignation(List<Employee> employees) {
    List<Employee> result = new ArrayList<>();
    for (Employee employee : employees) {
        if (employee.getDesignation().equals("Manager")) {
            result.add(employee);
        }
    }
    return result;
}

Then the salary rule arrives, so we copy the method and change one line.

public static List<Employee> filterBySalary(List<Employee> employees) {
    List<Employee> result = new ArrayList<>();
    for (Employee employee : employees) {
        if (employee.getSalary() > 50000) {
            result.add(employee);
        }
    }
    return result;
}

Friday brings the combined rule, and we copy the method a third time.

public static List<Employee> filterByDesignationAndSalary(List<Employee> employees) {
    List<Employee> result = new ArrayList<>();
    for (Employee employee : employees) {
        if (employee.getDesignation().equals("Manager")
                && employee.getSalary() > 50000) {
            result.add(employee);
        }
    }
    return result;
}

2.3 Why This Hurts

Look at the three methods side by side. Almost every line repeats. Only the condition inside the if differs.

That duplication costs real money over time:

  • Every new rule means a new method. Ten rules leave you with ten near identical methods.
  • Bug fixes multiply. A mistake in the loop now hides in ten places instead of one.
  • Names turn ugly. Try naming the method for “Manager or Engineer, salary above 40000, joined this year”.
  • Tests balloon. Each method needs its own test even though the loop never changes.

One rule of thumb explains everything that follows. Separate the part that stays the same from the part that keeps changing. Here the loop stays the same and the condition changes.

3. What Passing Code as a Parameter Really Means

So how do we hand a condition to a method? That question sits at the heart of this article.

3.1 The Plain English Definition

Passing code as a parameter means you wrap a block of logic, hand it to another method, and let that method run the block whenever it wants.

You never run the block yourself. You only describe it. The receiving method decides the timing, the order, and how many times it runs.

That last sentence deserves a pause. Control moves to the other side. You supply the what, and the method owns the when.

3.2 An Everyday Analogy

Picture a vending machine with a slot for instruction cards. The machine always does the same three things: pick an item, read your card, drop the item if the card says yes.

You write “only chocolate” on one card. Tomorrow you write “anything under fifty rupees” on another. The machine never changes. Your card carries the rule.

Our filter method is that machine. The condition is the card.

3.3 Data Versus Behaviour

We already pass data all the time. A String name, an int count and a List of employees all count as data.

Behaviour differs. Behaviour answers a question or performs an action. “Does this employee earn above 50000?” counts as behaviour.

Java has no raw code type, so behaviour travels inside an object. Every stage below wraps the same condition in an object, and each stage simply wraps it with less typing than the previous one.

4. Step One: Pull the Changing Part Into an Interface

Our changing part always answers yes or no about one employee. An interface with a single boolean method captures that perfectly.

4.1 Defining the Predicate Interface

package com.javahandson;

public interface EmployeePredicate {
    boolean test(Employee employee);
}

A predicate is simply a test that returns true or false. The word comes from logic, and Java borrowed it for exactly this purpose.

Notice the interface holds one abstract method and nothing else. Keep that detail in mind, because it becomes important in section 7.

4.2 Writing the Implementations

Each rule now becomes a tiny class.

package com.javahandson;

public class ManagerPredicate implements EmployeePredicate {
    @Override
    public boolean test(Employee employee) {
        return employee.getDesignation().equals("Manager");
    }
}

class HighSalaryPredicate implements EmployeePredicate {
    @Override
    public boolean test(Employee employee) {
        return employee.getSalary() > 50000;
    }
}

Both classes hold no state. They only answer a question about the employee you hand them.

4.3 One Filter Method Forever

Here comes the payoff. We write the loop once and accept the rule as a parameter.

public static List<Employee> filterEmployees(List<Employee> employees,
                                             EmployeePredicate predicate) {
    List<Employee> result = new ArrayList<>();
    for (Employee employee : employees) {
        if (predicate.test(employee)) {   // the caller's code runs right here
            result.add(employee);
        }
    }
    return result;
}

List<Employee> managers = filterEmployees(list, new ManagerPredicate());
List<Employee> wellPaid = filterEmployees(list, new HighSalaryPredicate());

Read that comment line again. The caller supplied the logic, and our loop triggers it. That single line shows passing code as a parameter in action.

4.4 What We Gained

  • One loop covers every rule you will ever write
  • A new requirement adds a class and touches nothing else
  • The loop needs one test, and each rule needs its own small test
  • Rules turn reusable, so counting and reporting can share them

5. The Verbosity Problem

The design improved a lot. The typing did not.

5.1 A Class for Every Tiny Rule

Count what one rule costs us. A file, a package line, a class declaration, an @Override, a method signature and a closing brace. All of that wraps a single comparison.

Ten rules mean ten files. Your project explorer fills with classes named after conditions, and none of them holds real logic.

5.2 The Noise to Signal Ratio

Look at HighSalaryPredicate once more. Eight lines exist so one comparison can travel to a method.

Programmers call this ceremony. The compiler wants it, but a reader gains nothing from it. Good code keeps ceremony low and meaning high.

5.3 The Rule Lives Far From the Call

There is a second cost, and it hurts more than the typing. The condition now sits in another file.

Someone reading filterEmployees(list, new HighSalaryPredicate()) cannot tell what the rule does. They must open a second file to find one comparison. Reading code beats writing code in frequency, so this matters.

6. Anonymous Classes: A Half Step Forward

Java gave us anonymous classes long before Java 8. They let you declare and create a class in one expression, without naming it.

6.1 Writing the Rule Inline

List<Employee> result = filterEmployees(list, new EmployeePredicate() {
    @Override
    public boolean test(Employee employee) {
        return employee.getDesignation().equals("Manager")
                && employee.getSalary() > 50000;
    }
});

No extra file appears now. The rule sits right where you use it, so section 5.3 stops being a problem.

6.2 What Still Annoys Us

Count the characters again. The words new EmployeePredicate(), @Override and public boolean test(Employee employee) add nothing new. Your method signature already announced all three.

Six lines of scaffolding still surround one comparison. Java developers lived with that trade for years, and libraries stayed shy about accepting behaviour because callers hated writing it.

7. How Lambdas Finished the Job

Java 8 removed the last of the ceremony. Everything you designed in section 4 stays exactly as it was.

7.1 The Same Filter in One Line

List<Employee> engineers =
        filterEmployees(list, e -> e.getDesignation().equals("Engineer"));

List<Employee> seniorManagers =
        filterEmployees(list, e -> e.getDesignation().equals("Manager")
                                && e.getSalary() > 50000);

System.out.println(engineers);
// Output: [Employee[name=Amar, designation=Engineer, salary=40000.0]]

Six lines shrank to one, and the rule still sits at the call site. A reader now grasps the whole thing in a glance.

7.2 Your Design Never Changed

This point matters more than the shorter syntax. Compare the two calls:

  • filterEmployees keeps the same signature it had in section 4.3
  • EmployeePredicate keeps the same single method
  • The loop still asks the rule about every employee
  • Only the way you write the rule at the call site got shorter

A lambda is not a new mechanism. It is a shorter way to hand over the same object your interface always expected, which is why the whole pattern survived into Java 8 unchanged.

7.3 Why the Single Method Rule Mattered

Section 4.1 asked you to remember one detail. EmployeePredicate declares exactly one abstract method.

That shape earns a name: a functional interface. Java 8 accepts a lambda only where such an interface appears, because one method leaves no ambiguity about what your lambda implements.

Our Custom Functional Interface in Java article covers the rules, the @FunctionalInterface annotation and what counts as abstract.

7.4 What This Article Leaves to Others

Lambdas carry a few rules of their own. None of them changes the design idea above, so we point you to the right place instead of repeating them:

8. The Four Stages Side by Side

We travelled through four styles. This table puts them next to each other.

StageLines per ruleExtra filesRule visible at call siteBest for
Separate filter methods9 or moreNoneNo, the rule hides insideOne rule that never changes
Named predicate classes8 or moreOne per ruleOnly through the class nameRules you reuse and test alone
Anonymous classes6NoneYes, buried under scaffoldingJava 7 and older projects
Lambda expressions1NoneYes, and it reads like a sentenceJava 8 and newer, everyday use

8.1 What Actually Improved at Each Stage

Stage two fixed the real problem. Duplication disappeared the moment the loop accepted a rule as a parameter.

Stages three and four fixed nothing about the design. They only removed typing. That distinction helps in interviews, because people often credit lambdas with a change they did not make.

8.2 When a Named Class Still Wins

Lambdas are not always the answer. Reach for a named class when the rule spans many lines, when several teams share it, or when the rule deserves its own unit test.

A good name also documents intent. EligibleForBonusPredicate tells a story that a five line lambda cannot.

9. The Recipe You Can Reuse

Filtering employees was only an example. The same four steps work on any algorithm that keeps growing new variants.

9.1 Find the Part That Varies

Put two versions of your method side by side and read them line by line. The lines that match form the stable skeleton. The lines that differ form your behaviour.

In our example three methods matched on nine lines and differed on one. That ratio told us exactly what to extract.

9.2 Name the Varying Part as a Question or a Job

Say the varying part out loud in one short phrase. “Does this employee qualify?” names a question, so a boolean method fits.

“Print this employee” names a job with no answer, so a void method fits. “Turn this employee into a label” names a transformation, so a method with a return type fits.

9.3 Pick an Interface That Matches the Shape

Java already ships interfaces for the common shapes, so check those before writing your own. Write your own when the parameter names carry meaning that a generic type would lose.

// your own, when the domain wording helps a reader
public interface EmployeePredicate {
    boolean test(Employee employee);
}

// or the ready made one, when the shape is all you need
import java.util.function.Predicate;
Predicate<Employee> rule = e -> e.getSalary() > 50000;

9.4 Keep the Skeleton and Accept the Rest

Write the stable part once, then add one parameter for the behaviour. Making the method generic often costs nothing and widens its reach.

public static <T> List<T> filter(List<T> items, Predicate<T> rule) {
    List<T> result = new ArrayList<>();
    for (T item : items) {
        if (rule.test(item)) {
            result.add(item);
        }
    }
    return result;
}

List<Employee> managers = filter(staff, e -> e.getDesignation().equals("Manager"));
List<String>   longNames = filter(names, n -> n.length() > 5);

9.5 A Quick Checklist

  • Do two or more methods share most of their lines?
  • Can you describe the difference in one short phrase?
  • Will new variants of that difference keep arriving?
  • Does the skeleton make sense without knowing the variant?

Four yes answers mean you found a good candidate. A no on the third question usually means you should leave the code alone.

10. Beyond Filtering: Other Shapes of the Same Idea

A yes or no rule is only the first shape. Three more show up constantly in real projects.

10.1 An Action Instead of a Test

Sometimes the varying part performs a job and answers nothing. Printing, logging and saving all fit that description.

public static <T> void forEachItem(List<T> items, Consumer<T> action) {
    for (T item : items) {
        action.accept(item);
    }
}

forEachItem(staff, e -> System.out.println(e.getName()));
forEachItem(staff, e -> log.info("processing {}", e.getName()));

10.2 The Execute Around Pattern

Here is the shape that wins the most real code. Some tasks need identical setup and cleanup around a small piece of varying work.

Opening a file, starting a transaction, acquiring a lock and timing a block all share that structure. The setup and cleanup never change. Only the middle changes.

public interface BufferedReaderProcessor {
    String process(BufferedReader reader) throws IOException;
}

public static String readFile(String path, BufferedReaderProcessor task)
        throws IOException {
    try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
        return task.process(reader);      // only this part varies
    }
}

String firstLine = readFile("data.txt", reader -> reader.readLine());
String twoLines  = readFile("data.txt", reader -> reader.readLine()
                                                 + reader.readLine());

Notice what the caller never writes. No try, no close, no leaked file handle on an exception. Your method guarantees the cleanup, and the caller supplies only the interesting middle.

10.3 Callbacks

A callback flips the timing further. You hand over code now, and the method runs it much later, often after an event or a network reply.

public static void loadReport(String id,
                              Consumer<String> onSuccess,
                              Consumer<Exception> onFailure) {
    try {
        onSuccess.accept(fetch(id));
    } catch (Exception ex) {
        onFailure.accept(ex);
    }
}

loadReport("R-100",
        text -> System.out.println("got " + text.length() + " chars"),
        error -> System.out.println("failed: " + error.getMessage()));

Button listeners in Swing and Android work exactly this way. You supply the reaction, and the framework picks the moment.

10.4 Where the JDK Already Does This

Once you know the shape, you start seeing it everywhere in the standard library:

  • List.removeIf keeps the loop and takes your test
  • List.sort keeps the sorting algorithm and takes your comparison
  • Map.computeIfAbsent keeps the lookup and takes your value builder
  • Stream.filter and Stream.map keep the traversal and take your rule
  • Optional.orElseGet keeps the null check and takes your fallback

Every one of those methods hides a loop or a check you no longer write. Our Filtering in Streams article picks up the story from here.

11. Behaviour Parameterization and Design Patterns

Interviewers love this connection. Two classic patterns describe what we built, and Java 8 shrank both of them.

11.1 This Is the Strategy Pattern

Map our code onto the classic vocabulary and it lines up perfectly:

  • EmployeePredicate plays the strategy interface
  • ManagerPredicate and its siblings play the concrete strategies
  • filterEmployees plays the context that runs a strategy
  • The call site picks the strategy at runtime

Textbooks drew this pattern with three boxes and a lot of classes. A lambda collapses each concrete strategy into one line, so the pattern survives while its boilerplate disappears.

11.2 Compared With the Template Method Pattern

Template Method solves a similar problem with inheritance. A parent class fixes the algorithm and leaves one abstract hook for a subclass.

Behaviour parameterization solves it with a parameter instead. That difference carries real weight:

  • A subclass binds the variation at compile time, while a parameter binds it at runtime
  • Inheritance allows one parent, so combining two variations turns awkward
  • Each new variation needs a whole subclass rather than one lambda
  • A parameter keeps the varying code visible at the call site

11.3 Did Lambdas Kill These Patterns?

No, and that makes a good answer in an interview. The patterns describe a design, not an amount of typing.

Java 8 lowered the cost of applying them until the cost almost vanished. Cheap patterns get used far more often, which explains why modern Java code passes behaviour around so freely.

12. Designing Your Own API That Takes Behaviour

Writing such a method takes a little care. Four habits keep your callers happy.

12.1 Put the Behaviour Parameter Last

A trailing lambda reads far better, especially a multi line one. Every JDK method follows this convention, so your API will feel familiar.

// awkward, the data hides after a block of code
filter(e -> e.getSalary() > 50000, staff);

// natural, the block trails at the end
filter(staff, e -> e.getSalary() > 50000);

12.2 Name the Parameter for Its Role

Call it rule, action, printer or fallback. Those words tell a caller what their code must do.

Avoid names such as func, arg2 or lambda. They repeat the type and teach nobody anything.

12.3 Document When and How Often It Runs

Your caller lost control of the timing, so give it back in the documentation. State whether the code runs once per element, only on failure, or possibly never.

Also mention threads. A caller who learns about parallel execution afterwards has already shipped a race condition.

12.4 Accept One Behaviour Where You Can

One behaviour parameter reads well. Two still work when the roles differ clearly, as our success and failure callbacks did.

Four unnamed lambdas at one call site turn unreadable. At that point a small interface with named methods serves everyone better.

13. Common Mistakes and Pitfalls

These trip up almost everyone once. Reading them now saves an afternoon later.

13.1 Abstracting Something That Never Varies

The pattern earns its keep only when variants keep arriving. One rule with no second version in sight needs a plain method.

Premature abstraction adds an interface, a parameter and a layer of indirection for nothing. Wait for the second requirement, then extract.

13.2 Passing a Flag Instead of Behaviour

Many developers reach for a boolean or an enum first. That choice looks smaller and ages terribly.

// the flag approach: every new rule edits this method
public static List<Employee> filter(List<Employee> staff, boolean byDesignation) {
    for (Employee e : staff) {
        if (byDesignation ? e.getDesignation().equals("Manager")
                          : e.getSalary() > 50000) { ... }
    }
}

filter(staff, true);   // what does true mean here?

Read that call site. true explains nothing, and the method grows a new branch for every requirement. Pass the rule and both problems vanish.

13.3 A Rule That Quietly Does Work

A test should answer a question and change nothing else. Saving, logging or list building inside a predicate surprises everyone who reads the call.

// avoid: the "rule" also writes to a list
filter(staff, e -> { audit.add(e); return e.getSalary() > 50000; });

// prefer: filter first, then act
List<Employee> matched = filter(staff, e -> e.getSalary() > 50000);
audit.addAll(matched);

13.4 Forgetting Who Controls the Timing

Your code no longer runs in the order you wrote it. The receiving method might skip it, repeat it, delay it, or run it on another thread.

Short circuiting causes the classic surprise. A method that stops at the first match never calls your rule for the remaining items, so counters inside the rule end up wrong.

13.5 Hiding Expensive Work Inside the Behaviour

Your rule runs once per element. A database call or a file read inside it turns a cheap loop into a slow one.

// one query per employee
filter(staff, e -> repository.findGrade(e.getName()).equals("A"));

// one query in total
Set<String> topGrades = repository.findAllWithGradeA();
filter(staff, e -> topGrades.contains(e.getName()));

13.6 Writing a Monster Block

A twenty line lambda defeats the purpose. The call site should read like a sentence, not like a second method.

Extract the logic into a private method with a clear name, then pass that method instead. Readers get a summary at the call site and the detail nearby.

14. Practical Walkthrough: A Small Report Tool

Time to tie everything together. We will build a tiny report tool that prints any group of employees under any heading.

14.1 The Complete Program

package com.javahandson;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Predicate;

public class ReportTool {

    public static <T> List<T> filter(List<T> items, Predicate<T> rule) {
        List<T> result = new ArrayList<>();
        for (T item : items) {
            if (rule.test(item)) {
                result.add(item);
            }
        }
        return result;
    }

    public static <T> void report(String heading, List<T> items,
                                  Predicate<T> rule, Consumer<T> printer) {
        System.out.println("== " + heading + " ==");
        for (T item : filter(items, rule)) {
            printer.accept(item);
        }
        System.out.println();
    }

    public static void main(String[] args) {
        List<Employee> staff = Arrays.asList(
                new Employee("Amar", "Engineer", 40000),
                new Employee("Iqbal", "Manager", 60000),
                new Employee("Suchit", "Manager", 55000),
                new Employee("Kartik", "Clerk", 35000));

        report("Managers", staff,
                e -> e.getDesignation().equals("Manager"),
                e -> System.out.println(e.getName()));

        report("Senior managers", staff,
                e -> e.getDesignation().equals("Manager") && e.getSalary() > 50000,
                e -> System.out.println(e.getName() + " : " + e.getSalary()));

        report("Everyone else", staff,
                e -> !e.getDesignation().equals("Manager"),
                e -> System.out.println(e.getName() + " (" + e.getDesignation() + ")"));
    }
}
== Managers ==
Iqbal
Suchit

== Senior managers ==
Iqbal : 60000.0
Suchit : 55000.0

== Everyone else ==
Amar (Engineer)
Kartik (Clerk)

14.2 Reading the Code

  • The report method takes two blocks of code, one rule and one printer
  • Neither block runs at the call site, and report controls both
  • Swapping a rule needs no change inside report
  • Both behaviour parameters sit at the end, following section 12.1
  • Generics keep the tool open to orders, products or any other type

14.3 Try It Yourself

Extend the program with three small exercises:

  • Print a heading for employees whose name starts with the letter S
  • Add a Consumer that writes each row into a StringBuilder instead of the console
  • Wrap report in an execute around method that prints the elapsed time

The third exercise reuses section 10.2 directly. Fixed setup, fixed cleanup, and your code in the middle.

15. Interview Questions

Q: What does passing code as a parameter in Java 8 mean?

A: It means you wrap a block of logic, hand it to another method, and let that method run the block later. The caller supplies the varying behaviour, and the method supplies the stable skeleton around it, such as a loop or a try block.

Q: What is behaviour parameterization?

A: Behaviour parameterization is the formal name for this technique. You split an algorithm into the part that stays the same and the part that keeps changing, then accept the changing part as a parameter. Our single filter method with a swappable rule shows it well.

Q: What problem does it solve?

A: It removes duplication caused by changing requirements. Without it, each new rule needs a near identical copy of the same method, so one bug in the loop spreads across every copy and every copy needs its own test.

Q: How does passing behaviour differ from passing an ordinary object?

A: An ordinary object carries values that your method reads. A behaviour object carries logic that your method invokes, so control passes back to the caller’s code at a moment the method chooses. The object mechanism stays the same, but the intent differs completely.

Q: How does this relate to the Strategy design pattern?

A: They describe the same design. The interface plays the strategy, each implementation plays a concrete strategy, and the method that accepts it plays the context. Java 8 did not replace the pattern, it only shrank each concrete strategy from a whole class to one line.

Q: How does it compare with the Template Method pattern?

A: Template Method fixes the algorithm in a parent class and leaves an abstract hook for a subclass, so the variation binds at compile time. Behaviour parameterization passes the variation as an argument, so it binds at runtime and needs no subclass at all.

Q: What is the execute around pattern?

A: Execute around handles tasks with identical setup and cleanup around a small varying middle, such as opening and closing a file. Your method owns the try block and the close call, while the caller passes only the work that happens in between.

Q: Why is a boolean flag a poor substitute for passing behaviour?

A: A flag explains nothing at the call site, since filter(staff, true) hides its meaning. It also forces you to edit the method for every new requirement, which brings back the duplication you tried to remove.

Q: When should you avoid this technique?

A: Avoid it when only one variant exists and no second one looks likely. The extra interface and parameter buy flexibility that nobody uses. Wait for the second requirement, then extract the varying part.

Q: Where does the JDK use passing code as a parameter?

A: Everywhere in modern Java. List.removeIf takes a test, List.sort takes a comparison, Map.computeIfAbsent takes a value builder, Optional.orElseGet takes a fallback, and every stream operation takes a block of code.

16. Conclusion

Let us wrap up what we covered.

  • Copy pasted filter methods break down as soon as requirements shift
  • Splitting the stable skeleton from the changing part removes the duplication
  • An interface with one method carries that changing part into your loop
  • Named classes work, yet they bury one comparison under eight lines
  • Anonymous classes move the logic inline but keep most of the ceremony
  • Lambdas shortened the call site without touching the design underneath
  • The same recipe covers tests, actions, transformations, execute around and callbacks
  • Strategy and Template Method describe this design, and Java 8 made it cheap

One sentence holds the whole article. Keep the part that never changes inside your method, and accept the part that always changes as a parameter.

Write the report tool from section 14 yourself, then swap the rules around. Once that clicks, the rest of Java 8 opens up quickly.

17. Further Reading

Leave a Comment