Tips for Writing Code in IntelliJ IDEA

Posted on by Trisha Gee

Many of our screencasts and videos highlight particular features of the IDE. Others are tutorials on specific technologies. We decided to create a video which puts all of this together and shows how using a range of IntelliJ IDEA features helps developers to write code.

IntelliJ IDEA is designed to help us stay in the flow so we don’t get distracted by the mechanics of what we’re doing. This video aims to show what that looks like.

This blog post is adapted from the transcript of the video.

This screencast shows how the features and shortcuts of IntelliJ IDEA help us to stay in the flow while we’re writing code.

The first and most important thing to remember is that we’re going to avoid using the mouse as much as possible. So, let’s say we use ⌘N on MacOs, or Alt+Insert on Windows or Linux, to bring up the “new” menu, we could use the mouse to select something from this menu. Or we could use the arrow keys. Or, like any window in IntelliJ IDEA, we can start typing and IntelliJ will search for our feature.

Before we dive into the details of writing code, let’s side-track briefly to look at how to configure the IDE to maximise the space for code.

By default IntelliJ IDEA has a lot of navigation bars and tool buttons visible. We can use Find Action, ⌘⇧A or Ctrl+Shift+A, to turn off the Editor Breadcrumbs. We can use Find Action again, or Search Everywhere (Shift Shift), to turn off the editor tabs, the navigation bar, and the tool window buttons. Finally, Hide All Windows will maximise the editor so we can completely focus on the code.

Now that’s done let’s get back to the code.

IntelliJ IDEA will automatically generate common code for you. One way to do this is by using Live Templates. We can type a short sequence, like psvm, and have IntelliJ IDEA generate things like the public static void main method. We can even create our own live templates for code snippets we commonly use.

Let’s assume we’ve created a class that looks like this:

public class Main {
    public static void main(String[] args) {
        System.out.println("Hi");
    }
}

If we put the cursor inside the main method, we can run it by using ⌃⇧R, or Ctrl+Shift+F10 on Windows. Because this is a Gradle project, IntelliJ IDEA uses Gradle to run the code and we can see the output in the run window. See the video Working with Gradle in IntelliJ IDEA for more information.

Let’s look at how IntelliJ IDEA helps us to write more real-world code. When we’re starting a piece of work, sketching out an idea or prototyping, sometimes we work from the outside in – we write code the way we want to use it, regardless of whether the class or method already exists. IntelliJ IDEA assumes we want to work this way, and shows us clearly which bits of code need our attention. Using F2 takes us to the next error, and pressing Alt+Enter gives us suggestions for how to fix it. Let’s assume we’ve changed our main method so it looks like this:

public static void main(String[] args) {
    new Order(1);
}

If the Order class doesn’t already exist, when we press Alt+Enter on Order the IDE shows Intention Actions suggesting to create it. If we choose to do so, IntelliJ IDEA creates the class and a constructor that matches the one we were trying to use. We can rename the constructor parameter to something more meaningful.

public class Order {
    public Order(int id) {
    }
}

We can use F2 again to find out what IntelliJ IDEA suggests for this ID parameter, and we can use Alt+Enter to get the IDE to generate the field and assign it to the value of this constructor parameter.

public class Order {
    private int id;

    public Order(int id) {
        this.id = id;
    }
}

We can use Generate Code inside the class (⌘N or Alt+Insert,) to create standard methods. We can generate a Getter for the ID. If the IDE puts the generated method in the wrong place, we can use ⌘⇧↓ or Ctrl+Shift+Down to move this method beneath the constructor. We can also generate toString, hashCode and equals methods for the class. When writing Java code in IntelliJ IDEA, there’s no need to manually type out standard methods, and using code generation also ensures a standard approach to these methods in all our classes. Using these features we can have a simple Order class completed:
public class Order {
    private int id;

    public Order(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Order order = (Order) o;
        return id == order.id;
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }

    @Override
    public String toString() {
        return "Order{" +
                "id=" + id +
                '}';
    }
}

We can also generate test classes for our code. IntelliJ IDEA supports a variety of testing frameworks, let’s use JUnit 5 for this case. We can generate the test methods using ⌃⏎ or Alt+Insert. If we want to, we can change the shape of these generated methods in the File and Code Templates settings.

IntelliJ IDEA is designed to keep us in the flow of coding, so there are ways to keep moving forward when writing code. For example, if we declare a new variable value, we don’t have to move the cursor to the start of the line to declare the type, we can use extract variable to create it. Or alternatively we can use postfix completion to keep typing forwards and not have to move the cursor to another place.

IntelliJ IDEA’s code suggestions support camel case, so we don’t have to type the whole of the method, or even the whole of the first part of a method name, to get relevant code suggestions. We can use IntelliJ IDEA’s code completion. Using code generation and completion we can quickly create a simple test class:

class OrderTest {
    @Test
    void shouldBeAbleToCreateANewOrder() {
        Order order = new Order(23);
        assertNotNull(order);
    }
}

A key part of writing code is committing frequently. Now we have simple functionality and basic test that works, we can already commit our changes. We use ⌘K, or Ctrl+K on Windows and Linux, to commit our changes. The video shows the new commit window from IntelliJ IDEA 2020.1, in older versions we’ll see a commit dialog.

We’ll probably need to make some changes to our working code that break compilation. There’s no need to manually fix problems with IntelliJ IDEA – press Alt+Enter on any error and the IDE suggests things that might make the code compile. If we changed the test code a little:

void shouldBeAbleToCreateANewOrder() {
    Order order = new Order(23, "description");
    assertNotNull(order);
}

We are shown an error that this is the wrong number of arguments for the constructor.  Alt+Enter suggests we can create a new constructor with this shape, or update the current constructor to take a new parameter. Let’s choose to update the existing constructor. Once IntelliJ IDEA has made the change, the code is compiling again. There’s no need to look for a list of problems to fix, the IDE has applied the defaults so it all works. We can go to the declaration of this constructor from the test using ⌘⌥B or Ctrl+Alt+B. We’ll see it has a new description parameter.

public class Order {
    private int id;

    public Order(int id, String description) {
        this.id = id;
    }
// rest of the class...
}

We can use back and forth navigation to go to back to the test. We’ll extract our description value into a variable so we can check it in the test. We can use smart code completion to get IntelliJ IDEA to show the methods on Order that match the type we need. We actually don’t have one that returns a description yet, so let’s call it as if it existed.

void shouldBeAbleToCreateANewOrder() {
    String expectedDescription = "description";
    Order order = new Order(23, expectedDescription);
    assertNotNull(order);
    assertEquals(expectedDescription, order.getDescription());
}

Using Alt+Enter we can get IntelliJ IDEA to create the getDescription method with the signature that we expect. If we choose “create read-only property”, IntelliJ IDEA will create a getter method and a field, but no setter.

public class Order {
    private int id;
    private String description;

    public Order(int id, String description) {
        this.id = id;
    }

    public String getDescription() {
        return description;
    }
// rest of the class...
}

We can use Alt+Enter on the constructor description parameter and get IntelliJ IDEA to assign the description parameter to the description field. If we re-run the test with ⌃⌘R or Alt+Shift+R it should pass.

Let’s move on to some other tips for writing code. Maybe we need to calculate the price of this order by iterating over a list of the LineItems in this order. To start sketching out this method, we might start with a local variable ArrayList of LineItems.

    public double price() {
        ArrayList<LineItem> lineItems = new ArrayList<LineItem>;
    }

(note: the code above doesn’t compile)

We’ll probably want a price variable to store the calculation result, which we’ll return at the end of the method. Again, we can use smart completion here to suggest the best way to complete this statement given the return type.

    public double price() {
        ArrayList<LineItem> lineItems = new ArrayList<LineItem>();
        double price = 0;

        return price;
    }

IntelliJ IDEA has a number of Live Templates that help us to iterate over something. In the case of an ArrayList like this, we probably want to use iter, which gives us the most readable way to iterate over a Collection like this.
    public double price() {
        ArrayList<LineItem> lineItems = new ArrayList<LineItem>();
        double price = 0;
        for (LineItem lineItem : lineItems) {
            
        }
        return price;
    }

We can use code completion and code generation to get the code into the shape we want:
    public double price() {
        ArrayList<LineItem> lineItems = new ArrayList<LineItem>();
        double price = 0;
        for (LineItem lineItem : lineItems) {
            price += lineItem.getPrice();
        }
        return price;
    }

Now we have the code more or less looking the way we want, we can use IntelliJ IDEA’s inspections to guide us to the next steps. We should have one warning which says we’re using a collection, but we never actually put anything in it. We could use the Introduce Field refactoring to turn the lineItems from a local variable into a field of this order class. We can choose where to initialise the field, let’s say we want to do it in the constructor.

public class Order {
    private int id;
    private String description;
    private final ArrayList<LineItem> lineItems;

    public Order(int id, String description) {
        this.id = id;
        this.description = description;
        lineItems = new ArrayList<>();
    }

What does this give us? Well, now we have to think about out how line items are added to this list, probably via an “add” method or similar. But we can put aside that problem for now.

Not all of IntelliJ IDEA’s suggestions are visible as warnings. Intentions and inspections which are not set to warn, will be accessible when we press Alt+Enter. It’s always a good idea to press Alt+Enter on any code that we think could be written a different way to see what IntelliJ IDEA suggests for us. If we do it on our for-loop for calculating the price, IntelliJ IDEA suggests we could use a sum() stream operation. IntelliJ IDEA will automatically refactor this code so that it works exactly as before but uses the new syntax (note: you need to be using Java 8 or above for this to work).

    public double price() {
        double price = lineItems.stream().mapToDouble(LineItem::getPrice).sum();
        return price;
    }

One final thing we can do to simplify this method is to inline the variable, since it no longer make sense in a one-line method.
    public double price() {
        return lineItems.stream().mapToDouble(LineItem::getPrice).sum();
    }

This code uses the Streams API from Java 8. I like my streams calls to be on one line per operation to help me to read it. We can use reformat code, ⌘⌥L or Ctrl+Alt+L, to see if IntelliJ IDEA will apply the formatting I want. If it says the code is already formatted according to the current settings, I can highlight the code I want to reformat and press Alt+Enter, and select “Adjust code style settings” to see just the settings that apply to this code block. In Wrapping and Braces, we can change the settings for Chained method calls to “Wrap always”. When we make a change, it’s previewed in the editor, so we can decide whether or not that’s the formatting we want.
    public double price() {
        return lineItems.stream()
                .mapToDouble(LineItem::getPrice)
                .sum();
    }

We can put the calls back on the first line by using Join Lines (Shift+Ctrl+J) to bring the line below onto this line. This is particularly useful when working with Strings which span multiple lines.

Finally, once we’ve made all the changes we want, we could use Run Anything, which is Ctrl Ctrl, to run our test and make sure we haven’t broken anything. With everything working, we should commit all our changes. IntelliJ IDEA makes it easy to commit small sets of changes that we have high confidence will work.

That’s a summary of a very small subset of functionality in IntelliJ IDEA that makes it easy for us to focus on writing code that does what we need. For more information see:

Subscribe

Subscribe for updates