Comparing One String With Multiple Values in One Expression in Java – 在 Java 中用一个表达式比较一个字符串和多个值

最后修改: 2023年 12月 25日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this tutorial, we’ll discuss the various ways of finding a string among a group of strings using a single expression.

在本教程中,我们将讨论使用单个表达式在一组字符串中查找字符串的各种方法。

Let’s assume there’s a fruit “Apple” and a group of fruits “Mango”, “Papaya”, “Apple”, “Pineapple”, etc. Now we’ll explore the various ways to see if the string “Apple” is present among the group of fruits.

假设有一种水果 “苹果”,以及一组水果 “芒果”“木瓜”“苹果”“菠萝” 等。现在,我们将探索各种方法来查看 “Apple” 字符串是否出现在这组水果中。

2. Introduction to the Problem

2.问题介绍

Before we move on to the next sections covering the single expression solution, let’s look at an implementation with the if condition:

在接下来的章节介绍单一表达式解决方案之前,我们先来看看 if 条件的实现:

boolean compareWithMultipleStringsUsingIf(String str, String ... strs) {
    for(String s : strs) {
        if (str.equals(s)) {
            return true;
        }
    }
    return false;
}

This is a very basic implementation, perhaps the most popular among all the implementations. We iterate through the array of Strings and return true when the String str matches with any one of the elements in strs.

这是一个非常基本的实现,也许是所有实现中最流行的一种。我们遍历 Strings 数组,当 String strstrs 中的任何一个元素匹配时,返回 true

Let’s see how this works:

让我们看看它是如何工作的:

@Test
void givenStrings_whenCompareWithMultipleStringsUsingIf_thenSuccess() {
    String presentString = "Apple";
    String notPresentString = "Avocado";

    assertTrue(compareWithMultipleStringsUsingIf(presentString, "Mango", "Papaya", "Pineapple", "Apple"));
    assertFalse(compareWithMultipleStringsUsingIf(notPresentString, "Mango", "Papaya", "Pineapple", "Apple"));
}

We’re finding the presence of the Strings such as “Apple” and “Avacado” in a group of fruits. But this involves multiple lines of code. Hence let’s look at the upcoming sections for solutions involving a single expression.

我们要在一组水果中找到 “Apple”“Avacado”Strings 的存在。但这涉及多行代码。因此,让我们在接下来的章节中看看涉及单一表达式的解决方案。

3. Match With Set

3.与套装匹配

java.util.Set has the contains() method which checks if an element exists in the collection. Hence, we’ll use java.util.Set for our use case with a single expression:

java.util.Set 具有 contains() 方法,该方法可检查元素是否存在于集合中。因此,我们将使用java.util.Set来处理我们的用例,并使用一个表达式:

boolean compareWithMultipleStringsUsingSet(String str, String ... strs) {
    return Set.of(strs).contains(str);
}

With a single expression, we’ve initialized a Set and then used the contains() method to see if str is present in the Set. However, we cannot implement a single expression case-insensitive matching method using Set.

通过单表达式,我们初始化了一个 Set 并使用 contains() 方法查看 Set 中是否存在 str 。但是,我们无法使用 Set. 实现单表达式大小写不敏感匹配方法。

Let’s test the method compareWithMultipleStringsUsingSet():

让我们测试 compareWithMultipleStringsUsingSet() 方法:

@Test
void givenStrings_whenCompareWithMultipleStringsUsingSet_thenSuccess() {
    String presentString = "Apple";
    String notPresentString = "Avocado";

    assertTrue(compareWithMultipleStringsUsingSet(presentString, "Mango", "Papaya", "Pineapple", "Apple"));
    assertFalse(compareWithMultipleStringsUsingSet(notPresentString, "Mango", "Papaya", "Pineapple", "Apple"));
}

Just like before, we first pass “Apple” to the method, and it returns true and when we pass “Avocado”, it returns false. Hence, that works pretty well.

就像之前一样,我们首先将 “Apple” 传递给该方法,它会返回 true ;当我们传递 “Avocado” 时,它会返回 false 。因此,这个方法非常有效。

4. Match With List

4.与列表匹配

Similar to Set, List also has the contains() method. Hence let’s check out the implementation using List:

Set 类似,List 也有 contains() 方法。因此,让我们来看看使用 List 的实现:

boolean compareWithMultipleStringsUsingList(String str, String ... strs) {
    return List.of(strs).contains(str);
}

There isn’t much difference, we’ve replaced Set with List.

我们用 List 代替 Set 并没有太大区别。

Let’s see it in action as well:

让我们也来看看它的实际效果:

@Test
void givenStrings_whenCompareWithMultipleStringsUsingList_thenSuccess() {
    String presentString = "Apple";
    String notPresentString = "Avocado";

    assertTrue(compareWithMultipleStringsUsingList(presentString, "Mango", "Papaya", "Pineapple", "Apple"));
    assertFalse(compareWithMultipleStringsUsingList(notPresentString, "Mango", "Papaya", "Pineapple", "Apple"));
}

The contains() method works as expected and it returns the correct result.

contains()方法按预期运行,并返回正确的结果。

5. Match With Stream

5.与匹配

The Stream API encourages the use of declarative statements and hence it can help us in implementing a single expression method:

Stream API 鼓励使用声明式语句,因此可以帮助我们实现单一表达式方法

boolean compareWithMultipleStringsUsingStream(String str, String ... strs) {
    return Arrays.stream(strs).anyMatch(str::equals);
}

In a single expression, we converted the array of Strings into a Stream, and then we used the method anyMatch(). The method anyMatch() is a terminal operation in a Stream pipeline. Each element in the Stream is compared with the String str. However, the anyMatch() method returns the first match.

在单个表达式中,我们将 Strings 数组转换为 Stream ,然后使用了方法 anyMatch() 。方法 anyMatch()Stream 管道中的 终端操作Stream 中的每个元素都会与 String str. 进行比较。不过,anyMatch() 方法会返回第一个匹配结果。

Let’s check out the method in action:

让我们来看看这种方法的实际效果:

@Test
void givenStrings_whenCompareWithMultipleStringsUsingStream_thenSuccess() {
    String presentString = "Apple";
    String notPresentString = "Avocado";

    assertTrue(compareWithMultipleStringsUsingStream(presentString, "Mango", "Papaya", "Pineapple", "Apple"));
    assertFalse(compareWithMultipleStringsUsingStream(notPresentString, "Mango", "Papaya", "Pineapple", "Apple"));
}

The method works as expected with “Apple” and “Avocado” as the first argument, returning true and false respectively.

使用 “Apple”“Avocado” 作为第一个参数时,该方法如预期一样工作,分别返回 truefalse

Let’s see a case-insensitive version using Stream:

让我们看看使用 Stream 的不区分大小写版本:

boolean compareCaseInsensitiveWithMultipleStringsUsingStream(String str, String ... strs) {
    return Arrays.stream(strs).anyMatch(str::equalsIgnoreCase);
}

Unlike the earlier version we had to call the equalsIgnoreCase() method as a predicate to anyMatch().

与早期版本不同,我们必须调用 equalsIgnoreCase() 方法作为 anyMatch() 的谓词。

We can now take a look at the method in action:

现在,我们可以看看该方法的实际效果:

@Test
void givenStrings_whenCompareCaseInsensitiveWithMultipleStringsUsingStream_thenSuccess() {
    String presentString = "APPLE";
    String notPresentString = "AVOCADO";

    assertTrue(compareCaseInsensitiveWithMultipleStringsUsingStream(presentString, "Mango", "Papaya", "Pineapple", "Apple"));
    assertFalse(compareCaseInsensitiveWithMultipleStringsUsingStream(notPresentString, "Mango", "Papaya", "Pineapple", "Apple"));
}

This time it can find out “APPLE” among “Mango”, “Papaya”, “Pineapple”, and “Apple”.

这一次,它可以从“芒果”“木瓜”“菠萝”“苹果 “中找出“APPLE”

6. Match With StringUtils

6. 与 StringUtils 匹配

Before we can use the class StringUtils from the commons-lang3 library, let’s first update the pom.xml with its Maven dependency:

在使用 commons-lang3 库中的 StringUtils 类之前,让我们先用 Maven 依赖关系更新 pom.xml

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.13.0</version>
</dependency>

Now, let’s use the class StringUtils:

现在,让我们使用 StringUtils 类

boolean compareWithMultipleStringsUsingStringUtils(String str, String ... strs) {
    return StringUtils.equalsAny(str, strs);
}

The method equalsAny() in StringUtils helps implement our use case in a single expression.

StringUtils中的方法equalsAny()有助于在单个表达式中实现我们的用例

Let’s see the method in action:

让我们看看该方法的实际效果:

@Test
void givenStrings_whenCompareWithMultipleStringsUsingStringUtils_thenSuccess() {
    String presentString = "Apple";
    String notPresentString = "Avocado";

    assertTrue(compareWithMultipleStringsUsingStringUtils(presentString, "Mango", "Papaya", "Pineapple", "Apple"));
    assertFalse(compareWithMultipleStringsUsingStringUtils(notPresentString, "Mango", "Papaya", "Pineapple", "Apple"));
}

This also works as expected, returning true and false when “Apple” and “Mango” are passed to the method, respectively.

“Apple”“Mango” 分别传递给该方法时,该方法也会按照预期运行,分别返回 truefalse

Let’s take a look at the case-insensitive version of the method as well:

我们也来看看该方法的不区分大小写版本:

boolean compareCaseInsensitiveWithMultipleStringsUsingStringUtils(String str, String ... strs) {
    return StringUtils.equalsAnyIgnoreCase(str, strs);
}

Instead of the method equalsAny() we used the method equalsAnyIgnoreCase() of StringUtils.

我们使用了 StringUtilsequalsAnyIgnoreCase() 方法,而不是 equalsAny() 方法。

Let’s see how the method works:

让我们来看看这个方法是如何运作的:

@Test
void givenStrings_whenCompareCaseInsensitiveWithMultipleStringsUsingStringUtils_thenSuccess() {
    String presentString = "APPLE";
    String notPresentString = "AVOCADO";

    assertTrue(compareCaseInsensitiveWithMultipleStringsUsingStringUtils(presentString, "Mango", "Papaya", "Pineapple", "Apple"));
    assertFalse(compareCaseInsensitiveWithMultipleStringsUsingStringUtils(notPresentString, "Mango", "Papaya", "Pineapple", "Apple"));
}

We could successfully find the String “APPLE” among the group of fruits.

我们成功地在一组水果中找到了字符串 “APPLE”

7. Match With ArrayUtils

7.使用 ArrayUtils 匹配

Furthermore, we’ll see another class ArrayUtils from the same commons-lang library:

此外,我们还将看到来自同一个 commons-lang 库的另一个类 ArrayUtils

boolean compareWithMultipleStringsUsingArrayUtils(String str, String ... strs) {
    return ArrayUtils.contains(strs, str);
}

ArrayUtils is a utility class that helps to check if an element is present in an array of objects. Hence, we exploited it to help us implement our use case involving Strings. Unfortunately, ArrayUtils doesn’t provide any method to find a String object in a case-insensitive way. Hence we cannot do a single expression implementation for the same.

ArrayUtils 是一个实用程序类,可帮助检查对象数组中是否存在元素。因此,我们利用它来帮助我们实现涉及 Strings 的用例。遗憾的是,ArrayUtils 没有提供任何方法来以不区分大小写的方式查找 String 对象。因此,我们无法为此实现单一表达式。

Let’s see how the method compareWithAnyUsingArrayUtils() works:

让我们看看 compareWithAnyUsingArrayUtils() 方法是如何工作的:

@Test
void givenStrings_whenCompareWithMultipleStringsUsingArrayUtils_thenSuccess() {
    String presentString = "Apple";
    String notPresentString = "Avocado";

    assertTrue(compareWithMultipleStringsUsingArrayUtils(presentString, "Mango", "Papaya", "Pineapple", "Apple"));
    assertFalse(compareWithMultipleStringsUsingArrayUtils(notPresentString, "Mango", "Papaya", "Pineapple", "Apple"));
}

Quite unsurprisingly, it works as well.

不出所料,效果也很好。

8. Match With Regular Expression

8.使用正则表达式匹配

Regular expression helps find patterns and hence we’ll use it for our use case:

正则表达式有助于发现模式,因此我们将在用例中使用它:

boolean compareWithMultipleStringsUsingRegularExpression(String str, String ... strs) {
    return str.matches(String.join("|", strs));
}

The method String.join() creates a pipe-delimited list of Strings, for example, Mango|Papaya|Pineapple|Apple which is used as a regular expression pattern. In a single expression, we created the regular expression pattern with the array of Strings and then used the method matches() to check if the String str has the pattern.

方法 String.join() 创建了一个以管道分隔的 Strings 列表,例如,Mango|Papaya|Pineapple|Apple,它被用作正则表达式模式。在单个表达式中,我们使用 Strings 数组创建了正则表达式模式,然后使用方法 matches() 检查 String str 是否包含该模式。

Time to see the method in action:

是时候看看这种方法的实际效果了:

@Test
void givenStrings_whenCompareWithMultipleStringsUsingRegularExpression_thenSuccess() {
    String presentString = "Apple";
    String notPresentString = "Avocado";

    assertTrue(compareWithMultipleStringsUsingRegularExpression(presentString, "Mango", "Papaya", "Pineapple", "Apple"));
    assertFalse(compareWithMultipleStringsUsingRegularExpression(notPresentString, "Mango", "Papaya", "Pineapple", "Apple"));
}

The method returns true for the argument “Mango” and false for “Avocado”. Hence, we can say it works too. However, regular expressions are always performance-intensive, so it’s better to avoid them.

对于参数 “芒果”,该方法返回 true;对于参数 “鳄梨”,该方法返回 false。因此,我们可以说它也是有效的。不过,正则表达式总是性能密集型的,因此最好避免使用

Now, let’s take a look at the case-insensitive implementation:

现在,让我们来看看不区分大小写的实现方法:

boolean compareCaseInsensitiveWithMultipleStringsUsingRegularExpression(String str, String ... strs) {
    return str.matches("(?i)" + String.join("|", strs));
}

We just had to modify the regular expression by prepending it with (?i) to do case-insensitive pattern matching.

我们只需修改正则表达式,在其前面加上 (?i) 以进行不区分大小写的模式匹配。

Let’s take a look at the method in action:

让我们来看看这种方法的实际效果:

@Test
void givenStrings_whenCompareCaseInsensitiveUsingRegularExpression_thenSuccess() {
    String presentString = "APPLE";
    String notPresentString = "AVOCADO";

    assertTrue(compareCaseInsensitiveWithMultipleStringsUsingRegularExpression(presentString, "Mango", "Papaya", "Pineapple", "Apple"));
    assertFalse(compareCaseInsensitiveWithMultipleStringsUsingRegularExpression(notPresentString, "Mango", "Papaya", "Pineapple", "Apple"));
}

By using the method now we can find “APPLE” in the provided group of fruits as well.

通过使用该方法,我们也可以在提供的水果组中找到 “APPLE”

9. Benchmark

9. 基准

Let’s calculate the average execution time of each of the single expression methods using Java Microbenchmark Harness (JMH).

让我们使用 Java Microbenchmark Harness (JMH) 计算每个单一表达式方法的平均执行时间。

Let’s take a look at the class configured for carrying out the benchmark:

让我们来看看为执行基准而配置的班级:

@State(Scope.Benchmark)
@BenchmarkMode(Mode.AverageTime)
@Warmup(iterations = 2)
@Measurement(iterations = 5)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@Fork(value = 1)
public class CompareAnyBenchmark {
    private final String[] groupOfFruits = {"Apple", "Mango", "Dragon Fruit", "Water Melon", "Avocado", "Guava", "Orange"};
    private final String fruit = "Apple";

    @Benchmark
    public boolean compareWithMultipleStringsUsingStringUtils() {
        return StringUtils.equalsAny(fruit, groupOfFruits);
    }

    @Benchmark
    public boolean compareCaseInsensitiveWithMultipleStringsUsingStringUtils() {
        return StringUtils.equalsAnyIgnoreCase(fruit, groupOfFruits);
    }
    //Other benchmark methods...
    public static void main(String[] args) throws Exception {
        Options options = new OptionsBuilder().include(CompareAnyBenchmark.class.getSimpleName())
          .threads(1)
          .shouldFailOnError(true)
          .shouldDoGC(true)
          .jvmArgs("-server")
          .build();
        new Runner(options).run();
    }
}

The annotations at the class level set up the benchmark to measure the average time, in nanoseconds, taken for five runs of each method. Finally, the main() method is for running the benchmark.

类级别的注释设置了基准,以纳秒为单位测量每个方法运行 5 次所需的平均时间。最后,main() 方法用于运行基准。

Let’s now take a look at the average execution time taken for each of the methods we discussed so far:

现在让我们来看看迄今为止讨论过的每种方法的平均执行时间:

Method Name Avg. Time Error(±) Unit
compareWithMultipleStringsUsingArrayUtils() 1.150 0.031 ns/op
compareWithMultipleStringsUsingRegularExpression() 1175.809 177.940 ns/op
compareWithMultipleStringsUsingSet() 96.961 11.943 ns/op
compareWithMultipleStringsUsingList()
28.718 1.612 ns/op
compareWithMultipleStringsUsingStream() 47.266 3.968 ns/op
compareWithMultipleStringsUsingStringUtils 1.507 0.040 ns/op
compareCaseInsensitiveWithMultipleStringsUsingRegularExpression() 1803.497 645.104 ns/op
compareCaseInsensitiveWithMultipleStringsUsingStream() 63.079 56.509 ns/op
compareCaseInsensitiveWithMultipleStringsUsingStringUtils() 1.521 0.077 ns/op

The methods compareCaseInsensitiveWithMultipleStringsUsingRegularExpression() and compareWithMultipleStringsUsingRegularExpression() using regular expressions take the longest to execute. On the other hand, the methods compareWithMultipleStringsUsingArrayUtils(), and compareWithMultipleStringsUsingStringUtils() take the least time to execute.

使用正则表达式的方法 compareCaseInsensitiveWithMultipleStringsUsingRegularExpression()compareWithMultipleStringsUsingRegularExpression() 的执行时间最长。另一方面,compareWithMultipleStringsUsingArrayUtils() 和 compareWithMultipleStringsUsingStringUtils() 方法的执行时间最少。

Without considering any external library, the methods compareWithMultipleStringsUsingStream(), and compareCaseInsensitiveWithMultipleStringsUsingStream() have the best score. Moreover, the performance also doesn’t vary much for case-insensitive searches.

在不考虑任何外部库的情况下,方法 compareWithMultipleStringsUsingStream(),compareCaseInsensitiveWithMultipleStringsUsingStream() 的得分最高。此外,不区分大小写搜索的性能差异也不大。

10. Conclusion

10.结论

In this article, we explored various ways to find the presence of a String in a group of Strings. With java.util.Set, java.util.List, java.util.Stream and regular expressions we didn’t use any external library outside of the JDK. Hence it’s advisable to use them rather than going for external libraries like commons-lang. Moreover, the List implementation is the best choice in the JDK library.

在本文中,我们探讨了在一组 String 中查找 String 是否存在的各种方法。通过使用 java.util.Setjava.util.Listjava.util.Stream 和正则表达式,我们没有在 JDK 之外使用任何外部库。因此,我们建议您使用它们,而不是使用外部库(如 commons-lang)。此外,List 实现是 JDK 库中的最佳选择。

As usual, the code examples can be found over on GitHub.

像往常一样,代码示例可以在 GitHub 上找到