Loop Syntax and Design in Java

Loop Syntax and Design

Welcome back! In this tutorial, we’ll dive into a new control flow technique known as the loop.

Table of Contents

Recursion

Up until now, we’ve been able to play around with variables, classes, methods, and even some branching. After we introduced branching, we decided to tackle a little introduction to a verification tool called unit testing. At this point, we should be pretty familiar with the basics of logic and conditions. But, what do we do if we want to run some code that repeats itself?

Oddly enough, we don’t have to introduce any new syntax to be able to loop a code snippet. For instance, what happens when we run the following method?

public static void printForever(String printMe) {
    System.out.println(printMe);
    printForever(printMe);
}

Well, let’s step through it.

Stepping Through an Example

Let’s say we call Foo.printForever("Hello, World!"). For reference, Foo is a generic term for the class that this method might appear in.

First, we’ll notice the input string gets passed into our print statement. In the console we should see the string "Hello, World!". Then, the method drops down to a funny looking line:

printForever(printMe);

From this line, it appears that we call the method from inside itself. As it turns out, this is completely legal and results in what is known as a recursive call.

Unfortunately, this results in our string being printed infinitely because the method calls itself forever. Luckily, we’ll eventually end up watching the program crash with a stack overflow exception.

Recalling the Stack

If we think back to our methods tutorial, we’ll remember that method calls make their way onto the memory stack. As we nest methods, the call stack grows larger. Typically, we reach some finite limit in method calls before backtracking along the call stack.

However, in our example above, we never hit that limit. Instead, we continue to add method calls until we run out of memory. Don’t worry! This is pretty easy to fix. We just have to add some sort of base case which defines that last method call in the stack.

Let’s try using an integer to specify the number of prints we want to make.

public static void recursivePrint(String printMe, int numOfPrints) {
    // Base case
    if (numOfPrints <= 0) {
        System.out.println("Finished printing!");
    } else {
        System.out.println(printMe); 
        printForever(printMe, numOfPrints - 1);
    }
}

In this implementation, we supply a new parameter which we use to specify how many times we want our string to be printed. Inside the method, we add a special case for anyone who decides to print zero or fewer copies of their string.

The real magic happens in our else case. To trick the recursion into hitting a base case, we always supply the next recursive call with one fewer number of prints. That way, the numOfPrints parameter determines the number of calls overall.

Don’t worry if that seems confusing! Recursion is not something we will use at this point, but it is definitely a nice concept to have in our back pocket. In fact, it’ll be used a lot more when we touch on data structures like trees and graphs. For now, let’s dive into something more intuitive!

Iterative Looping

Recursion is one way of looping a piece of code, but it is often quicker and more intuitive to use an iterative approach.

With recursion, we don’t actually attack our initial problem until we reach the last recursive call. The result of that computation filters back through until we ultimately solve the initial problem we had.

With loops, we run a computation repeatedly until we reach our result. As a result, loops are typically more intuitive since they mirror the way we normally think. That’s why languages like Java include a looping syntax. In fact, java includes syntax for at least 4 different loops, but we will only cover two of them: for and while.

The While Loop

The while loop has the following syntax:

while (condition) {
    // loop body
}

As long as the condition remains true, the loop body will run continuously. This loop structure is pretty bare bones and open to modification. However, the major benefit to this loop structure is the clarity of the condition for the sake of readability.

If we wanted to implement our recursion example using this syntax, we might do the following:

public static void whilePrint(String printMe, int numOfPrints) {
    int count = 0;
    while (count < numOfPrints) {
        System.out.println(printMe);
        count++;
    }
}

Just like before, we provide a function with two inputs: a String and an int. However, this time we create a counter to track how many times we have looped. The loop condition then relies on count to grow until it hits the number of prints that the user requests.

At this point, we should note that count starts at zero. That may seem unusual if you don’t have a programming background. Zero is not strictly mandatory, but it is usually the value used when counting in a loop. We will see why a bit further in the tutorial so get used to seeing it.

Also, we should note that we increment count at the bottom of the loop body using the ++ operator. That unary operator adds one to count which is much cleaner than count = count + 1.

The For Loop

In addition to the while loop, Java gives us syntax for the for loop:

for (initialization; condition; increment) {
    // loop body
}

At first, this syntax can seem complex and overwhelming. What goes in initialization? Why is there an increment statement? As a result, a lot of beginners fallback on the while loop. However, the for loop introduces a little syntactic sugar that can make our loops a bit cleaner.

Let’s try implementing our print method one last time with this syntax.

public static void forPrint(String printMe, int numOfPrints) {
    for (int count = 0; count < numOfPrints; count++) {
        System.out.println(printMe);
    }
}

In essence, we save two lines, but the overall structure is easier to read. It’s very clear that the loop runs from zero to numOfPrints while printing the input string each time.

That said, for loops can get a little more ugly if we have compound conditions. In those cases, we should probably opt for the while loop structure. Regardless, we now have 3 different looping mechanisms under our belt.

Loop Indices

Loops aren’t just for running a chunk of code on repeat. They can also be used to iterate over a collection of data. Now, we haven’t touched on any sort of data structures yet, but we are familiar with strings.

But, what is a string? Well, it’s a collection of characters. In other words, we can actually use a loop to iterate over those characters to do something useful. For instance, we could try printing each character individually:

public static void printChars(String characters) {
    for (int i = 0; i < characters.length(); i++) {
        System.out.println(characters.charAt(i));
    }
}

Just like our previous function, we used a for loop. Again, our initial count variable starts at zero. That is because strings are indexed at zero. In other words, the first character in every string is at location zero. When we call characters.charAt(i) on the first iteration, we should get the first character in our input string.

Another critical part of the loop is the condition. Notice that the condition runs the index up until one before the length of the string. That is because the last character in the string actually has an index of length() - 1.

If we tried to access the character one beyond length() - 1, we would get an index out of bounds exception. That might seem annoying at first, but it’s really an important security feature. Languages like C and C++ don’t have this kind of protection which means we can actually poke around in memory if we’re not careful.

Refactoring

While we’re here, it seems like a nice opportunity to bring up this notion of refactoring.

Above, we saw three methods which all implemented the same functionality. This just goes to show that even on a simple example there are several ways to implement a solution.

When writing out solutions, we should always strive for correctness first. We need to make sure our solution provides the intended behavior—JUnit testing is a nice start.

Next, we typically go through a phase of refactoring which means we try to find ways to clean up and optimize our code. However, we don’t always like to change method names and parameter signatures. Those types of changes can cause external code to fail compilation.

Instead, we usually just change what happens inside a method. That is our opportunity to address performance and reliability concerns. For instance, we might change our implementation from recursion to loops just for the sake of readability. In other cases, we might try to find ways to trade off speed for memory in a method.

Regardless, this should serve as a reminder that code is a living substance. It has to be reviewed and modified as needed to solidify its role in a system.

Up Next

Now that we’ve covered looping, we’ll be able to get some bigger projects going. In fact, we’ll probably finish up the Java basics series with just two more tutorials.

Up next, we’ll finally tackle readability which includes cool topics like JavaDoc. In addition, we’ll dig into a few controversial topics regarding programming style.

Then, we’ll finish off the series with a overall review of the material in this series. In that tutorial, we’ll try to tackle class structure a bit deeper. We’ll want to get comfortable with access modifiers as well as getters and setters. When we’re done, we should be able to create a couple of classes and use their objects to do some fun things!

In the future, we’ll start to tackle some deeper object-oriented concepts like hierarchies, data structures, and software patterns. Get pumped!

As always, if you enjoyed this tutorial, please share it with your friends. If you have any questions or comments, feel free to leave them below or contact me directly. And if you want to keep up to date with the latest articles, don’t forget to subscribe to The Renegade Coder. Until next time!

Series Navigation← JUnit Testing in JavaReadability and Style in Java →
Advertisements

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.