1

Take the following POJOs:

public class Widget {
    private String fizz;
    private Long buzz;
    private List<Fidget> collaborators;

   // Constructor, getters & setters
}

public class Fidget {
    private String fizz;
    private String foo;

    // Constructor, getters & setters
}

And the following (working) method:

public void compriseWidgets(List<Fidget> fidgetList) {
    List<Widget> widgets = new ArrayList<Widget>();
    Widget currentWidget = null;

    for (Fidget fidget : fidgetList) {
        if (currentWidget == null || 
                !currentWidget.getFizz().equals(fidget.getFizz())) {

            currentWidget = new Widget();
            widgets.add(currentWidget);
            currentWidget.setFizz(fidget.getFizz());
            currentWidget.setBuzz(fidget.getFoo().length());
        }

        currentWidget.getCollaborators().add(fidget);
    }

    return widgets;
}

Here we want to return a List<Widget> and populate that list only:

  1. From the first Fidget in the input list (hence currentWidget == null); and
  2. If the Fidget and currentWidget have the same fizz value

Furthermore, we want to keep appending collaborators to the currentWidget regardless of whether the fizzes match or not.

My problem

A new code style guideline is requiring that we declare ALL variables with final...meaning I need to get the above code refactored to look like so:

public void compriseWidgets(final List<Fidget> fidgetList) {
    final List<Widget> widgets = new ArrayList<Widget>();
    final Widget currentWidget = null;

    for (final Fidget fidget : fidgetList) {
        ...
    }

    return widgets;
}

Because it requires both the creation of a new Widget inside the loop, but an external (outside the loop) reference to a Widget that we can add collaborators to, I'm at a total loss for how to rewrite this with final. Any ideas? Also, please note, this is nothing that I can "push back" on, I just need to figure it out and get it working with the new coding standard.

hotmeatballsoup
  • 385
  • 6
  • 58
  • 136
  • 3
    Are you *sure*? I have sometimes seen a style guideline that all *method parameters* be declared `final`, but never that all variables whatever be `final`. Whatever problem a guideline of the latter kind is intended to solve, the resulting workarounds are likely to be a lot worse. – John Bollinger Dec 10 '18 at 18:14
  • For example, you can comply with the letter of the guideline by declaring local variables as collections, arrays, or other containers where you need mutable data. Let the containers be `final`, and mutate the contents all you like. Is that better than using non-`final` variables where it makes sense to do so? By no means. – John Bollinger Dec 10 '18 at 18:18
  • Also what is the point of your `if` statement? `currentWidget` is always null so only the first condition of the OR gets evaluated always. – Nicholas K Dec 10 '18 at 18:19
  • 1
    @JohnBollinger I agree with you, but **yes**, I am sure. The whole team has already pushed back on this, its just the lay of the current land. :-/ – hotmeatballsoup Dec 10 '18 at 18:20
  • @NicholasK `currentWidget` is only null going into the first iteration of the for-loop. Since its null during this first iteration, the `if`-statement executes and instantiates `currentWidget`. From that point on, regardless of how many `Fidgets` are in the `fidgetList`, and regardless of whether or not the `if`-statement will execute again or not (based on the `fizz` strings matching), `currentWidget` will continue to be non-null. Its confusing, I know. :-/ – hotmeatballsoup Dec 10 '18 at 18:22
  • Btw, how is the compriseWidgets method working? Its method signature returns void, but the method body returns List. Also, about the logic in the loop... it's taking a list of List, and then for each Fidget object, it creates a new Widget object with just a single Fidget in its list? – Sam Malayek Dec 10 '18 at 19:04
  • 2
    Time to look for a new employer. – shmosel Dec 10 '18 at 19:28

2 Answers2

1

To expand on my comment, you could convert your example code more or less mechanically, like so:

public List<Widget> compriseWidgets(final List<Fidget> fidgetList) {
    final List<Widget> widgets = new ArrayList<Widget>();
    final Widget[] currentWidget = new Widget[] {null};

    for (final Fidget fidget : fidgetList) {
        if (currentWidget[0] == null || 
                !currentWidget[0].getFizz().equals(fidget.getFizz())) {

            currentWidget[0] = new Widget();
            widgets.add(currentWidget);
            currentWidget.setFizz(fidget.getFizz());
            currentWidget.setBuzz(fidget.getFoo().length());
        }

        currentWidget.getCollaborators().add(fidget);
    }

    return widgets;
}

Many variables can be made final without any particular impact, including the lists of Fidgets and Widgets, and the loop variable in the enhanced for loop. The only other variable in the original method was currentWidget, which the implementation modifies. This can be replaced with a (final) array of length 1, whose zeroth element can then be used as a drop-in replacement for the original variable.

A more troublesome requirement along the same lines would be that you may not use assignment statements (initializers in variable declarations not being considered "assignments"). This is pushing toward a more functional style of programming, which I suppose may be the intent of your new guideline. You might, then, approach it something like this:

public List<Widget> compriseWidgets(final List<Fidget> fidgetList) {
    final List<Widget> widgets = new ArrayList<Widget>();
    final ListIterator<Fidget> fidgets = fidgetList.listIterator();

    while (addWidget(widgets, fidgets)) { /* empty */ }

    return widgets;
}    

private boolean addWidget(final List<Widget> widgets, final ListIterator<Fidget> fidgets) {
    if (fidgets.hasNext()) {
        final Fidget firstFidget = fidgets.next();
        final Widget currentWidget = new Widget();

        widgets.add(currentWidget);
        currentWidget.setFizz(firstFidget.getFizz());
        currentWidget.setBuzz(firstFidget.getFoo().length());
        currentWidget.getCollaborators().add(firstFidget);

        while (fidgets.hasNext()) {
            final nextFidget = fidgets.next();

            if (currentWidget.getFizz().equals(nextFidget.getFizz())) {
                currentWidget.getCollaborators().add(nextFidget);
            } else {
                fidgets.previous();
                return true;
            }
        }
    }

    return false;
}

This is pretty much the same trick, just a little less obvious. The mutable state is hidden in the call stack (each invocation of addWidget() stands in for a mutation of the original method's currentWidget()) and in a container object, this time a ListIterator.

One could go further in the functional programming direction. In general, for example, you could look toward Stream-based approaches, though I don't think that works out completely cleanly in this particular case. More general functional programming does not have the constraints that apply to Streams, however.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157
0

The Builder design pattern is a great way to build immutable objects. Source: https://stackoverflow.com/a/15461337/4245294

What I love about this version of this design pattern is how it gives you the perfect spot for validation rules before object creation.

Example applied to this problem:

public class Widget {
    private final String fizz;
    private final Long buzz;
    private final List<Fidget> collaborators;

    private Widget(Builder builder) {
        this.fizz = builder.fizz;
        this.buzz = builder.buzz;
        this.collaborators = builder.collaborators;
    }

    public static Builder builder() {
        return new Builder();
    }

    public static class Builder {
        private String fizz;
        private Long buzz;
        private List<Fidget> collaborators = new ArrayList<>();

        public Builder addFizz(String fizz) {
            this.fizz = fizz;
            return this;
        }

        public Builder addBuzz(Long buzz) {
            this.buzz = buzz;
            return this;
        }

        public Builder addCollaborators(List<Fidget> fidgets) {
            collaborators.addAll(fidgets);
            return this;
        }

        public Builder addCollaborator(Fidget fidget) {
            collaborators.add(fidget);
            return this;
        }

        private void validate() throws InvalidArgumentException{
            ArrayList<String> invalidArguments = new ArrayList<>();
            boolean failedValidation = false;
            if (collaborators.isEmpty()) {
                invalidArguments.add("collaborators");
                failedValidation = true;
            }
            if (this.fizz == null) {
                invalidArguments.add("fizz");
                failedValidation = true;
            }
            if (this.buzz == null) {
                invalidArguments.add("buzz");
                failedValidation = true;
            }
            if (failedValidation) {
                throw new InvalidArgumentException(invalidArguments.toArray(new String[0]));
            }
        }

        public Widget build() {
            validate();
            return new Widget(this);
        }
    }
}

And you create a valid Widget object like so:

Widget widget = Widget.builder().addFizz("test").addBuzz(999).addCollaborators(fidgets).build();

Your compriseWidget method has problems that I mentioned in a comment to the Question, otherwise I would provide an example for that as well.

Sam Malayek
  • 3,595
  • 3
  • 30
  • 46
  • Thanks @LXXIII I will check it out. In the meantime do you mind moving this to a comment, or adding code to make this a full answer? – hotmeatballsoup Dec 10 '18 at 18:23