14

I know this is a very basic question, but I am always trying to find ways to make my code a clean and concise as possible. Is there a way to compare the equality of three or more strings in a single if() statement? I am currently using the && operator to progressively compare each string. However, as you could imagine, between long variable names and methods being called, these if() statements get very cluttered very quickly. Additionally I am planning to have an unknown number of these Strings, and would like to avoid complex for loops with cluttered if()s nested inside of them. This is what my code currently looks like:

String a = new String("a");
String b = new String("b");
String c = new String("c");
if(a.equals(b) && b.equals(c)) {
    doSomething();
}

Is there a way, or a collection of some sort that I can use that would allow me to compare these values more like this:

if(a.equals(b).equals(c)) {
    doSomething();
}
Chetan Kinger
  • 15,069
  • 6
  • 45
  • 82
Robert Krupp
  • 179
  • 1
  • 1
  • 7
  • @RoberKrupp I just posted an answer that gives you the best of all worlds. Please take a look and let me know if you need any clarification. – Chetan Kinger May 31 '15 at 19:01
  • Have you heard of functions? You can write function to do stuff, like pretty much whatever you want. You can then call them, how cool is that? You could even name it something like allStringsEqual or something, so other people know what the function does. Neat, right? – aaa90210 Jun 01 '15 at 00:23
  • 1
    @aaa90210 strictly speaking Java doesn't have functions. It has [methods](https://stackoverflow.com/questions/155609/difference-between-a-method-and-a-function). – Boris the Spider Jun 02 '15 at 10:00

7 Answers7

18

If you have several objects of the same type, it's a good idea to organize them in the data structure like array or list. So suppose you have a List of String objects:

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

You want to know whether all the strings in the list equal to each other. This can be done in a number of methods. Probably the shortest one is:

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

Longer, but more optimal solution is proposed by @niceguy. If you are using Java-8, you can also do this way:

if(strings.stream().distinct().count() == 1) {
    // all strings are equal
}
Tagir Valeev
  • 97,161
  • 19
  • 222
  • 334
  • 1
    This is a really clean way of doing it. Although, it has the unnecessary overhead introduced by the hashing algorithms used in a `HashSet` – Chetan Kinger May 31 '15 at 19:08
  • @Tagir Valeev I REALLY like how concise your solution is, however, being a more-or-less self-taught programmer, I am not very familiar higher order data structures like 'HashSet' Do you know of any resources which I might utilize to learn these concepts? I have already tried to look through the java documentation, however I am sure you already know how dense (or dare I even say intimidating) that information can be. – Robert Krupp Oct 31 '15 at 16:46
  • 1
    Counting all occurrences of all unique strings is quiet some overhead and `distinct().count()` basically does the same as the `new HashSet<>(strings).size()` behind the scenes. On the other hand, `strings.isEmpty() || strings.stream().allMatch(strings.get(0)::equals)` is not so much longer while being short-circuiting and more lightweight in general. If `null` is supposed to be handled, `strings.isEmpty() || strings.stream().allMatch(Predicate.isEqual(strings.get(0)))` would be the way to go. – Holger Apr 20 '20 at 15:56
17

There is no simple way to chain your equals() commands like this. In order to be able to chain them this way, equals() would have to return a reference to the String itself. Then, however, it can't return a boolean value that represents the outcome of the comparison anymore.

Also, I see it as not particularly problematic to compare three strings separately as in your first example. Make sure you keep your code well formatted, and it will remain easy to understand, even for longer variable names:

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

Alternatively, you could use one of the other solutions proposed in this thread and wrap your values into a Collection and write a helper method. Note however that this might have a negative impact on memory usage, performance and possibly readablity.


On a side note, you should never create Strings using the Constructor new String(). Simply assign the string literal directly to your variable:

String a = "a";
TimoStaudinger
  • 41,396
  • 16
  • 88
  • 94
  • @TimeSta You have chosen to ignore the `null` checks which makes the `if` condition look acceptable. Add the `null` checks and you will soon see how unreadable the code becomes. A better option would be to wrap the `if` conditions in a method and then call that single method in the master `if` condition. Also, three `String` objects is fine. Imagine what happens when you have to compare just 5 `String` objects? See my answer.. – Chetan Kinger May 31 '15 at 19:04
  • @ChetanKinger If a more generic solution or null checks would be required, your solution would most certainly be superior to mine. However, in this case OP did not ask for null checks, nor would they be necessary considering the sample code. In this case, I'd prefer multiple conditions directly inside the if clause over a helper method, which is down to personal preference, though. The effect on memory usage, performance and maintainability wouldn't be much different for both solutions. – TimoStaudinger May 31 '15 at 19:16
  • Fair enough. Null checks may not be mandatory based on the sample code but I doubt that they can be ignored in the real world. Also, the OP is clearly looking for a solution with less clutter. Wrapping 3 or more if conditions into a method with a meaningful name and calling the method in the master if statement surely makes the if condition easily readable. l don't feel this advantage could be given up for personal style. – Chetan Kinger May 31 '15 at 19:33
  • @ChetanKinger You only need a single null check for the first string when you using this approach. And using the symmetric `Objects.equals` avoids even that check. – CodesInChaos Jun 01 '15 at 07:09
  • @CodesInChaos Yes. Using `Objects.equals` would be ideal as already mentioned in my answer. – Chetan Kinger Jun 01 '15 at 07:33
  • For literal created `String`'s in java 7 you can `if(a == b & b == c) {;}` [Strings literal](http://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.10.5) – Aliaksei Yatsau Jun 01 '15 at 09:06
  • @AlexeyYatsew relying on the behaviour of the `String` pool and hoping that all strings passed into your method are literals, rather than (for example) created by a `StringBuilder` or read from a file, is extremely errorprone. You suggestion is only useful in the degenerate case where you are doing something like `if("a" == "a")`. And **even then** I wouldn't let this through code review. – Boris the Spider Jun 02 '15 at 09:56
  • @TimoSta First, thank you for the insight, you are completely right the way you format the code is many times just as important as the logic itself. However, I do have a follow-up question. Is there any particular reason NOT declare a new String object using the constructor I have above? I guess I have not questioned such a simple seemingly insignificant yet recurrent part of my code since being taught way back in high school. – Robert Krupp Oct 31 '15 at 16:38
3

There are different ways to approach this problem depending on the number of String objects being compared.

If you only have a few String objects to be compared and you pretty much need to do this kind of comparison in one or two places in your application, you can wrap the code in a method inside the class that needs such a comparison so that the code becomes more readable :

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

The client code then be simplified and more readable :

if(isConditionSatisfied(a,b,c)) { //doSomething }

Note that Objects.equals is an inbuilt method available since JDK 1.7. In comparison to the other answers, the use of Object.equals safeguards you from a NullPointerExcsption which in my opinion is an absolute necessity.

If you find yourself needing this kind of a comparison quite often (and not only for String objects) why look for a solution only for comparing String objects? Why not find a generic solution that will work for any kind or number of objects?

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;
    }
}

Ok so you want to compare 4 String objects, why not :

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

But now you want to compare 5 Integer objects, no problem :

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

You can even mix and match objects :

if(ObjectUtils.multipleEquals("1",2,"3",4) { }
Chetan Kinger
  • 15,069
  • 6
  • 45
  • 82
  • 2
    You don't need to compare the last item to the first item. That simplifies the code. – usr May 31 '15 at 22:42
  • I have no idea if this is why they downvoted but your code is a bit jenky to be honest. For reference, Jesper has a really tidy one [here](http://stackoverflow.com/a/5503202). – Radiodef Jun 01 '15 at 03:38
  • @Radiodef Jesper does have a neat implementation but the code will break at the first sign of a null which in my opinion is a bad thing. The code in my answer is just indicative and can always be improved as per one's taste. My answer covers a lot of good points. Use of a wrapper method, use of ` Objects.equals` and solid code that won't break in case of null values to mention a few. Not sure why the users felt that giving me a chance to improve my answer that covers so many good points was not really an option? I have edited my answer to remove the unnecessary checks. – Chetan Kinger Jun 01 '15 at 06:41
  • I have edited the code in my answer. It would be great if the downvoters can post a comment and let me know if there is any other improvement they feel that my answer requires. Clean code that does not work is pointless. Code in other answers will mostly break in case of one rouge `null` value. (I am totally against the idea of someone downvoting an answer unless it is blatantly incorrect. That's why I chose to leave comments on other answers rather than downvoting them even if most of them will fail in the real world) – Chetan Kinger Jun 01 '15 at 06:44
  • @ChetanKinger I would probably shorten the if-checks, they feel redundant. - It is perfectly acceptable to throw an Exception if someone feels he needs to call the method without arguments, this can be documented and the code will get cleaner and I think it is an error to call this function with an empty array, so it should get thrown. – Falco Jun 01 '15 at 08:10
  • @Falco That's a personal choice. I can't be modifying my code to everyone's liking. – Chetan Kinger Jun 01 '15 at 09:17
2

Additionally I am planning to have an unknown number of these Strings, and would like to avoid complex for loops with cluttered if()s nested inside of them.

I don't think that a for loop would be that complex.

You can certainly check for equality by trying

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

But comparing everything to a will have the same result. If everything is equal to a, everything is equal to each other.

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

This makes the for loop simpler. If you don't mind the overhead of comparing a to itself you can simply check your entire array or collection (or whatever) against a for equality.

The if doesn't look cluttered either. return from the function as soon as one pair of strings is not equal.

null
  • 5,207
  • 1
  • 19
  • 35
2

This may be an unusual idea, but situations like this are where a little metaprogramming may come in handy.

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); prints the following:

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);
}

You can generate as many overloads as you want, no need for varargs. Just copy and paste to a utility class and you're done.

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

For such a simple code generation, the margin for error is small. The only downside is maintainability, because if somebody ever did need to tinker with it (such as to change the handling of null, which is ignored in both my and your examples), they need to modify the generator and run it again.

Radiodef
  • 37,180
  • 14
  • 90
  • 125
  • Isn't this the point of `varargs`? – Teepeemm Jun 01 '15 at 01:11
  • 1
    @Teepeemm Sure, but varargs is an array. If you just want a few overloads but want to make sure they're all the same, there's another way. – Radiodef Jun 01 '15 at 01:20
  • @Radiodef I really like this solution. This could make a very good eclipse plugin. +1 from me even though I have an answer on this question which is being bombarded with downvotes :). That being said, I still feel that `null` checks is an absolute necessity since there will be one rouge client that will throw in a `null` somewhere for sure. Why not use `Objects.equals` as suggested in my answer? – Chetan Kinger Jun 01 '15 at 07:16
1

How about something like this:

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;
}

Not exactly pretty, but you need to write it only once and it works for an arbitrary number of strings.

niceguy
  • 92
  • 4
  • This code will break in case of `null` values. – Chetan Kinger May 31 '15 at 17:48
  • @ChetanKinger good point, I added a null check. – niceguy May 31 '15 at 17:51
  • 1
    That null check will still fail if all values are null. It depends on the designer and the use case for this example, but what do you do if all strings are null? They are equal, so shouldn't you return true? – Matt C May 31 '15 at 17:56
  • 1
    @MatthewC: I agree, but that is indeed a design decision. I wanted to keep this example minimal. – niceguy May 31 '15 at 18:07
  • @niceguy That's not a design decision. That's actually an implementation decision. `Objects.equals` is a nice utility method that makes your work simple. See my answer. – Chetan Kinger May 31 '15 at 19:06
  • I think this is the simplest, and best answer. – Matt C Jun 01 '15 at 00:34
  • @MathewC Sure. If you don't care about NullPointerExcsption – Chetan Kinger Jun 01 '15 at 02:47
  • Consider the cases `allEqual(null)` and `allEqual(largeLinkedList)`. The first can arguably be dealt with contractually, the second could be a nasty performance surprise. – Aurand Jun 01 '15 at 06:38
0

One more way is to use String as implementation of Comparable.

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

Pros: This solution is universal for all Comparable objects.

Cons: If all values equals that it'll be N(great!) comparison otherwise N logN max. Also here will be allocated memory for temporary storage for sort method which will vary from very small to N/2.

Aliaksei Yatsau
  • 749
  • 7
  • 12