0

I've two objects and need to compare their properties. The code currently use a very long sequence of if/else statements to step through the properties.

Is there a more intelligent to go about it?

For example, it looks like:

 if (car.getIsElectric() && (!parkingGarage.getIsElectric())) {
   log("Electric cars are not permitted here");
   return 1;
 }

 if (car.getIsSuv() && (!parkingGarage.getIsSuv())) {
   log("SUVs are not allowed in.");
   return 1;
 }
 ...

This questions is similar to: Comparing the properties of two objects and I want to see if there is an equivalent form in Java.

Simply_me
  • 2,840
  • 4
  • 19
  • 27
  • 1
    An infinite sequence?! – Uzer Aug 22 '18 at 20:20
  • 1
    meaning, an extremely long list of properties is compared with if/else statements. – Simply_me Aug 22 '18 at 20:23
  • Can we see some more code? Depending on what you're trying to achieve, you can probably reduce the amount of code by using a bit-string where each bit represents a property. – Jacob G. Aug 22 '18 at 20:27
  • Do you just need a boolean equal / not equal answer or do you need to know how they differ? If it's just boolean, you could write a long series of comparisons with `&&` in between. Still ugly, but at least shorter. – Robert Aug 22 '18 at 20:28
  • @JacobG. the code is just 100 lines of if/else statements like the one above for different properties. – Simply_me Aug 22 '18 at 20:32
  • Then can you give us an idea about what you're trying to achieve? – Jacob G. Aug 22 '18 at 20:33
  • @JacobG. Comparing each property without having to write an `if...else` for every single one. – Kröw Aug 22 '18 at 20:41
  • Unfortunately "comparing each property" doesn't provide any more information than what's in your question. What are you **actually** trying to do? – Jacob G. Aug 22 '18 at 20:43
  • 1
    @JacobG. the end goal is to let it through or to direct it to a different flow. For example, all electric cars go to parking garage A.... – Simply_me Aug 22 '18 at 20:48
  • That can be done easily by streaming your collection of cars and using `Collectors.groupingBy` – Jacob G. Aug 22 '18 at 20:49
  • @JacobG. I've updated the examples, perhaps it'll help. – Simply_me Aug 22 '18 at 20:54
  • Now that you've *totally* changed the question to not compare two objects of same type, but to apply **access rules** of whether a car can enter a garage, there is no way to reduce your code, because the **rules** must be written somewhere, especially to get specific messages for why access was denied. --- *Down-vote applied for total change of question.* – Andreas Aug 22 '18 at 21:29
  • 1
    @Andreas there was no mention, ever, that these were of the same type. – Simply_me Aug 22 '18 at 21:41
  • 1
    @Simply_me True. There was also no mention, anywhere in the original question, that these were of different types. --- Since original question had `car` and `bus`, it was a very obvious (though incorrect) assumption that they were both objects of some type of `Vehicle` class, *especially* since the log message said "Both **vehicles** use different energy source". --- All 3 answers made that assumption, so I was certainly not the only one misdirected by the original question. – Andreas Aug 22 '18 at 22:24
  • @Andreas I don't see how the question has "totally changed." If anything, I think the edits simply made the current problem a bit more easy for me to understand. Also, we all assumed that the objects were some type of `Vehicle`. It was our mistake to assume so. I really think you should remove your down vote; the question hasn't "totally changed," it simply became more specific, ruling out all our answers. It's good that the OP added clarification. – Kröw Aug 23 '18 at 01:25
  • @Simply_me Btw, I'd consider making an abstract class called `ParkingGarage` with an abstract method as so: `abstract boolean filter(Car car)`. This way, subclasses of `ParkingGarage` can decide for themselves, (in their `filter` method), whether or not a given car should be allowed in. If you'd like I can post an example as an answer. – Kröw Aug 23 '18 at 01:27
  • 1
    @Kröw Comparing two objects to see if they are "equal" (i.e. have same values for properties of same name), which is what original question appeared to do, vs. checking if one object is "permitted/allowed" to use another object, is an *entirely* different question. The code (as little as there is of it) may *look* very similar, but in the first case you can easily define general rules for automatic comparison of properties of same name and type, while such rules wouldn't seems appropriate for *access policy enforcement*. As such, question was "totally changed". – Andreas Aug 23 '18 at 18:52
  • 1
    @Simply_me If you have multiple problems you are trying to solve, IMO, you need to create one posting for each problem. In this case, object comparison is one problem. Access rules to the parking garage is another. Next time, apply problem decomposition and break down your problem into smaller problems and solve each one independently. Then synthesize your overall solution by integrating the solution of each of the solved problems. – hfontanez Aug 23 '18 at 18:52
  • @Simply_me [Here](https://stackoverflow.com/a/51977104/5921170)'s a reflective solution that works for objects of different, unrelated types. You won't have to write out each comparison. – Kröw Aug 23 '18 at 19:50
  • @Simply_me using reflection is also bad. I take that back, it is worse than comparing fields one by one. But don't take my word for it since Krow thinks I am making things up. Look it up yourself independently. – hfontanez Aug 23 '18 at 20:00

4 Answers4

1

You can reflectively iterate over every field in the objects. My example assumes that the two objects are of the same exact type, but I'm sure that you can improve it to compare two different objects along the same class hierarchy.

public class Vehicle {

    public int width, height;

    public Vehicle(int int1, int int2) {
        this.width = int1;
        this.height = int2;
    }

    public static void main(String[] args) {
        Vehicle vehicle1 = new Vehicle(5, 10), vehicle2 = new Vehicle(5, 10), vehicle3 = new Vehicle(5, 20);

        System.out.println(equal(vehicle1, vehicle2));
        System.out.println(equal(vehicle2, vehicle3));
        System.out.println(equal(vehicle1, vehicle3));
    }

    public static boolean equal(Vehicle first, Vehicle other) {
        for (Field f : Vehicle.class.getDeclaredFields()) {
            try {
                if (!f.get(first).equals(f.get(other)))
                    return false;
            } catch (IllegalArgumentException e) {
                // Method callers can only pass in Test objects to this method, so this
                // shouldn't be thrown either. Watch out for callers passing in null to this
                // method though.
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                // Fields are public and this method belongs to Test, so this shouldn't be
                // thrown.
                e.printStackTrace();
            }
        }
        return true;
    }

}

The main part of the above code is the static equal method. It compares each Field, declared in Vehicle, on each object and returns false if one is not equal. If you'd also like to compare fields inherited by Vehicle you can use getFields() instead of getDeclaredFields().

In short, the kind of code that I think you'd be looking for, is this:

for (Field f : Vehicle.class.getDeclaredFields())
    try {
        if (!f.get(first).equals(f.get(other)))
            return false;
    } catch (IllegalArgumentException | IllegalAccessException e) {
        e.printStackTrace();
    }
return true;

(Btw, I liked "an infinite sequence" better :) )

Kröw
  • 504
  • 2
  • 13
  • 31
  • :-) thank you for the quick answer. Unfortunately they are not the same objects but their properties do match 1:1. – Simply_me Aug 22 '18 at 20:57
  • @Simply_me If they have all the same properties, why are they different classes? That seems like a waste of code. – Andreas Aug 22 '18 at 21:27
  • I won't downvote your answer, but you should know that having a static `equals` method is a terrible idea. Instead, overriding `equals` and `hashCode` should've been suggested. – hfontanez Aug 22 '18 at 21:56
  • @hfontanez First of all, the method itself is irrelevant to my answer. What the method *does* is the important part. Put that code wherever you'd like it. :) Second, could you tell me why having such a method is bad? – Kröw Aug 23 '18 at 00:56
  • @Simply_me I don't know if you've looked into solving this with inheritance, so I'll suggest doing that. (That way comparing the values will work between two different classes.) I'm gonna try to update my answer to work for your problem though. – Kröw Aug 23 '18 at 00:59
  • @Kröw the OP asked "Is there a more intelligent to go about it?" Your solution is not more intelligent. See Andreas' solution so that you can understand what I am talking about. – hfontanez Aug 23 '18 at 17:05
  • @hfontanez Whether my answer is more intelligent or not is completely subjective. My answer allows the user to use the same code to compare the objects regardless of how many properties are in the object. The size of the comparison code is not increased or changed. My solution is *inordinately* more intelligent than simply comparing each property. On a side note, Andreas's answer was clear and very conventional, but it still involved adding in more code for each property in the objects. Also, you still haven't answered my question from my previous comment. – Kröw Aug 23 '18 at 18:12
  • @Kröw is not subjective at all. Here you have a class that has no unique behavior (since all methods are static) with variables. Also, throwing an exception simply because the instances being passed are not the same is bad. Exception should not be used for non-exceptional conditions. I can go on and on. Your solution is not close to be the best. Or, as the OP put lt no more "intelligent" solution than what he already had. – hfontanez Aug 23 '18 at 18:29
  • @hfontanez The fact that I think the answer is more intelligent and you don't, makes it subjective. Now I've told you this before but you're not acknowledging it: **The solution I gave was the content of the `equal` method** everything else is just to present the solution. Where did I throw an exception? Have you even looked at my code? An exception like that is simply propagated upwards since the caller used my method incorrectly. Look, repeating myself is not gonna add useful content to this post so please go back and actually read my answer as well as my previous comments, then we can talk. – Kröw Aug 23 '18 at 19:20
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/178638/discussion-between-krow-and-hfontanez). – Kröw Aug 23 '18 at 19:20
  • Can this be done via a generic method, taking in any kind of class? – FDM Mar 17 '22 at 19:23
0

Note: This answer was for original question, comparing two objects of same type, and doesn't apply to updated question that checks access rules.


To compare two objects with a lot of if statements, we'll use a Vehicle object with 3 fields as an example:

class Vehicle {
    private final String  type;
    private final int     wheelCount;
    private final boolean electric;
}

As you already know, you have to write setters (or constructor) and getters for each one, so you already have "repetitive work" to do, so just get comfy with it:

    public Vehicle(String type, int wheelCount, boolean electric) {
        this.type = type;
        this.wheelCount = wheelCount;
        this.electric = electric;
    }
    public String getType() {
        return this.type;
    }
    public int getWheelCount() {
        return this.wheelCount;
    }
    public boolean isElectric() {
        return this.electric;
    }

And the standard methods:

    @Override
    public int hashCode() {
        int hash = this.type.hashCode();
        hash = hash * 31 + this.wheelCount;
        hash = hash * 3 + (this.electric ? 1 : 2);
        return hash;
    }
    @Override
    public boolean equals(Object obj) {
        if (! (obj instanceof Vehicle))
            return false;
        Vehicle that = (Vehicle) obj;
        return (this.type.equals(that.type)
             && this.wheelCount == that.wheelCount
             && this.electric == that.electric);
    }

So now you can simple do:

if (! car.equals(bus)) {
    return 1;
}

And that's the one and only if statement.

Andreas
  • 154,647
  • 11
  • 152
  • 247
  • 1
    thank you for the detailed reply. If I read it correctly your equals method does all the if/else in the returns statement, so it's similar approach to what I currently have. I dont want to have a 2 mile long return statement either :-). – Simply_me Aug 22 '18 at 21:21
  • @Simply_me But only you know how each field should be compared, so you need to write the comparisons. – Andreas Aug 22 '18 at 21:25
  • +1 for making Vehicle immutable. However, since vehicle is probably not part of some vehicle pool, you can have vehicles that are similar and yet not the same. For this reason, implementing `Comparable` and overriding `equals` and `hashCode` is necessary. – hfontanez Aug 22 '18 at 21:46
  • 1
    @hfontanez I did implement `equals` and `hashCode`, but why would you *need* to implement `Comparable`? You can only do that, if you can apply an *ordering* to the objects. There may not be any kind of *natural order* to vehicles. – Andreas Aug 22 '18 at 22:31
  • @Andreas you can naturally order cars by wheel count or by type using your example. – hfontanez Aug 23 '18 at 17:03
  • Who downvoted Andreas answer? Obviously someone who doesn't know better. Even I who disagreed with Andreas on the use of `Comparable` interface, upvoted his answer. CLEARLY, it is a more "intelligent" solution than what the OP posted, AND made use of industry best practices: 1) Creating immutable objects, 2) Overriding `equals` and `hashCode` for object instance comparisons. – hfontanez Aug 23 '18 at 18:34
-1

The first thing you need to do is for your class to implement the Comparable Interface. If your class creates composite objects (objects containing other objects), each one of these objects must implement the comparable interface as well. The second thing you need to do is to override Object.equals(Object) and Object.hashCode() methods. Your example is creating a static equals method which I strongly recommend you don't do. Like I mentioned before for implementing Comparable, the classes for all object references must override equals and hashCode.

The comparable interface has a single method: compareTo(T t). The purpose of this method is to compare two objects in order to determine if the two objects are SIMILAR (but not the same). For example, you can say two vehicles are similar if they are the same make/model, manufactured the same year, have the same options, engine, wheels, etc., but they have different VIN numbers. Basically, the two vehicles LOOK the same, but are not the exact same car. Meanwhile, the purpose of the equals and hashCode method is to establish object instance equality.

Following the same example, lets say you call the police because your vehicle is stolen. The police gather all the information about your car and they will investigate all cars that matches the description of the vehicle (looks like your car). But, upon looking up the VIN number, they determine that the car IS NOT your car. So basically....

yourVehicle.compareTo(suspiciousVechile); // return true
yourVehicle.equals(suspiciousVehicle); // returns false

Although this is not recommended in the Comparable interface Javadoc, you can have cases where a true result from compareTo can yield a different result than equals. This is for cases where an object is a copy of another, but they are different instances. Sometimes this fact is important; although often it is not.

I would suggest you look into Java's String class to see how they make use of both (compareTo and equals) methods. Ignore the fact that equals doesn't call compareTo internally.

hfontanez
  • 5,774
  • 2
  • 25
  • 37
-1

For two arbitrary objects, you can reflectively iterate over the fields in one of the objects to see which fields the objects both have, 1:1. You can then compare these fields.

For the quick solution, check below.

Here's a really big and possibly annoying example:

class Test {

    public static Field getFieldSafely(String fieldName, Object object) {
        Field[] fields = object.getClass().getFields();
        for (Field f : fields)
            if (f.getName().equals(fieldName))
                return f;
        return null;
    }

    public static boolean equal(Object first, Object other) {
        for (Field f : first.getClass().getFields()) {
            String fieldName = f.getName();
            Field otherField = getFieldSafely(fieldName, other);
            if (otherField != null)
                try {
                    if (!f.get(first).equals(otherField.get(other)))
                        return false;
                } catch (IllegalArgumentException | IllegalAccessException e) {
                    e.printStackTrace();
                }
        }
        return true;
    }

    public static void main(String[] args) {
        Vehicle vehicle1 = new Vehicle(5, 10), vehicle2 = new Vehicle(5, 10), vehicle3 = new Vehicle(5, 20);
        Box box1 = new Box(5, 10), box2 = new Box(100, 200);

        System.out.println(equal(vehicle1, vehicle2));// True
        System.out.println(equal(vehicle2, vehicle3));// False
        System.out.println(equal(vehicle1, vehicle3));// False
        System.out.println(equal(box1, box2));// False
        System.out.println(equal(vehicle1, box1));// True
    }

}

class Box {
    public int width, height;

    public Box(int width, int height) {
        this.width = width;
        this.height = height;
    }

}

class Vehicle {

    public int width, height, weight;

    public Vehicle(int int1, int int2) {
        this.width = int1;
        this.height = int2;
    }

}

The most important methods are in the class Test. The method, equal(...), takes in two objects. It then checks if each field in the first object is contained in the second object. For each field it finds in the first object, that has the same name as a field in the second object, the fields are checked for equality. If all the fields found in both objects are equal, the method returns true. Otherwise, it returns false.

Here are the important methods. You can modify them as you see fit to meet your needs.

getFieldSafely(...) simply returns null if a field with the given name isn't found in the given object. Otherwise, it returns the field.

public static Field getFieldSafely(String fieldName, Object object) {
    Field[] fields = object.getClass().getFields();
    for (Field f : fields)
        if (f.getName().equals(fieldName))
            return f;
    return null;
}

public static boolean equal(Object first, Object other) {
    for (Field f : first.getClass().getFields()) {
        String fieldName = f.getName();
        Field otherField = getFieldSafely(fieldName, other);
        if (otherField != null)
            try {
                if (!f.get(first).equals(otherField.get(other)))
                    return false;
            } catch (IllegalArgumentException | IllegalAccessException e) {
                e.printStackTrace();
            }
    }
    return true;
}

Good luck. :)

Kröw
  • 504
  • 2
  • 13
  • 31
  • Also, this answer is prone to NullPointerException because neither or the two arguments being passed can be null and are not checked before being used. Overriding `Object.equals(Object)` is better because the first check it does (if done correctly) is an `instanceof`check which by nature, checks that the argument being passed is not null: `if (!(obj instanceof Vehicle)) {return false;}` In this check, null is not an instance of anything. And if it is not null and an instance of something else, it will fail the check just the same. One check instead of many. And that merits a down vote. – hfontanez Aug 23 '18 at 18:46
  • @hfontanez Again, you seem to be misunderstanding what I'm presenting as a solution. My solution is for the OP to reflectively compare the fields of each object. The code in my answer just shows how that can work. However the OP chooses to implement my solution, may give way to `NullPointerException`s. The method itself is just to present the answer, and it does that perfectly. Now, I implore you to stop downvoting peoples' answers for reasons as you did mine. Not only is it annoying, but it wrongly impacts the credibility of others and misguidedly ranks their solutions. – Kröw Aug 23 '18 at 19:27