[Java] [Java] Various methods to repeatedly acquire the values stored in List

9 minute read

Front

In this article, I’ll show you some ways to handle iteration using the List data structure regardless of implementation.

Since the content of the article is a common sense level for those who normally use Java for programming, the target audience is beginners to intermediate learners who have begun learning Java. However, regarding the iterative processing using the forEach method added in JDK 1.8, it seems that few people are still using it at the development site, so I would like you to also remember how to use the forEach method at this time. ..

Note that this article deals with simple iteration using the List collection, so we will not touch on the Stream API that performs intermediate operations such as filter. We would like to introduce the Stream API in another article.

What you can do by reading this article

  • Iteration with List collection

Main repetitive processing operation method

  • Random access using get(int) method
  • Sequential access using extended for statement
  • Sequential access using forEach(Consumer<T>) method (JDK 1.8 or later)

Random access using the get method

Since convenient and efficient functions such as extended for statement and forEach method appeared in JDK 1.8, it is rare to use the get method for the List collection to iterate through random access. It was. However, this random access is the most basic iterative method using List collection regardless of implementation, so be sure to remember it if you are programming using Java.

The basic operation method is as follows.

import java.util.List;
import java.util.ArrayList;

public final class TestGet {

    public static void main(String[] args) {
        final List<String> testList = new ArrayList<>(3);
        testList.add("test1");
        testList.add("test2");
        testList.add("test3");

        for (int i = 0, size = testList.size(); i <size; i++) {
            // Get the i-th variable element (random access)
            System.out.println(testList.get(i));
        }
    }
}

The sample code above implements a test List that can store three elements as an ArrayList and then adds the test values add.

After that, use a basic for statement and repeat the process for the size of the testList prepared earlier. In the sample code above, the variable i is incremented each time the iterative process is performed, so as the comment says, the testList.get(i) process is performed sequentially for the elements of testList. It will be accessed randomly.

Therefore, the execution result of the above sample code is output as follows.

test1
test2
test3

important point

  • Be sure to clarify the accessible range when performing random access

When using random access using the get method as in the sample code above, be sure to check the size of the List to be accessed. If you specify an index that is larger than the size of the target List in the get method, a IndexOutOfBoundsException (out-of-range exception) will occur and the process will fail.

For example, in the following cases.

import java.util.List;
import java.util.ArrayList;

public final class TestIndexOutOfBoundsException {

    public static void main(String[] args) {
        final List<String> testList = new ArrayList<>();
        testList.add("test1");
        testList.add("test2");
        testList.add("test3");

        // specify the 4th index that does not exist
        testList.get(3);
    }
}

When I run the above sample code I get the following exception:

java.lang.IndexOutOfBoundsException: Index 3 out of bounds for length 3
    at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:64)
    at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:70)
    at java.base/jdk.internal.util.Preconditions.checkIndex(Preconditions.java:248)
    at java.base/java.util.Objects.checkIndex(Objects.java:373)
    at java.base/java.util.ArrayList.get(ArrayList.java:425)

The size of the List to be randomly accessed can be obtained with the size() method as in the sample code that ends normally. If you can always get only one record from the database based on a unique key, you can use test(get)(0); without checking the size of List with the size() method. It is acceptable for the process to get the target value.

The important point is to clarify the accessible range so that IndexOutOfBoundsException does not occur at runtime when doing random access.

  • Avoid random access to LinkedList as much as possible

In Java’s List collection, there is a commonly used data structure called LinkedList as well as ArrayList. This LinkedList has a data structure suitable for inserting a new element into an array or deleting an element in an array because the stored context of the elements is retained by a link.

However, random access to LinkedList has the worst performance. If the number of elements to be randomly accessed is about 100, the processing time will be almost the same as when using ArrayList, but the repeated access will be performed for large data of 100,000 or more in LinkedList. That’s not realistic from a performance standpoint.

Therefore, when handling a large amount of data in a LinkedList repeatedly, the sequential access such as the extended for statement and the forEach method, which will be introduced later, is used instead of the random access using the get method. Please go.

Aside

I have introduced the following sample code for random access.

import java.util.List;
import java.util.ArrayList;

public final class TestGet {

    public static void main(String[] args) {
        final List<String> testList = new ArrayList<>(3);
        testList.add("test1");
        testList.add("test2");
        testList.add("test3");

        for (int i = 0, size = testList.size(); i <size; i++) {
            // Get the i-th variable element (random access)
            System.out.println(testList.get(i));
        }
    }
}

Perhaps I thought that the for statement of the above sample code is a writing style that is not familiar to beginners, so I will leave an explanation. In the sample code above, the for statement can be written as follows.

for (int i = 0; i <testList.size(); i++) {
    // do something
}

In the sample code introduced at the beginning, I tried to call the size() method that was in the initialization expression part in the conditional expression part to get the size of testList.

However, you should avoid writing the for statement above. Because the conditional expression part of the for statement, i <testList.size(), is executed each time iterate, so in the above code, the size() method is called for the size of testList. It will be. If there are 10 or 100 cases, there is almost no impact on performance, but even if small differences are accumulated, it will be a big difference.

For example, you can check that the processing of the conditional expression part is executed every time with the following sample code.

public final class Main {

    public static void main(String[] args) throws Exception {
        // Make sure the conditional part is executed every time
        for (int i = 0; i <10 && saySomething(); i++) {
            // do something
        }
    }

    private static boolean saySomething() {
        System.out.println("Hello World!");
        return true;
    }
}
```The execution result of the above sample code is output as follows. Although I don't write the above code in my usual development, I was able to confirm that the `saySomething()` method of the conditional expression part is executed 10 times.

Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World!


From the above, by calculating the number of cases used in the conditional expression part in the initialization expression part that is executed only once at the start of the for statement, it is possible to write a more efficient program without waste. This is a technique that can be applied not only to the `size()` method in the List collection but also in a variety of situations, so I would like everyone who is aiming for a better program to keep this in mind every day.

# Sequential access using the `extended for statement`

It's been a while since the `extended for statement` was added as a Java feature, and it has already become the de facto standard for sequential iteration of List collections. Writing the for statement is also simpler and more efficient than when using random access.

Prior to the introduction of the `extended for statement`, the `Iterator` interface was implemented by itself to achieve sequential access. Sequential access with the `Iterator` interface is complicated to implement and many, and since it is rarely used in the legacy writing style, I will not cover it in detail in this article, but as a knowledge, before the appearance of the `extended for sentence` Please put in the corner of your head that you realized sequential access by implementing the `Iterator` interface yourself.

Also, the `IndexOutOfBoundsException` (out-of-range exception) introduced during random access using the `get` method and the performance degradation when using `LinkedList` do not occur with sequential access using the `extended for statement`.

Therefore, if you can use the `extended for sentence`, do not repeat it by random access, and try to write the iteration process using the `extended for sentence`.

Now, the random access sample code that uses the `get` method introduced above can be rewritten as sequential access using the `extended for statement` as follows.

```java
import java.util.List;
import java.util.ArrayList;

public final class TestEnhancedForStatement {

    public static void main(String[] args) {
        final List<String> testList = new ArrayList<>(3);
        testList.add("test1");
        testList.add("test2");
        testList.add("test3");

        for (String value: testList) {
            // Get the values stored in testList in order from the beginning
            System.out.println(value);
        }
    }
}

The way to read the extended for statement in the sample code above is as follows: “Get the values stored in testList in order from the beginning as String type values and store them in the variable name value”. Become. The syntax is the same for other high-level languages such as Python and Ruby, so if you have already touched other high-level languages, you should not have any difficulty.

And the output result of executing the above sample code is as follows.

test1
test2
test3

Sequential access using the forEach method

With the introduction of the functional interface in JDK1.8, the forEach(Consumer<T>) method for iterating the List collection has been added. This method works very much like the forEach(BiConsumer<T,U>) method I introduced in the article 1 I wrote about iterating over Map collections. It is now possible to write sequential access processing more concisely and intuitively than the extended for statement.

The basic way of writing is as follows.

import java.util.List;
import java.util.ArrayList;

public final class TestForEach {

    public static void main(String[] args) {
        final List<String> testList = new ArrayList<>(3);
        testList.add("test1");
        testList.add("test2");
        testList.add("test3");

        testList.forEach((value) -> {
            // Get the values stored in testList in order from the beginning
            System.out.println(value);
        });
    }
}

What seems difficult is the argument, but you pass the lambda expression that implements the functional interface Consumer<T> as the argument to the forEach() method. Consumer<T> is a functional interface that abstracts the operation that receives only one argument and returns no value.

I will omit about the lambda expression and the functional interface because it deviates from the purpose of this article, but when using the forEach() method in List, it is okay to remember the above sample code.

And the output result of executing the above sample code is as follows.

test1
test2
test3

important point

  • forEach method cannot return a value

As I mentioned earlier in the explanation of the functional interface, the forEach method cannot return a value due to the characteristics of Consumer<T>. Therefore, if you want to return false as a boolean value when some error is detected during the iteration of List, consider using the extended for statement.