Everyday Refactorings in IntelliJ IDEA

Helen Scott

We often change the code we work with for many reasons such as fixing bugs, improving readability, and adding new features among others. This blog covers every day refactorings.

This blog post covers the same material as the video. This provides an easy way for people to skim the content quickly if they prefer reading to watching, and to give the reader/watcher code samples and links to additional information.

The code used in this blog is available here.

Renaming Elements

Renaming something manually by changing its name directly in a class file is not safe. For example, if we rename our Bug class to ABug in the editor, we might miss updating all the other code that uses it. Since IntelliJ IDEA 2020.2, you will see a "related problems" error message in the editor.

Rename the Bug class to ABug

The in-place rename refactoring is highlighted in the gutter. By clicking the R icon in the gutter, we can preview the renaming of all usages of Bug to ABug.

Rename class with preview

IntelliJ IDEA also finds variables with similar names and lets us select which ones we want to update.

Alternatively, when we change a class name in the editor we can use the shortcut ⌥⏎, or Alt+Enter, to repeat these steps.

You can also use ⇧F6 on macOS, or Shift+F6 on Windows/Linux, to invoke the renaming refactoring and IntelliJ IDEA will suggest alternative names for your classes, methods and variables. IntelliJ IDEA completes the refactor safely if you select a new name.

public class Bug {
    private long id;
    private String description;

    public Bug(long id, String description) {
        this.id = id;
        this.description = description;
    }

    public long getId() {
        return id;
    }

    public String getDescription() {
        return description;
    }
}

For example, if we rename a field description to desc, IntelliJ IDEA detects if this field is used in getter and setter methods and asks if we want to update them. It will also update its usage in method parameter names.

Refactor variable name

Let’s work with an interface called View which defines a method getItems. We can use ⌘B on macOS, or Ctrl+B on Windows/Linux to navigate to its usages. This interface is implemented by the class ViewImpl.

In the interface called View, use ⇧F6 on macOS, or Shift+F6 on Windows/Linux, to rename the method getItems to getItemsList. When we press Enter, its implementation is updated in the ViewImpl class.

Refactor variable name

You can also access the Project Window and change the names of your classes, packages, files, or directories by using ⇧F6 on macOS, or Shift+F6 on Windows/Linux on the file name.

Changing the Signature

Let’s see how we can safely change the signature of a class or method. Our method binaryStrings in BugReport accepts one String argument:

public boolean binaryStrings(String b) {
    Stack<Character> s = new Stack<>();
    for (char c : b.toCharArray())
        if (s.empty() || !s.peek().equals(c))
            s.push(c);
        else
            s.pop();
    return s.empty();
}

We can add a parameter to the method binaryStrings, for example, a counter of type int. Again, this is an in-place refactoring, and will be highlighted by an R icon in the gutter area. We can click on the icon in the gutter and update the method signature, adding a default value to the parameter so that any callers of the method use the default value.

Refactor variable name

We can also use ⌥⏎ on macOS, or Alt+Enter on Windows/Linux, to update the method signature and add the default parameter value.

We can also change a method signature by invoking the Change Signature dialog, by using ⌘F6 on macOS, or Ctrl+F6 on Windows/Linux. Let’s add a boolean parameter state with default value "true". We can also change the order of the parameters using this dialog. It also has an option to choose to delegate via overload. When we refactor it, the method is overloaded.

Refactor variable name

public class MyMap {
    int count = 0;

    public int getCount() {
        return count;
    }

    public void setCount(int Count) {
        this.count = count;
    }

    public static void main(String[] args) {
        MyMap myMap = new MyMap();
        System.out.println(myMap.getCount());
    }
}

We can use this MyMap class to see how changing the class signature affects it. We can use ⌘F6 on macOS, or Ctrl+F6 on Windows/Linux to modify the class signature. Let’s add two generic parameters to it, one called K with the default value Integer, and one called V with the default value String. When we refactor the class, the usage of MyMap changes.

Refactor variable name

Extracting a Method

Extract method refactoring can make your methods shorter and more readable.

public boolean binaryStrings(String b) {
    Stack<Character> s = new Stack<>();
    for (char c : b.toCharArray())
        if (s.empty() || !s.peek().equals(c))
            s.push(c);
        else
            s.pop();
    return s.empty();
}

Let’s select code in our binaryStrings method which can be grouped. We can extract this code to another method by using the shortcut ⌥⌘M on macOS, or Ctrl+Alt+M on Windows/Linux.

The local variable or method parameters that are used by this block will be passed as parameters to it. Type in the method name, for example, manipulateStack, and the code block that we extracted is replaced with the call to the method.

Refactor variable name

You can also extract a method by selecting code, and using ⌥⏎ on macOS, or Alt+Enter on Windows/Linux, and selecting Extract Method.

Inlining a Method

Inline method refactoring is the reverse of Extract Method refactoring. We can inline the method manipulateStack that we just created by using the shortcut ⌥⌘N on macOS, or Ctrl+Alt+N on Windows/Linux. We can either inline the method contents and delete the method, or keep the method. For this example, we deleted the method.

Refactor variable name

But why would you want to inline a method? We can use it, for example, to remove unnecessary redirection, or we might also want to inline badly refactored methods and extract them again.

Introducing a Constant

It is always advisable to use constants over literal values in a codebase so let’s look at how we can refactor our code to introduce a constant.

public static boolean beautifulBinaryStrings(String s) {
    while (!s.equals(s = s.replaceAll("AA|BB", ""))) {
        System.out.println(s);
    }
    return s.isEmpty();
}

Our method beautifulBinaryStrings uses a String literal value. We can extract this value to a constant by using the shortcut ⌥⌘C on macOS, or Ctrl+Alt+C on Windows/Linux.

IntelliJ IDEA suggests the constant name, based on the literal value. We will choose the first recommendation. You could also choose to move this constant to another class if required.

Refactor variable name

Introducing a Field

You can introduce or extract a field and initialize it with this refactoring.

The method getItemsList in our ViewImpl class uses constants 0 and 4 to get a subset of a list.

public class ViewImpl implements View {
   @Override
   public List<Integer> getItemsList() {
       return List.of(1, 2, 3, 4, 5).subList(0,4);
   }
}

Instead of defining these as constants, you can use ⌥⌘F on macOS, or Ctrl+Alt+F on Windows/Linux to introduce fields to store these values so that they can have different values across instances.

Refactor variable name

Extracting a Parameter

With Extract parameter refactoring, you can select a constant, or expression, in your method and define it to be passed to the method as an argument. For this example we have also introduced a second field to the getItemsList method called toIndex.

public class ViewImpl implements View {
   private final int fromIndex;
   private final int toIndex;

   public ViewImpl() {
       fromIndex = 0;
       toIndex = 4;
   }

   public List<Integer> getItemsList() {
       return List.of(1, 2, 3, 4, 5).subList(fromIndex, toIndex);
   }
}

In the getItemsList method, you can pass the expression List.of(1,2,3,4,5) as an argument to this method. The type of the method parameter is the same as the type of selected expression.

Refactor variable name

You can also select the Delegate via method overloading check box to preserve the original method and allow the introduction of a second method. This means that either method can be used depending upon the caller.

Refactor variable name

Extract parameter can help you to make your methods, or method calls, more readable by moving expressions into the most logical place. Using understandable parameter names also helps readability.

Introducing a Variable

We can also extract a variable if required. Sometimes we can make code more readable by moving expressions into a suitably named variable.

public ContextActionsWithAltEnter(double cityPopulation) {
    if (cityPopulation > 0x1.2016eb678a2p43 && cityPopulation < 987677.8) {
        if (cityPopulation % 5 == 0) {
            this.cityPopulation /= 2;
        }
    }
    this.cityPopulation = cityPopulation;
}

The if statement in our ContextActionsWithAltEnter constructor looks a bit complicated. We can select the expression and use ⌥⌘V on macOS, or Ctrl+Alt+V on Windows/Linux. Make sure to give it a helpful name such as lowerLimit. We can also extract the second condition to a variable and name it upperLimit. You can choose to define the extracted variables as final if required, and as a var if using Java 10 or higher.

Refactor variable name

This expression looks much more readable now.

Safely Deleting

If you no longer want to use a file or symbol in your project, you should delete it safely.

Choose the file or class you want to delete and use ⌘⌦, or Alt+Delete on Windows/Linux. IntelliJ IDEA will ensure it is safe to delete the resource before deleting it.

Safe Delete

Summary

IntelliJ IDEA has powerful refactoring capabilities, which you can use every day to improve your code, safely.

However, refactoring is not a magic bullet. Please create a safety net of tests to ensure that your refactorings are not changing the behavior of your code.

See also:

Subscribe

Subscribe for updates