이 문서에서는 Java의 List 에서 특정 원소를 대소문자 구분없이 찾는 것 관련내용을 정리합니다.

Java개발을 하다보면, 리스트를 순회하면서 특정 원소를 찾고 싶을 때가 있습니다. (대소문자 구분 없이!) 예를 들어, 다음과 같은 리스트가 있다고 가정하고 찾으려는 것 역시 정의하고 시작합니다.

List<String> list = Arrays.asList("Apple", "Banana", "Orange");
String search = "apple";

이 글의 목적은 위 상황에서 apple 이 ‘있다’고 판단되어야 합니다.

Java native for-loop

그저 for-loop 를 진행하면서 String 함수인 equalsIgnoreCase를 넣으면 됩니다.

public static boolean containsIgnoreCase(List<String> list, String search) {
    for (String s : list) {
        if (s != null && s.equalsIgnoreCase(search)) {
            return true;
        }
    }
    return false;
}

Stream 을 이용한 방법을 사용 (추천)

아래처럼 스트림을 이용하는 방법도 있습니다.

List<String> list = Arrays.asList("Apple", "Banana", "Orange");
String search = "apple";

boolean exists = list.stream()
                     .anyMatch(search::equalsIgnoreCase);

성능을 최우선으로 하는 방법

성능이 너무너무 중요하고 빈번하게 호출되는 부분이라면 아래처럼 TreeMap 을 활용하는 방법도 있습니다. 위 방법들 모두 loop 를 하는 방법으로 O(n) 인 반면, 아래 TreeMap 을 활용하면 O(log n) 으로 성능이 향상됩니다.

// Create a set that ignores case automatically
Set<String> caseInsensitiveSet = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
caseInsensitiveSet.addAll(yourList);

boolean exists = caseInsensitiveSet.contains("APPLE");

Library 를 활용해보자

이런 기본적인 for-loop 등은, apache commons 라이브러리가 가장 많이 쓰이고 잘 되어 있습니다. 여기서는 org.apache.commons 그룹에 있는 라이브러리인 commons-collections4 라이브러리를 활용하는 방법을 보겠습니다.

Dependency 추가

우선 pom.xml / build.gradle 에 아래처럼 dependency를 추가합니다.

만약 4.5.0-M3 버전이 마음에 걸리면 4.4 버전을 이용하시면 될 듯 합니다.

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-collections4</artifactId>
    <version>4.5.0-M3</version>
</dependency>
implementation 'org.apache.commons:commons-collections4:4.5.0-M3'

사용방법

아래처럼 사용해볼 수 있습니다.

import org.apache.commons.collections4.IterableUtils;
import org.apache.commons.collections4.Predicate;
import java.util.Arrays;
import java.util.List;

public class Main {
    public static void main(String[] args) {
        List<String> fruits = Arrays.asList("Apple", "Banana", "Orange");
        String target = "BANANA";

        // Using IterableUtils to find an element ignoring case
        boolean exists = IterableUtils.matchesAny(fruits, new Predicate<String>() {
            @Override
            public boolean evaluate(String object) {
                return object != null && object.equalsIgnoreCase(target);
            }
        });

        System.out.println("Contains " + target + "? " + exists);

    }
}

이와는 별개로, Map 사용 시, key 값에 대한 대소문자 불편을 해소하려면, 아래처럼 사용하면 됩니다. CaseInsensitiveMap 을 활용한 방법입니다.

import org.apache.commons.collections4.map.CaseInsensitiveMap;
import java.util.Map;

Map<String, String> config = new CaseInsensitiveMap<>();
config.put("Timeout", "5000");

// This will return "5000" even though the case is different
String val = config.get("TIMEOUT");

마치며

방법이야 여러 가지가 있을 수 있겠지만, 글의 뒤 쪽에 설명한 라이브러리 같은 것들을 활용하는 것이 효율적일 수 있습니다.

내부적으로야 for-loop 를 사용할 지 모르겠으나, 우리 코드에서 그렇게 사용하면 아무래도 가독성이 떨어질 수밖에없으니까요.