JSONObject.similar(JSONObject) what does this really compare? - java

I was looking for a way to compare two JSONObjects besides using
JSONObject.toString().equals(JSONObject.toString())
and came across this similar method but it's really vague about what it compares.
"Determine if two JSONObjects are similar. They must contain the same set of names which must be associated with similar values." Link
What is that supposed to mean? How similar? Exactly the same?
I searched really hard and couldn't find anything that could clear it up.

Here's the source for JSONObject.similar():
/**
* Determine if two JSONObjects are similar.
* They must contain the same set of names which must be associated with
* similar values.
*
* #param other The other JSONObject
* #return true if they are equal
*/
public boolean similar(Object other) {
try {
if (!(other instanceof JSONObject)) {
return false;
}
if (!this.keySet().equals(((JSONObject)other).keySet())) {
return false;
}
for (final Entry<String,?> entry : this.entrySet()) {
String name = entry.getKey();
Object valueThis = entry.getValue();
Object valueOther = ((JSONObject)other).get(name);
if(valueThis == valueOther) {
continue;
}
if(valueThis == null) {
return false;
}
if (valueThis instanceof JSONObject) {
if (!((JSONObject)valueThis).similar(valueOther)) {
return false;
}
} else if (valueThis instanceof JSONArray) {
if (!((JSONArray)valueThis).similar(valueOther)) {
return false;
}
} else if (valueThis instanceof Number && valueOther instanceof Number) {
if (!isNumberSimilar((Number)valueThis, (Number)valueOther)) {
return false;
};
} else if (!valueThis.equals(valueOther)) {
return false;
}
}
return true;
} catch (Throwable exception) {
return false;
}
}
Essentially similar() recursively compares the names and values of the JSONObjects and returns true if they're the same.
First, it checks if the keySets are equal, then moves on to the values.
It recursively checks each value in the keySet to see if they are equal in each JSONObject. If not, it returns false.

The description you found relates to method JSONObject.similar(), which compares if two JSON objects are the same, but having perhaps a different order of its attributes.
The equals() will compare each string caracter, one-by-one checking if it is the same, having the same order.

Related

Comparing An Entry In A Map With An Object

I have a Map in Java like so,
private HashMap<String, Object[][]> theMap;
Where the key is a String and the entry is going to be something along the line of,
theMap = new HashMap<>();
Object[][] theData = {
{Boolean.FALSE, "Text"}
};
theMap.put("Key1", theData);
Somewhere along the line I would like to check if an entry in the map is equivalent to another object. Currently I am doing it like this,
Object[][] tempData = {
{Boolean.FALSE, "Text"}
};
for(Object key: entries.keySet()) {
if(entries.get(key).equals(tempData)) {
entries.remove(key);
}
}
And it is not working.
I would prefer the comparison to be done with an object rather than with another map. I'm wondering what I'm doing wrong with this comparison here?
The reason you are not getting equality is that arrays inherit Object#equals() which is based on identity, not equality of contents. You could consider using java.util.Arrays.deepEquals(Object[], Object[]) to compare.
That is the answer to the immediate question. However, using a 2-dimensional array of Object to hold a boolean and a String is really bad code smell and indicates you need to encapsulate what you are putting in the array.
Identity vs Equivalence
Please make sure that you understand that by default the equals() method of Object checks on whether two object references are referring to the same object (identity), which is not what your code is checking.
Instead, your code is checking whether the two objects (the values you put on the map) are having the same value (equivalence).
Here are two articles about this topic:
What is the difference between identity and equality in OOP?
Overriding equals method in Java
In this particular problem of yours, I think the solution involves two steps:
Your tempData and theData does not seems to be an array
of elements of the same type (it does not appear to be a 2-dimensional
array either). Instead, it contains a Boolean value and then a
String value. In this case, I think you really should think
through what this thingy is and design a class for it (I am showing
an example below)
The class should override the equals() (and hashCode()) methods
so that you can use its equals() for equivalence checking.
Note also that your IDE (e.g. Eclipse) probably can generate a template for equals() and hashCode() for you.
Example: (here I assume your Boolean represents a condition, and your String represents a message)
class MyRecord {
private Boolean condition;
private String message;
public Boolean getCondition() {
return condition;
}
public void setCondition(Boolean condition) {
this.condition = condition;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result
+ ((condition == null) ? 0 : condition.hashCode());
result = prime * result
+ ((message == null) ? 0 : message.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
MyRecord other = (MyRecord) obj;
if (condition == null) {
if (other.condition != null)
return false;
} else if (!condition.equals(other.condition))
return false;
if (message == null) {
if (other.message != null)
return false;
} else if (!message.equals(other.message))
return false;
return true;
}
}

TreeSet does not properly add distinct data

I am trying to test a class for a test-assignment poker-game in which it is only important to determine the validity or value of particular hands.
My PokerHand object contains a TreeSet<Card>. I thought this would be an ideal data-structure since doubles are not allowed, and it automagically sorts it with red-black tree algorithm.
The problem however, is that it appears to have some side-effects that I am not yet aware of. I understand that doubles will not be added to a TreeSet, but in my tests I make sure not to. Instead I noticed that it will not add new Card objects to the TreeSet as soon as the number fields are equal, but not the type.
This is the equals method for a Card
#Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Card other = (Card) obj;
return this.type == other.type && this.number == other.number;
}
This is the test, adding various cards...
#Test
public void testOnePair() {
hand.addCard(new Card(3, Card.CARD_TYPE.SPADES));
hand.addCard(new Card(8, Card.CARD_TYPE.CLUBS));
hand.addCard(new Card(10, Card.CARD_TYPE.HEARTS));
hand.addCard(new Card(14, Card.CARD_TYPE.SPADES));
hand.addCard(new Card(14, Card.CARD_TYPE.CLUBS));
assertEquals("One Pair", this.hand.getValue());
}
What appears to be happening is that the last Card is not added, so the size of the TreeSet effectively remains 4, even though the cards are clearly distinct. It does not even consult the equals method.
It does however reach the compareTo method.
#Override
public int compareTo(Object t) {
if (t.getClass().equals(this.getClass())) {
Card otherCard = (Card)t;
if (otherCard.equals(this)) {
return 0;
}
return this.number - otherCard.number;
}
else {
throw new ClassCastException("Cannot convert " + t.getClass().toString() + " to Card");
}
}
It has been a while since I've gotten back into Java 8 and maybe I'm just clearly overseeing something. I hope somebody can help me forward with this.
I've always been reluctant to ask questions here. Solved this as soon as I submitted it... Wanted to share this with you. TreeSet only cares about the compareTo method. So I changed it to be the following.
#Override
public int compareTo(Object t) {
if (t.getClass().equals(this.getClass())) {
Card otherCard = (Card)t;
if (this.number == otherCard.number) return this.type.compareTo(otherCard.type);
return this.number - otherCard.number;
}
else {
throw new ClassCastException("Cannot convert " + t.getClass().toString() + " to Card");
}
}
This solved it, because now the comparable contract is "aware" of the type properties.

Suspicious call to Collection.contains method in ArrayList

I am getting a warning that watchStore.contains(s) is a suspicious call to java.util.Collection#contains. How can I fix it? I want to use contains() to find a particular object with the matching serial number.
public Watch findWatchBySerialNumber(long srch) {
long s = srch;
Watch watch = null;
for(int i = 0; i < watchStore.size(); i++) {
watch = watchStore.get(i);
if(watchStore.contains(s)) {
System.out.print("item found");
return watch;
}
}
System.out.print("item not found");
return null; // watch is not found.
}
Presuming that Watch is the class, watchStore is a List<Watch>, and that a field serialNo exists on Watch...
public Optional<Watch> findWatchBySerialNumber(long serial) {
return watchStore.stream()
.filter(w -> w.getSerialNo() == serial)
.findFirst();
}
If you're not using Java 8, the code is close, but a bit more dangerous since you have the chance to return null. If you can use Guava's Optional, that'd be a better choice here.
public Watch findWatchBySerialNumber(long serial) {
for(Watch w : watchStore) {
if(w.getSerialNo() == serial) {
return w;
}
}
return null;
}
Your contains isn't going to work since your list doesn't contain Longs, it contains Watchs. This is also why the compiler sees it as dubious; contains accepts an Object but it will return false if what you're looking for doesn't have a comparable equals for what's in your list.
You have to iterate over the entirety of your collection to find it in this scenario, especially since you're looking for a specific property on those objects as opposed to a specific, easy-to-provide value.
please how can I fix that. I want to use the contain() to find a
particular object with the matching serial number.
In that case override Watch's equals() to use serialNumber field for comparison.
Then add constructor that accepts serialNumber.
public class Watch {
private final long serialNumber;
public Watch(long serialNumber) {
this.serialNumber = serialNumber;
}
#Override
public boolean equals(Object obj) {
return obj == this ||
(obj instanceof Watch && ((Watch)obj).serialNumber == serialNumber);
}
#Override
public int hashCode() {
return (int)serialNumber;
}
}
Replace if(watchStore.contains(s)){ with if(watchStore.contains(watchToFind)){ where Watch watchToFind = new Watch(s);
you can use contains method from org.apache.commons.lang.ArrayUtils package.
Checks if the value is in the given array.
The method returns false if a null array is passed in.
Parameters:
array the array to search through
valueToFind the value to find
Returns:
true if the array contains the object
long [] imageHashes= {12l,13l,14l,15l};
System.out.println(ArrayUtils.contains(imageHashes, 13l));

Not sure how to complete this equals method

Could someone help me with this question please? I've tried looking up other examples of this to find what I need to do and keep running into something called and EqualsBuilder, is that what I need to use? Do I need to have it call on equals again if it satisfies neither of the IFs?
The following code contains a class definition and an incomplete method definition. The equals method is used to compare Buildings.
It is intended to return true if Buildings have the same names and number of floors (but are not necessarily the same Building) and false otherwise.
public class Building {
private String name;
private int noOfFloors;
public boolean equals (Object rhs) {
if (this == rhs) {
return true;
}
if (!(rhs instanceof Building)) {
return false;
}
Building b = (Building) rhs;
// missing return statement
}
}
public boolean equals (Object rhs) {
if (this == rhs) {
return true;
}
if (!(rhs instanceof Building)) {
return false;
}
Building b = (Building) rhs;
// This is what you're supposed to add. It will return true only if both
// object's attributes (name and number of floors) are the same
return this.name.equals(b.name) && this.noOfFloors == b.noOfFloors;
}
The only thing that you have to test for now is the fields of both objects. If they are equal, then you should return true, if at least one of them is not then you should return false.
Since your fields in that case are int and Stringyou can use == for the integer field and .equals() for the String field.
Something like this should do the job just fine:
if(this.name.equals(b.name) && this.noOfFloors == b.noOfFloors){
return true ;
}
else{
return false;
}
After the instanceOf tests you want to compare the fields of the object to the other object. Something like Objects.deepEquals() should do the trick for you nicely.

contains giving faulty results

I have a class 'CoAutoria' that's suposed to hold 2 instances of an 'Author' class (which only has a name, for now) and the number of articles those authors have in common.
In order to figure out the top 10 of co-authors (regarding number of articles) I created a TreeSet of 'CoAutoria', to hold the total of articles, for each pair.
I need to cycle through a Map of years, gather the different authors and their respective Set of co-Authors. Then, for each pair, create an instance of 'CoAutoria' and: add it to the treeset (if it doesn't already exists); or simply sum its number of articles to the one existing on the set.
I already created the compareTo method, to insert it on the treeset, and created the equals method so that the order of the authors doesn't matter.
Here's the main code:`
public class CoAutoria implements Comparable<CoAutoria>
{
private Autor autor1;
private Autor autor2;
private int artigosComum;
(...)
}
#Override
public int compareTo(CoAutoria a2)
{
String thisAutor1 = autor1.getNome();
String thisAutor2 = autor2.getNome();
String caAutor1 = a2.getAutor1().getNome();
String caAutor2 = a2.getAutor2().getNome();
if((autor1.equals(a2.getAutor1()) && autor2.equals(a2.getAutor2())) || (autor1.equals(a2.getAutor2()) && autor2.equals(a2.getAutor1())))
{
return 0;
}
else
{
return 1;
}
}
#Override
public boolean equals(Object o)
{
if(this == o)
{
return true;
}
if( o == null || o.getClass() != this.getClass())
return false;
CoAutoria ca = (CoAutoria) o;
String thisAutor1 = autor1.getNome();
String thisAutor2 = autor2.getNome();
String caAutor1 = ca.getAutor1().getNome();
String caAutor2 = ca.getAutor2().getNome();
if((thisAutor1.equals(caAutor1) && thisAutor2.equals(caAutor2)) || (thisAutor1.equals(caAutor2) && thisAutor2.equals(caAutor1)))
{
return true;
}
else
{
return false;
}
}
The main problem is: When I check if the set already has a certain instance of 'CoAutoria', (I'm using the contains() method of TreeSet), it gives me faulty results...sometimes it checks correctly that the Pair A-B already exists in that set (on the form of B-A), but sometimes it doesn't... For what I've read, the contains uses the equals method, so that's not suposed to happen..right?
[EDIT:]
Since the first post I started to think that maybe the problem resided on the compareTo..So I changed it to
public int compareTo(CoAutoria a2)
{
String thisAutor1 = autor1.getNome();
String thisAutor2 = autor2.getNome();
String caAutor1 = a2.getAutor1().getNome();
String caAutor2 = a2.getAutor2().getNome();
if(this.equals(a2))
{
System.out.println("return 0");
return 0;
}
else
{
int aux = thisAutor1.compareTo(caAutor1);
if(aux != 0)
{
return aux;
}
else
{
return thisAutor2.compareTo(caAutor2);
}
}
}
But it still gives my bad results..I thought I'd figured it now: if it's the same 'CoAutoria', I return 0, if not I go through the names, and order it by their compareTo values..but something's missing
Your contains method is breaking, because your compareTo method is always returning 0 or positive, no negatives. This means your compareTo is inconsistent. A correct implementation should return 0 if the authors are the same, or positive and negative values when the authors are different.
Example (assuming author1 is different than author2):
int i = author1.compareTo(author2); // i should be positive or negative
int j = author2.compareTo(author1); // j should be the opposite of i
Yours will return 1 for both of the above cases, which will make ordered Collections not work as no element is ever smaller. As another example imagine if you had a Binary Tree(an ordered collection) that had the elements [1-10]. If you were searching for the element 5, your binary tree when comparing 5 against any element would always say that it was equal or greater.
How exactly you should change it is up to you. But an idea would be to sort the authors by name, then iterate over both collections and compare the authors together lexicographically.
EDIT: Even after your edit to your methods they are still not consistent. Try the following, they aren't the most efficient but should work unless you really want to optimize for speed. Notice they first sort to make sure author1 and author2 are in order before they are compared with the other CoAutor which is also sorted. I don't do any null checking and assume both are valid authors.
#Override
public boolean equals(Object o){
if (o == null || !(o instanceof CoAutoria)) return false;
if (o == this) return true;
return this.compareTo((CoAutoria)o) == 0;
}
#Override
public int compareTo(CoAutoria o) {
List<String> authors1 = Arrays.asList(autor1.getNome(), autor2.getNome());
List<String> authors2 = Arrays.asList(o.autor1.getNome(), o.autor2.getNome());
Collections.sort(authors1);
Collections.sort(authors2);
for (int i=0;i<authors1.size();i++){
int compare = authors1.get(i).compareTo(authors2.get(i));
if (compare != 0)
return compare;
}
return 0;
}

Categories

Resources