A blog about programming topics in general, focusing on the Java programming language.

Month: March 2023

For loops vs Streams in Java

Introduction

For loops vs Streams in Java. Probably one of the most asked questions since Java 8 introduced the Streams API.

Today’s blog post will discuss the pros and cons and when to use them.

Performance comparison

Check out this Twitter’s thread comparing for loops with Streams as a reference.

As we can see there, Streams tend to perform quite slower than for loops. This is especially true when the operations carried out by either the Stream or the for loop are not that many.

If the amount of data is small, for loops will usually perform better than Streams.

It’s also worth noticing that the Streams API allows us to create parallel Streams without having to worry about the implementation details. Quick disclaimer: Make sure you need parallel Streams before actually using them and run some benchmarks.

Readability comparison

This is probably the most subjective topic. There are many old-school developers that will stick with for loops forever. On the other hand, there are also many others (including myself) that started working with Streams and were charmed by the readability they provide.

For loops

Let’s use an example, we want to iterate over an ArrayList of Strings that will have a maximum of 5 elements and we want to know the total length of all Strings combined. With for loops we could do something like:

private static int getTotalLengthOfElements(List<String> input) {
    int totalLength = 0;
    for (int i = 0; i < input.size(); i++) {
        totalLength += input.get(i).length();
    }
    return totalLength;
}

We can then make use of this method

public static void main(String[] args){
    List<String> input = List.of("What", "a", "bunch", "of", "Strings");
    int totalLengthOfStrings = getTotalLengthOfElements(input);
    System.out.println(totalLengthOfStrings);
}

That will print 19.

For loop result

Streams

The previous example could also be written with Streams as:

private static int getTotalLengthOfElements(List<String> input) {
    return input.stream()
            .mapToInt(String::length)
            .sum();
}

This will, once again output 19:

Streams result

Here we leverage the existence of the sum function provided by the IntStream interface.

In my opinion, even though this will probably perform slower than the for approach, this looks way better in terms of readability.

If the input data were to change to a higher amount of Strings, we could make use of the parallelStream just by calling the method:

private static int getTotalLengthOfElements(List<String> input) {
    return input.parallelStream()
            .mapToInt(String::length)
            .sum();
}

So we could say that this approach is also better in terms of adaptability.

Conclusion

We briefly discussed for loops vs Streams in Java.

I would say that if you’re working on a project with your team, you should decide with which approach you all feel more comfortable.

Most likely, performance won’t be an issue when using any of both so the most important thing to worry about is readability. And that is entirely up to you.

Quick reminder that if you’re a Java lover just like me, don’t miss the Java posts I’ll be uploading to this blog.

Parameterized tests in Java

Introduction

Parameterized tests are a JUnit feature that allows us to execute the same test multiple times with different input data by making use of the annotation @ParameterizedTest.

When to use Parameterized tests

Let’s say we want to test our brand-new class:

public class Calculator {

    public int divide(int num1, int num2) {
        return num1 / num2;
    }
}

(I know it’s pretty complex logic 😉). A typical scenario for this case is when we divide by zero, we will get an exception. Let’s test that with a simple test class:

class CalculatorTest {

    static Calculator calculator;

    @BeforeAll
    static void setup() {
        calculator = new Calculator();
    }

    @Test
    void it_should_throw_exception_when_divided_by_zero() {
        assertThrows(ArithmeticException.class, () -> calculator.divide(1, 0));
    }
}

So we execute the test and see what happens:

Test execution

Nice! the test has worked out as expected.

Now, let’s say we want to prove that this won’t happen for several inputs. We could start creating tests for each input, but if we want to test 6 different inputs, that would require 6 different tests.

For this kind of occasion, we can make use of the @ParameterizedTest annotation.

How to use Parameterized tests

The first thing we need to know in order to make use of Parameterized tests is to be able to pass our custom input data to the test we want to parameterize. There are multiple ways of doing this but let’s focus on the main ones.

So, the question is: How do we pass custom input data to parameterized tests?

@ValueSource

We can use the @ValueSource annotation for some simple input data

@ParameterizedTest
    @ValueSource(ints = {1, 2, -1, -2})
    void it_should_not_throw_exception_with_valid_values(int num) {
        assertDoesNotThrow(() -> calculator.divide(1, num));
    }

This creates 4 different tests, one for each value in the @ValueSource annotation.

Test execution with @ValueSource

@MethodSource

What if we want to pass multiple parameters, or use a more complex logic for the creation of the input data? We could use the @MethodSource annotation. This annotation will receive as a parameter, the name of the method that will return the input data for the test.

For instance, if we want to pass both numbers when dividing we could do something like this:

@ParameterizedTest
    @MethodSource("getValidValues")
    void it_should_not_throw_exception_with_valid_values(int num1, int num2) {
        assertDoesNotThrow(() -> calculator.divide(num1, num2));
    }

    private static Stream<Arguments> getValidValues() {
        return Stream.of(
                Arguments.of(1, 1),
                Arguments.of(10, 2),
                Arguments.of(-1, 1),
                Arguments.of(-1, -1),
                Arguments.of(0, 1)
        );
    }

We passed the String "getValidValues" to the @MethodSource that references to the new method that we have just created: private static Stream<Arguments> getValidValues()

This generates 5 different tests at the execution time, given that we are passing a Stream containing 5 different Arguments:

Test execution with @MethodSource

Some considerations:

  • Method must be static. Otherwise, we will receive the error: org.junit.platform.commons.PreconditionViolationException: Cannot invoke non-static method
  • When passing multiple arguments, we can use the class Arguments wrapped in Stream.
  • When passing a single argument, the method can just return a Stream of the required data type. For instance, if we want to pass Integers the method would just return Stream<Integer>.
private static Stream<Integer> getValidValues() {
        return Stream.of(1, 2, -1, -2);
}

Conclusion

We learned how to make use of the Parameterized tests feature in Java. It’s a really simple, yet so powerful feature that every Java developer should know about.

The provided example was not complex at all so let me know if it would be useful to write another post to get deeper into the topic with more realistic examples. For a 101 introduction, it should be enough though.

If you’re interested, check out more TeachingDev Java posts!

Get to know Git aliases

Introduction

To boost your productivity as a Software Developer, you can familiarize yourself with many topics.

I would say the most useful technical skills are those that shorten the time you use on trivial tasks. We all have to deal with daily repetitive tasks that constantly drain so much time from us.

This is why I would encourage you to do some things, such as master your IDE, use git aliases and all things you can think of that make you spend less time than needed.

What are Git aliases?

Let me just say it straight: Git aliases are a quicker way to write git commands.

That’s it! There’s not really much more to add.

Why should you use Git aliases?

If you could work less for the same amount of outcome you would do it, right?

If you answered yes, that’s the reason you needed to hear to start using Git aliases. Anyways, let me just enumerate some reasons to use them:

  • You will be more efficient.
  • You will learn a bit more about how is Git configured internally.
  • If you are like me, you will feel better by using them.
  • Bonus: You will definitely look cooler when you share the screen with your coworkers. 😉

How to create Git aliases

I hope I have convinced you to use Git aliases so far. I know you might not want to invest too much time in creating your own aliases and getting used to them. Well, I bring you good news! There are multiple ways to get started with Git aliases. The one I recommend the most is importing already existing Git aliases that someone thought of.

Here I will leave my favorite ones, so you can have a look at them and decide which one suits you best. The good news is, even if you don’t feel any of them is matching your vibe, you can edit them later.

https://github.com/GitAlias/gitalias

https://github.com/peterhurford/git-aliases.zsh

https://github.com/SixArm/gitconfig-settings

This is a list of the most useful commands I’m personally using since I use the GitAlias project (the first one listed).

git a = add
git aa = add --all

git c = commit
git ca = commit --amend

git co = checkout

git cp = cherry-pick
git cpa = cherry-pick --abort
git cpc = cherry-pick --continue

git m = merge
git ma = merge --abort
git mc = merge --continue

git pf = pull --ff-only
git pr = pull --rebase

git rb = rebase
git rba = rebase --abort
git rbc = rebase --continue

git rv = revert

git s = status

As you can see, once you get used to it you will start working way faster than you used to. This project has some common singularities for the commands such as adding at the end a for --abort, c for --continue

How to create your own aliases

If you want to create your custom aliases, you just have to run the following command

$ git config --global alias.co checkout

By doing so, we would’ve just created the alias co for checkout so the next time we run the command git co in reality, we would have written git checkout.

You can also edit your ~/.gitconfig file

[alias]
    st = status
    ca = commit --amend
    ma = merge --abort
    mc = merge --continue

Share Git aliases across all your devices

For some extra bonus points, I’ll leave you with this repository if you want to share your git aliases inside your organization.

Maybe it would be useful for some new joiners to your team, new to git aliases, to have already set up git aliases.

If that’s the case, just check this repository and follow the instructions. It’s really simple.

https://github.com/pipelineinc/alias4git

Conclusion

I will admit that I’m a geek about shortcuts. I love to learn all my IDE’s shortcuts and all that kind of stuff. This made me boost my productivity as I’m saving time in probably the most repeated actions on a daily basis.

Still, if you’re not yet convinced about using Git aliases, just give them a try. See how they affect (or not) your productivity.

If you liked this post, let me know if you would be interested in some other productivity booster ideas. Such as, mastering your IDE. Thanks for reading!