在Java中检查三个或更多字符串的相等性

14

我知道这是一个非常基础的问题,但我一直在努力寻找方法使我的代码尽可能地简洁清晰。有没有办法在单个if()语句中比较三个或更多字符串的相等性?目前,我正在使用&&运算符逐步比较每个字符串。然而,由于变量名和方法名很长,if()语句很快就会变得非常混乱。此外,我计划拥有未知数量的这些字符串,并希望避免使用复杂的for循环,其中嵌套着混乱的if()语句。这是我的代码当前的样子:


```java if (string1.equals(string2) && string1.equals(string3) && string2.equals(string3)) { // some code here } ```
String a = new String("a");
String b = new String("b");
String c = new String("c");
if(a.equals(b) && b.equals(c)) {
    doSomething();
}

有没有一种方法或某种集合可以让我像这样更好地比较这些值:

if(a.equals(b).equals(c)) {
    doSomething();
}

@RoberKrupp,我刚刚发布了一个回答,为您提供了最好的解决方案。请看一下并让我知道是否需要任何澄清。 - Chetan Kinger
你听说过函数吗?你可以编写函数来做一些事情,几乎是你想要的任何事情。然后你就可以调用它们,这多酷啊!你甚至可以给它起一个名字,比如allStringsEqual之类的,让其他人知道这个函数是做什么的。很不错吧? - aaa90210
1
@aaa90210 严格来说,Java 没有函数。它有方法 - Boris the Spider
7个回答

19
如果您有多个相同类型的对象,将它们组织在数据结构(如数组或列表)中是一个好主意。假设您有一个由String对象组成的List

如果您有多个相同类型的对象,将它们组织在数据结构(如数组或列表)中是一个好主意。所以假设您有一个ListString对象组成:

List<String> strings = Arrays.asList("a", "b", "a", ...);

您想知道列表中的所有字符串是否相等。这可以通过多种方法实现。可能最短的一种是:

if(new HashSet<>(strings).size() == 1) {
    // all strings are equal
}

@niceguy 提出了一种更长,但更优的解决方案。如果您正在使用Java-8,也可以这样做:

if(strings.stream().distinct().count() == 1) {
    // all strings are equal
}

1
这是一种非常干净的方法。尽管它引入了HashSet中使用的哈希算法所引起的不必要的开销。 - Chetan Kinger
@Tagir Valeev,我非常喜欢你的解决方案是多么简洁,但作为一个或多或少是自学成才的程序员,我不太熟悉像'HashSet'这样的高阶数据结构。你知道我可以利用哪些资源来学习这些概念吗?我已经尝试查看了Java文档,但我相信你已经知道那些信息有多么密集(甚至可以说是令人生畏)。 - Robert Krupp
1
统计所有唯一字符串的出现次数是相当繁琐的工作,而 distinct().count() 基本上与幕后的 new HashSet<>(strings).size() 相同。另一方面,strings.isEmpty() || strings.stream().allMatch(strings.get(0)::equals) 虽然不那么长,但总体上更加轻便并且支持短路。如果要处理 null,则 strings.isEmpty() || strings.stream().allMatch(Predicate.isEqual(strings.get(0))) 是可行的方法。 - Holger

17

没有简单的方法来像这样链接你的equals()命令。为了能够以这种方式链接它们,equals()必须返回对字符串本身的引用。然而,这样做将无法再返回表示比较结果的布尔值。

此外,我认为像你第一个示例中分别比较三个字符串并不是特别问题。确保保持代码格式良好,即使变量名较长,代码也会易于理解:

if(myValueAWithALongName.equals(myValueBWithALongName) &&
   myValueBWithALongName.equals(myValueCWithALongName) &&
   myValueCWithALongName.equals(myValueDWithALongName) &&
   ... ) {

或者,您可以使用此线程中提出的其他解决方案之一,将您的值包装到集合中并编写一个帮助方法。但请注意,这可能会对内存使用、性能和可读性产生负面影响。


顺便提一下,永远不要使用构造函数new String()创建字符串。直接将字符串字面值分配给变量即可:

String a = "a";

@TimeSta 你选择忽略了null检查,这使得if条件看起来是可以接受的。添加null检查,你很快就会发现代码变得难以阅读。更好的选择是将if条件包装在一个方法中,然后在主if条件中调用该单个方法。此外,三个String对象是可以的。想象一下当你必须比较5个String对象时会发生什么?请参见我的答案... - Chetan Kinger
很好。根据示例代码,空值检查可能不是强制性的,但我怀疑在现实世界中它们不能被忽略。此外,OP显然正在寻找一个更简洁的解决方案。将3个或更多的if条件包装成一个有意义的名称的方法,并在主if语句中调用该方法,肯定会使if条件易于阅读。我认为这种优势不能为了个人风格而放弃。 - Chetan Kinger
@ChetanKinger 当您使用此方法时,只需要对第一个字符串进行一次空值检查。而使用对称的 Objects.equals 方法甚至可以避免该检查。 - CodesInChaos
@CodesInChaos 是的。正如我在回答中已经提到的那样,使用 Objects.equals 将是理想的选择。 - Chetan Kinger
@AlexeyYatsew 依赖于 String 池的行为,并希望传递到您的方法中的所有字符串都是字面值,而不是(例如)由 StringBuilder 创建或从文件中读取的字符串,这种做法极易出错。您的建议仅在像 if("a" == "a") 这样的退化情况下有用。即使是这种情况,我也不会让它通过代码审查。 - Boris the Spider
显示剩余3条评论

3

根据要比较的String对象数量的不同,有不同的解决方法。

如果你只需要比较几个String对象,并且在应用程序中只需要进行一两次这样的比较,那么可以将代码封装在需要此类比较的类中的方法中,以使代码更易读:

 public boolean isConditionSatisfied(String a,String b,String c) {
    return Objects.equals(a,b) && Objects.equals(b,c);
 }

客户端代码可以被简化并且更易读:
if(isConditionSatisfied(a,b,c)) { //doSomething }

请注意,自JDK 1.7以来,Objects.equals是一种内置方法。与其他答案相比,使用Object.equals可以防止NullPointerExcsption,我认为这是绝对必要的。
如果您经常需要进行这种比较(不仅仅是String对象),为什么只寻找用于比较String对象的解决方案?为什么不寻找适用于任何类型或数量对象的通用解决方案?
public class ObjectUtils {

    public static boolean multipleEquals(Object... objectsToCompare) {

        if (null == objectsToCompare) {
            return false;
        } else if (objectsToCompare.length == 0) {
            return false;
        } else if (objectsToCompare.length == 1)      {
            return true;
        }

        boolean allEqual = true;
        for (int curr = 1; curr < objectsToCompare.length; ++curr) {
            if(!Objects.equals(objectsToCompare[0],objectsToCompare[curr])) {
                allEqual = false;
                break;
            }
        }

        return allEqual;
    }
}

好的,您想要比较4个String对象,为什么不这样做:

if(ObjectUtils.multipleEquals("1","1","1","1") { }

但是现在你想要比较5个整数对象,没问题:

if(ObjectUtils.multipleEquals(1,2,3,4,5) { }

你甚至可以混合和匹配对象:
if(ObjectUtils.multipleEquals("1",2,"3",4) { }

2
你不需要将最后一项与第一项进行比较。这样可以简化代码。 - usr
我不确定这是否是他们downvote的原因,但说实话,你的代码有点不太优美。供参考,Jesper在这里有一个非常整洁的代码(https://dev59.com/JG035IYBdhLWcg3wTuJu#5503202)。 - Radiodef
@Radiodef Jesper有一个很棒的实现,但代码在遇到null时会立即崩溃,这在我看来是一个不好的事情。我的答案只是指示性的,可以根据个人口味进行改进。我的答案涵盖了许多好的观点,例如使用包装方法、使用 Objects.equals以及稳健的代码,在空值的情况下不会崩溃等等。不确定为什么用户们觉得给我一个提高答案的机会,而我的答案已经涵盖了这么多好的观点,不是真正的选项?我已经编辑了我的答案,去掉了不必要的检查。 - Chetan Kinger
我已经编辑了我的答案中的代码。如果那些给我投反对票的人能够发表评论并让我知道他们是否认为我的答案需要任何其他改进,那将是很好的。没有用的干净代码是毫无意义的。其他答案中的代码在出现一个“null”值时大多会崩溃。(我完全反对除非明显错误否则有人投反对票的想法。这就是为什么我选择在其他答案上留下评论而不是投反对票,即使它们中的大多数在现实世界中都会失败) - Chetan Kinger
@ChetanKinger 我可能会缩短if检查,它们感觉多余。如果有人觉得需要在不带参数的情况下调用该方法,则完全可以抛出异常,这可以记录在文档中,代码将变得更清晰。我认为使用空数组调用此函数是错误的,因此应该抛出异常。 - Falco
@Falco 这是个人选择。我不能修改我的代码以迎合每个人的喜好。 - Chetan Kinger

2

此外,我计划有许多这些字符串,希望避免复杂的嵌套if()循环。

我认为for循环并不会太复杂。

您可以通过尝试检查相等性来确定。

a.equals(b)
b.equals(c)
etc.

但是将所有内容与a进行比较会得到相同的结果。如果所有内容都等于a,那么所有内容就相互等同。

a.equals(b)
a.equals(c)
etc.

这使得 for 循环更简单。如果您不介意将 a 与其自身进行比较的开销,可以直接检查整个数组或集合(或其他任何内容)是否相等。

if 语句看起来也不会很混乱。只要有一对字符串不相等,就从函数中 return


2
这可能是一个不寻常的想法,但在这种情况下,一些元编程可能会有所帮助。
public static void nEquals(int nOverloads, PrintStream out) {
    if (nOverloads < 2)
        throw new IllegalArgumentException();

    for (int i = 2; i <= nOverloads; ++i) {
        out.println("public static boolean equals(");
        out.print("        Object obj0");

        for (int j = 1; j < i; ++j) {
            out.print(", Object obj" + j);
        }

        out.println(") {");
        out.print("    return obj0.equals(obj1)");

        for (int j = 2; j < i; ++j) {
            out.print(" && obj0.equals(obj" + j + ")");
        }

        out.println(";");
        out.println("}");
    }
}

nEquals(4, System.out); 打印如下内容:

public static boolean equals(
        Object obj0, Object obj1) {
    return obj0.equals(obj1);
}
public static boolean equals(
        Object obj0, Object obj1, Object obj2) {
    return obj0.equals(obj1) && obj0.equals(obj2);
}
public static boolean equals(
        Object obj0, Object obj1, Object obj2, Object obj3) {
    return obj0.equals(obj1) && obj0.equals(obj2) && obj0.equals(obj3);
}

您可以任意生成重载方法,无需使用varargs。只需将其复制粘贴至实用类中,便可完成操作。

if (Util.equals(a, b, c)) {
    doSomething();
}

对于这样简单的代码生成,出错的余地很小。唯一的缺点是可维护性,因为如果有人需要修改它(例如更改对null的处理,这在我的和你的示例中都被忽略了),他们需要修改生成器并再次运行它。


这不就是“可变参数”的意义吗? - Teepeemm
1
@Teepeemm 当然可以,但是varargs是一个数组。如果你只想要一些重载,但想确保它们都相同,还有另一种方法。 - Radiodef
@Radiodef,我真的很喜欢这个解决方案。这可能会成为一个非常好的Eclipse插件。尽管我在这个问题上有一个答案,但它正在遭受投票的打击,所以我还是给你点赞了 :)。话虽如此,我仍然觉得null检查是绝对必要的,因为肯定会有一个流氓客户端在某个地方抛出null。为什么不像我的答案建议的那样使用Objects.equals呢? - Chetan Kinger

1
如何尝试这样的代码:
boolean allEqual(List<String> elements) {
    if(elements.isEmpty() || elements.size() == 1) {
        return true;
    }
    String current = elements.get(0);
    if(current == null) {
        return false;
    }
    for(int i = 1; i < elements.size(); i++) {
        if(!current.equals(elements.get(i))) {
            return false;
        }
        current = elements.get(i);
    }
    return true;
}

这不算很美观,但你只需要编写一次代码,它即可适用于任意数量的字符串。


这段代码在遇到null值时会出错。 - Chetan Kinger
1
如果所有值都为null,那么该空值检查仍将失败。这取决于设计师和此示例的用例,但是如果所有字符串都为null,您该怎么办?它们是相等的,所以难道不应该返回true吗? - Matt C
1
@MatthewC:我同意,但这确实是一个设计决策。我想保持这个例子的最小化。 - niceguy
我认为这是最简单、最好的答案。 - Matt C
考虑allEqual(null)allEqual(largeLinkedList)两种情况。第一种情况可以通过契约处理,而第二种情况可能会带来性能上的惊喜。 - Aurand
显示剩余3条评论

0

另一种方法是使用 String 作为 Comparable 的实现。

public <T extends Comparable<T>> boolean equality(T... objects) {
    Arrays.sort(objects);
    return objects[0].compareTo(objects[objects.length - 1]) == 0;
}

优点: 这个解决方案适用于所有Comparable对象。

缺点:如果所有的值都相等,那么比较次数将为N(很好!),否则最大为N logN。此外,sort方法会分配内存用于临时存储,大小将从非常小到N/2不等。


2
我不确定这是否是最佳解决方案。排序的时间复杂度为O(n*log(n)),而简单地迭代并将第一个元素与其余元素进行比较的时间复杂度为O(n) - Pshemo
我并不是说这是最好的解决方案,只是另外一种选择。 - Aliaksei Yatsau

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接