FizzBuzz Kata With Java Streams
After only a couple of weeks of Judo practice, my son got bored. He complained that he wasn’t learning anything, because he kept doing the same thing over and over.
It’s not just young children that confuse learning and doing new things. For instance, how many software developers go through the trouble of deliberate practice by performing katas or attending dojos?
It may seem silly to repeat exercises that you’ve already done many times, but it’s not. It’s the only way to become a black belt in your field. And remember that mastery is one of the three intrinsic motivators (the others being autonomy and purpose).
Practicing means slowing down and moving focus from outcome to process.It’s best to use simple exercises that you can complete in a limited amount of time, so you can do the same exercise multiple times.
I’ve found that I virtually always learn something new when I practice. That’s not because I’ve forgotten how to solve the problem since last time, but because I’ve learned new things since then and thus see the world through new eyes.
For example, since Java 8 came out I’ve been trying to use the new streamclasses to help move to a more functional style of programming. This has changed the way I look at old problems, like FizzBuzz.
Let’s see this in action. Of course, I start by adding a test:
+ package remonsinnema.blog.fizzbuzz; + + import static org.junit.Assert.assertEquals; + + import org.junit.Test; + + + public class WhenFizzingAndBuzzing { + + private final FizzBuzz fizzbuzz = new FizzBuzz(); + + @Test + public void shouldReplaceWithFizzAndBuzz() { + assertEquals(“1”, “1”, fizzbuzz.get(1)); + } + + }
This test uses the When…Should form of unit testing that helps focus on behavior rather than implementation details. I let Eclipse generate the code required to make this compile:
+ package remonsinnema.blog.fizzbuzz; + + + public class FizzBuzz { + + public String get(int i) { + return null; + } + + }
The simplest code that makes the test pass is to fake it:
package remonsinnema.blog.fizzbuzz; public class FizzBuzz { public String get(int i) { – return null; + return “1”; } }
Now that the test passes, it’s time for refactoring. I remove duplication from the test:
public class WhenFizzingAndBuzzing { @Test public void shouldReplaceWithFizzAndBuzz() { – assertEquals(“1”, “1”, fizzbuzz.get(1)); + assertFizzBuzz(“1”, 1); + } + + private void assertFizzBuzz(String expected, int n) { + assertEquals(Integer.toString(n), expected, fizzbuzz.get(n)); } }
Next I add a test to force the real implementation:
public class WhenFizzingAndBuzzing { @Test public void shouldReplaceWithFizzAndBuzz() { assertFizzBuzz(“1”, 1); + assertFizzBuzz(“2”, 2); } private void assertFizzBuzz(String expected, int n) { package remonsinnema.blog.fizzbuzz; public class FizzBuzz { – public String get(int i) { – return “1”; + public String get(int n) { + return Integer.toString(n); } }
OK, now let’s get real with a test for Fizz:
public class WhenFizzingAndBuzzing { public void shouldReplaceWithFizzAndBuzz() { assertFizzBuzz(“1”, 1); assertFizzBuzz(“2”, 2); + assertFizzBuzz(“Fizz”, 3); } private void assertFizzBuzz(String expected, int n) { package remonsinnema.blog.fizzbuzz; public class FizzBuzz { public String get(int n) { + if (n == 3) { + return “Fizz”; + } return Integer.toString(n); }
Similar for Buzz:
public class WhenFizzingAndBuzzing { assertFizzBuzz(“Fizz”, 3); + assertFizzBuzz(“4”, 4); + assertFizzBuzz(“Buzz”, 5); } private void assertFizzBuzz(String expected, int n) { public class FizzBuzz { if (n == 3) { return “Fizz”; } + if (n == 5) { + return “Buzz”; + } return Integer.toString(n); }
Here I just copied and pasted the if
statement to get it working quickly. We shouldn’t stop there, of course, but get rid of the dirty stuff. In this case, that’s duplication.
First, let’s update the code to make the duplication more apparent:
package remonsinnema.blog.fizzbuzz; public class FizzBuzz { public String get(int n) { – if (n == 3) { – return “Fizz”; + MultipleReplacer replacer = new MultipleReplacer(3, “Fizz”); + if (n == replacer.getValue()) { + return replacer.getText(); } – if (n == 5) { – return “Buzz”; + replacer = new MultipleReplacer(5, “Buzz”); + if (n == replacer.getValue()) { + return replacer.getText(); } return Integer.toString(n); }
+ package remonsinnema.blog.fizzbuzz; + + + public class MultipleReplacer { + + private final int value; + private final String text; + + public MultipleReplacer(int value, String text) { + this.value = value; + this.text = text; + } + + public int getValue() { + return value; + } + + public String getText() { + return text; + } + + }
I just created a new value object to hold the two values that I had to change after the copy/paste.
Now that the duplication is clearer, it’s easy to remove:
package remonsinnema.blog.fizzbuzz; + import java.util.Arrays; + import java.util.Collection; + public class FizzBuzz { + private final Collection replacers = Arrays.asList( + new MultipleReplacer(3, “Fizz”), new MultipleReplacer(5, “Buzz”)); + public String get(int n) { – MultipleReplacer replacer = new MultipleReplacer(3, “Fizz”); – if (n == replacer.getValue()) { – return replacer.getText(); – } – replacer = new MultipleReplacer(5, “Buzz”); – if (n == replacer.getValue()) { – return replacer.getText(); + for (MultipleReplacer replacer : replacers) { + if (n == replacer.getValue()) { + return replacer.getText(); + } } return Integer.toString(n); }
I’m not done cleaning up, however. The current code suffers from feature envy, which I resolve by moving behavior into the value object:
package remonsinnema.blog.fizzbuzz; import java.util.Arrays; import java.util.Collection; + import java.util.Optional; public class FizzBuzz { public String get(int n) { for (MultipleReplacer replacer : replacers) { – if (n == replacer.getValue()) { – return replacer.getText(); + Optional result = replacer.textFor(n); + if (result.isPresent()) { + return result.get(); } } return Integer.toString(n);
package remonsinnema.blog.fizzbuzz; + import java.util.Optional; + public class MultipleReplacer { this.text = text; } – public int getValue() { – return value; – } – – public String getText() { – return text; + public Optional<String> textFor(int n) { + if (n == value) { + return Optional.of(text); + } + return Optional.empty(); } }
Now that I’m done refactoring, I can continue with multiples:
public class WhenFizzingAndBuzzing { assertFizzBuzz(“Fizz”, 3); assertFizzBuzz(“4”, 4); assertFizzBuzz(“Buzz”, 5); + assertFizzBuzz(“Fizz”, 6); } private void assertFizzBuzz(String expected, int n) { public class MultipleReplacer { } public Optional<String> textFor(int n) { – if (n == value) { + if (n % value == 0) { return Optional.of(text); } return Optional.empty();
The final test is for simultaneous “Fizz” and “Buzz”:
public class WhenFizzingAndBuzzing { assertFizzBuzz(“4”, 4); assertFizzBuzz(“Buzz”, 5); assertFizzBuzz(“Fizz”, 6); + assertFizzBuzz(“7”, 7); + assertFizzBuzz(“8”, 8); + assertFizzBuzz(“Fizz”, 9); + assertFizzBuzz(“Buzz”, 10); + assertFizzBuzz(“11”, 11); + assertFizzBuzz(“Fizz”, 12); + assertFizzBuzz(“13”, 13); + assertFizzBuzz(“14”, 14); + assertFizzBuzz(“FizzBuzz”, 15); } private void assertFizzBuzz(String expected, int n) {
public class FizzBuzz { public class FizzBuzz { new MultipleReplacer(3, “Fizz”), new MultipleReplacer(5, “Buzz”)); public String get(int n) { + StringBuilder result = new StringBuilder(); for (MultipleReplacer replacer : replacers) { – Optional<String> result = replacer.textFor(n); – if (result.isPresent()) { – return result.get(); + Optional<String> replacement = replacer.textFor(n); + if (replacement.isPresent()) { + result.append(replacement.get()); } } + if (result.length() > 0) { + return result.toString(); + } return Integer.toString(n); } }
This code is rather complex, but this is where streams come to the rescue:
public class FizzBuzz { new MultipleReplacer(3, “Fizz”), new MultipleReplacer(5, “Buzz”)); public String get(int n) { – StringBuilder result = new StringBuilder(); – for (MultipleReplacer replacer : replacers) { – Optional<String> replacement = replacer.textFor(n); – if (replacement.isPresent()) { – result.append(replacement.get()); – } – } – if (result.length() > 0) { – return result.toString(); – } – return Integer.toString(n); + return replacers.stream() + .map(replacer -> replacer.textFor(n)) + .filter(Optional::isPresent) + .map(optional -> optional.get()) + .reduce((a, b) -> a + b) + .orElse(Integer.toString(n)); } }
Note how the for
and if
statements disappear. Rather than spelling out howsomething needs to be done, we say what we want to achieve.
We can apply the same trick to get rid of the remainingif
statement in our ode base:
public class MultipleReplacer { } public Optional<String> textFor(int n) { – if (n % value == 0) { – return Optional.of(text); – } – return Optional.empty(); + return Optional.of(text) + .filter(ignored -> n % value == 0); } }
The code is on GitHub.
Reference: | FizzBuzz Kata With Java Streams from our JCG partner Remon Sinnema at the Secure Software Development blog. |
Hi sir,
very good article sir.Great work sir keep doing sir