이 문서에서는 Java의 List 에서 특정 원소를 대소문자 구분없이 찾는 것 관련내용을 정리합니다.
Java개발을 하다보면, 리스트를 순회하면서 특정 원소를 찾고 싶을 때가 있습니다. (대소문자 구분 없이!) 예를 들어, 다음과 같은 리스트가 있다고 가정하고 찾으려는 것 역시 정의하고 시작합니다.
List<String> list = Arrays.asList("Apple", "Banana", "Orange");
String search = "apple";
이 글의 목적은 위 상황에서 apple 이 ‘있다’고 판단되어야 합니다.
3 Ways for search
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 를 사용할 지 모르겠으나, 우리 코드에서 그렇게 사용하면 아무래도 가독성이 떨어질 수밖에없으니까요.