Evaluating objects containing Strings with Java ArrayList contains() - java

I would like to do a deeper String check of Objects to be able to do the following:
List<MyObj> myList = new ArrayList<MyObj>() {{
add(new MyObj("hello"));
add(new MyObj("world"));
}};
System.out.println(myList.contains("hello")); // true
System.out.println(myList.contains("foo")); // false
System.out.println(myList.contains("world")); // true
But getting false on each one with the following full code
/* Name of the class has to be "Main" only if the class is public. */
class Ideone {
public static class MyObj {
private String str;
private int hashCode;
public MyObj(String str) {
this.str = str;
}
public #Override boolean equals(Object obj) {
System.out.println("MyObj.equals(Object)");
if (obj == this) {
return true;
}
if (obj instanceof String) {
String strObj = (String) obj;
return strObj.equals(str);
}
return false;
}
public #Override int hashCode() {
if (hashCode == 0) {
hashCode = 7;
for (int i = 0; i < str.length(); ++i) {
hashCode = hashCode * 31 + str.charAt(i);
}
}
return hashCode;
}
}
public static final MyObj obj1 = new MyObj("hello");
public static final MyObj obj2 = new MyObj("world");
public static void main (String[] args) throws java.lang.Exception {
List<MyObj> myList = new ArrayList<MyObj>() {{
add(obj1);
add(obj2);
}};
System.out.println(myList.contains("hello"));
System.out.println(myList.contains("foo"));
System.out.println(myList.contains("world"));
}
}
If I'm right the List Object should use equals() and hashCode() to evaluate containing Objects.
So I #Override them and check their Strings additionally.
But it never gets into equals() as there's no output MyObj.equals(Object).

java.util.ArrayList#indexOf is used internally in ArrayList for contains().
There is a check,
o.equals(elementData[i])
So there is comparison of string with your object, so String.equals() is invoked for check of equality.

You are not fulfilling the equals contract at all:
The equals method implements an equivalence relation on non-null object references:
It is reflexive: for any non-null reference value x, x.equals(x) should return true. Yours is not reflexive.
It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true. Yours is not symmetric.
It is transitive: for any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true. Yours is not transitive
It is consistent: for any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.
For any non-null reference value x, x.equals(null) should return false.
So without respecting the contract for the method you can't expect intended behavior.
Just for example, what guarantees you that equals is going to be called on the object contained in the ArrayList and not in the other way, eg "hello".equals(new MyObj("hello")). You have no guarantees about it but since equals is normally supposed to be symmetric than you shouldn't mind.

As pointed out by others, the problem is that your equals method is never called. When you invoke myList.contains("hello"), ArrayList checks whether "hello".equals(<MyObj>), not the other way around.
Instead, I recommend implementing your equals method properly, so that two MyObject instances with equal value are considered equal, and then create a helper method like this:
public boolean myContains(List<MyObj> list, String value) {
return list.contains(new MyObj(value));
}

List<MyObj> myList = new ArrayList<MyObj>()
is a list of MyObj, so you need to use MyObj while checking myList.contains:
System.out.println(myList.contains(new MyObj("hello")));
System.out.println(myList.contains(new MyObj("foo")));
System.out.println(myList.contains(new MyObj("world")));

You're asking the List to compare a String to a MyObj... They are never going to be "equal". Try a map instead:
Map<String, MyObj> map = new HashMap<String, MyObj>() {{
put("hello", new MyObj("hello"));
put("world", new MyObj("world"));
}};
Then you can use:
if (map.containsKey("hello"))
and to get the corresponding object:
MyObj o = map.get("hello"); // get() returns null if key not found

It is not equals of your object called when contains executes but the one from String class.
And String implementation checks with instanceof whether the class is a String to perform String-like operations to determine the answer. If object is not a String it will return false;

Related

ArrayList contains method not work as I would expect? [duplicate]

This question already has answers here:
Using an instance of an object as a key in hashmap, and then access it with exactly new object? [duplicate]
(3 answers)
Closed 5 years ago.
ArrayList use equals() in its contains method to see if provide object equals any item in the list as the document say:
Returns true if this list contains the specified element. More
formally, returns true if and only if this list contains at least one
element e such that (o==null ? e==null : o.equals(e)). see this
I have this class
class Foo
{
private String value;
public Foo(String value)
{
this.value = value;
}
#Override
public boolean equals(Object o)
{
return o == null ? this.value == null : o.toString().equals(this.value);
}
}
I want to use contains method to check if item exists like this
List<Foo> list = new ArrayList<Foo>();
Foo item1 = new Foo("item1");
Foo item2 = new Foo("item2");
list.add(item1);
list.add(item2);
System.out.println(item1.equals("item1")); //return true
System.out.println(list.contains("item1")); //false !! why?!
but contains method return false , while item1.equals("item1") return true.
why contains return false when it use equals method for provided object
Your equals() implementation violates the symmetric principle :
It is symmetric: for any non-null reference values x and y,
x.equals(y) should return true if and only if y.equals(x) returns
true.
item1.equals("item1") returns true
while
"item1".equals(item1) returns false.
So you should not expect that Collection method such as contains() works in a consistent way.
As a general rule, equals() overriding should not try to interoperate with other classes but only with instances of the underlying class.
In your case, it works only for String parameter passed to the method.
You should rather make it working for Foo instances parameter:
#Override
public boolean equals(Object o) {
if (!(o instanceof Foo){
return false;
}
Foo other = (Foo) o;
return Objects.equals(other.value, this.value);
}
Your problem is that your equality is not symmetric. Foo == String does not imply String == Foo.
If you look at the implementation of ArrayList.contains you'll see that it calls objectToFind.equals(objectInList), which may be contrary to what you were expecting:
o.equals(elementData[i])
So in your case that's String.equals(Foo). Because String.equals will return false for anything that's not a String, ArrayList.contains returns false.
Looking at the docs here the contains will use the equals method on the string "item1" and not the equals method on item1. e.g.
"item1".equals(item1)
and not
item1.equals("item1")
You could use list.contains(new Foo("item1")) instead.
While item1.equals("item1") is true, "item1".equals(item1) will be false. Having a non-symmetric equals relation causes a lot of confusion.
In general, you want equals to only be true among classes you control (usually only the exact class you're comparing), so that you can ensure that the relation is symmetric. (And to avoid further confusion, always define hashCode whenever you define equals.)
Your equals implementation is clearly incorrect.
You should read chapter 3 of Joshua Bloch's "Effective Java" to learn how to override equals and hashcode correctly.
This will work better:
/**
* Demo equals override
* User: mduffy
* Date: 8/10/2017
* Time: 10:45 AM
* #link https://stackoverflow.com/questions/45616794/arraylist-contains-method-not-work-as-i-would-except
*/
public class Foo {
private final String value;
public Foo(String value) {
this.value = value;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Foo)) return false;
Foo foo = (Foo) o;
return value != null ? value.equals(foo.value) : foo.value == null;
}
#Override
public int hashCode() {
return value != null ? value.hashCode() : 0;
}
#Override
public String toString() {
final StringBuilder sb = new StringBuilder("Foo{");
sb.append("value='").append(value).append('\'');
sb.append('}');
return sb.toString();
}
}
YOUR Equals method is not considering the instance of the Object, so is wrongly implemented
you have a list of Foos and you want to know if the list contains a STRING
this:
System.out.println(list.contains("item1"));
is not the same as
System.out.println(list.contains(new Foo("item1")));
because
new Foo("item1") never ever will return true on equals to the string "item1"
edit:
contains in the ArrayList is implemented as
#Override
public int indexOf(Object o) {
E[] a = this.a;
if (o == null) {
for (int i = 0; i < a.length; i++)
if (a[i] == null)
return i;
} else {
for (int i = 0; i < a.length; i++)
if (o.equals(a[i]))
return i;
}
return -1;
}
so this part is the important one
for (int i = 0; i < a.length; i++)
if (o.equals(a[i]))
return i;
as you can see
o.equals(a[i])
means
String.equals(Foo)
which is NOT the same as what you did:
Foo.equals(String)
you are breaking a rule of symmetry, that is the reason why is not working!
the problem is in your equals override: you are comparing o.toString() vs this.value.
to get it working this are your options:
Override the toString method on your Foo class to return value
Change the last part to this.value.equals(o.getValue()) // have to create the getter

When do we say Object1 .equals(Object2) is true?

I have below code written:
public class Test{
int a;
public static void main(String[] args){
Test t1 = new Test();
Test t2 = new Test();
if(!t1.equals(t2))
System.out.println("They're not equal");
if(t1 instanceof Test)
System.out.println("True");
}
}
And here is the Output:
They're not equal
True
I even tried to assign the same value to instance variable 'a' of both these objects, like below,
t1.a = 10;
t2.a = 10;
Still the same output.
May I know when the t1.equals(t2) will return True?
How does the equals() method work on objects?
By default, calling equals execute the equals method of Object class, which returns true only when you are comparing an instance to itself.
You can override this method in other classes, so that equals would return true when the properties of both objects are equal.
For example :
#Override
public boolean equals(Object other)
{
if (other == null)
return false;
if (other instance of Test) {
Test t = (test) other;
return this.a == t.a;
}
return false;
}
Adding this method to your Test class would change the result of t1.equals(t2) to true.
The Object.equals(Object) Javadoc says (in part),
Indicates whether some other object is "equal to" this one.
The equals method implements an equivalence relation on non-null object references:
It is reflexive: for any non-null reference value x, x.equals(x) should return true.
It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
It is transitive: for any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
It is consistent: for any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.
For any non-null reference value x, x.equals(null) should return false.
For your code you could override equals like
#Override
public boolean equals(Object o) {
if (o instanceof Test) {
return ((Test) o).a == a;
}
return false;
}
The default implementation of Object.equals treats two objects as equal only if they are exactly the same object, not just the same contents but the same reference.
Objects can have different implementations of equals, but you must program them explicitly: if you want to check that all fields are equal, you must actually write an equals implementation that checks that.

Check if list of objects contains a String

I have the below class with the fields name and id -
public class MyObj {
private String name;
private String id;
//getters and setters + toString implementation
}
In my main method, I am creating a list of MyObjand adding elements to the list.
public static void main(String[] args) {
List<MyObj> myList = new ArrayList<MyObj>();
MyObj myObj1 = new MyObj();
myObj1.setName("test");
myObj1.setId("123");
myList.add(myObj1);
MyObj myObj2 = new MyObj();
myObj2.setName("testOne");
myObj2.setId("456");
myList.add(myObj2);
}
When I iterate through the list, and print the contents, I have this -
MyObj [name=test, id=123]
MyObj [name=testOne, id=456]
I want to check if my List has a string. For example, if it contains name or id? When I use the contains method, it returns false. Clearly I am doing something wrong. How do I check if a list of objects has a string?
You might want something like Java8 Stream and filter, but if I understand your question (and the answers and comments) you want to get all the MyObj matching a criteria?
In that case, equals is not the way to do it. eg:
MyObj a = ...
a.setName("A");
a.equals("A"); // returns true
"A".equals(a); // returns false!
The javadoc of equals says that:
The equals method implements an equivalence relation on non-null
object references:
It is reflexive: for any non-null reference value x, x.equals(x) should return true.
It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
It is transitive: for any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
It is consistent: for any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or
consistently return false, provided no information used in equals
comparisons on the objects is modified.
For any non-null reference value x, x.equals(null) should return false.
And in that case, you have a non symmetric equals (even if javadocs says it should be rather than it must be).
You can use filter, or better Java 8's Stream.
In pre Java 8, for loops, or external library such as Guava is the way to go.
The "simple" use case is the following:
List<MyObj> matching = new ArrayList<>();
for (MyObj o : list) {
if (null != o.getName() || null != o.getId()) {
matching.add(o);
}
}
This says that you have either a name defined (eg: not null), either an id.
If you need to go further, with advanced criteria, you can do that:
interface Predicate<T> {
boolean test(#Nullable T value);
}
public class Lists {
static <T> List<T> filter(List<? extends T> list, Predicate<T> predicate) {
List<MyObj> matching = new ArrayList<>();
for (MyObj o : list) {
if (predicate.test(o)) {
matching.add(o);
}
}
return matching;
}
}
And an example:
Lists.filter(listMyObj, new Predicate<MyObj>() {
public boolean test(MyObj o) {
return null != o.getName() || null != o.getId();
}
});
But you should use Guava since it does the same, and better (my example is more or less what Guava does).
As for Java 8:
myList.stream()
.filter(o -> null != o.getName() || null != o.getId())
.collect(Collectors.toList())
And I think you can do better using MyObj::getName/getId and some Function wrapper, to do the "isNull" test.
You need to implement equals method in your MyObj

HashSet contains duplicate entries

A HashSet only stores values ones, when the equals method says that they're the same. Thats what I thought.
But now i'm adding Elements to a HashSet where the equals method returns true and the size of the set still grows?? sorry I'm confused. Some hints where i'm wrong would be nice.
Element t1 = new Element(false, false, false, false);
Element t2 = new Element(true, true, true, true);
Element t3 = new Element(false, false, false, false);
if (t1.equals(t3))
System.out.println("they're equal");
Set<Element> set = new HashSet<>();
set.add(t1);
set.add(t2);
set.add(t3);
System.out.println("set size: " + set.size());
so in this example my console output is:
they're equal
set size: 3
That makes no sense to me.. shouldn the size be 2?
The problem is that your Element class has not overridden the equals and hashCode methods or these implementations are broken.
From Object#equals method javadoc:
The equals method implements an equivalence relation on non-null object references:
It is reflexive: for any non-null reference value x, x.equals(x) should return true.
It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns true.
It is transitive: for any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then x.equals(z) should return true.
It is consistent: for any non-null reference values x and y, multiple invocations of -x.equals(y) consistently return true or consistently return false, provided no information used in equals comparisons on the objects is modified.
For any non-null reference value x, x.equals(null) should return false.
From Object#hashCode method javadoc:
The general contract of hashCode is:
Whenever it is invoked on the same object more than once during an execution of a Java application, the hashCode method must consistently return the same integer, provided no information used in equals comparisons on the object is modified. This integer need not remain consistent from one execution of an application to another execution of the same application.
If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.
It is not required that if two objects are unequal according to the equals(java.lang.Object) method, then calling the hashCode method on each of the two objects must produce distinct integer results. However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.
Make sure the implementations of these methods satisfy these rules and your Set (backed by a HashSet) will work as expected.
Your objects have different hashes so HashSet "puts" then in different "buckets".
If you have your own model classes you need to change some basic functions work like done in the below example.
Execution code :
HashSet<MyModel> models = new HashSet<MyModel>();
for (int i = 1; i < 5; i++)
models.add(new MyModel(i + "", "Name :" + i + ""));
for (int i = 3; i < 5; i++)
models.add(new MyModel(i + "", "Name :" + i + ""));
for (Object object : models)
System.out.println(object);
Model Class :
/**
* Created by Arun
*/
public static class MyModel {
private String id = "";
private String name = "";
public MyModel(String id, String name) {
this.id = id;
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#Override
public String toString() {
return getId();
}
#Override
public boolean equals(Object obj) {
return !super.equals(obj);
}
public int hashCode() {
return getId().hashCode();
}
}
Hope this helps.
Yes We Can implement it with the object of the classes which are not FINAL.
HashSet Checks for two methods hashCode() and equals() before adding any Object.
First it checks for the method hashCode(),if it returns the hashcode which is same with any of the object in Set, then it checks for the equals method for that object,which internally compares the references for both objects i.e this.obj1==obj.If these are the same references in that case it returns true means it is a duplicate value.
We can add duplicate non-final objects by overriding HashCode and equals method.
In HashCode() you can return same hashcode in case of same parameters.
See example:
public class Product {
int i;
Product(int a)
{
this.i=a;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + i;
return result;
}
#Override
public boolean equals(Object obj) {
/*if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Product other = (Product) obj;
if (i != other.i)
return false;
return true;*/
return true;
}
}
`
`
import java.util.HashSet;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Product p1=new Product(1);
Product p2=new Product(1);
Product p3=new Product(1);
Set s=new HashSet();
s.add(p1);
s.add(p2);
s.add(p3);
System.out.println(s.size());
}
}
The output will be 1.
P.S:Without overriding these methods,output will be 3 since it will use their default behavior.

Java: equals and ==

Lets see we have 2 references to instances of a user-defined class, a and b in Java.
Can there ever be a situation where
a == b but a.equals(b) return false?
Sure! The implementation of .equals() is completely up to the class, so I could write:
class Foo
public boolean equals(Object other) {
return false;
}
}
Now it doesn't matter what two instances you pass — even the exact same instance twice — I'm always going to say they're not equal.
This particularly setup is silly, but it illustrates that you can get a false result from .equals() for the same object twice.
Note that we're talking here about what can happen, not what should. No class should ever implement a .equals method that claims an object isn't equal to itself. For trusted code, it's reasonable to assume this will never happen.
if a == b then a.equals(b) should be true. And if a.equals(b) then maybe a == b but not necessarily.
The == operator just test if both are referencing the same object. While equals executes a logic that you implemented. The last one can be overridden the first one is an operator from the language, and such as cannot be overridden in Java.
References
what is the difference between == operator and equals()? (with hashcode() ???)
From java.lang.Object documentation:
The equals method implements an equivalence relation on non-null
object references:
It is reflexive: for any non-null reference value x, x.equals(x) should return true.
It is symmetric: for any non-null reference values x and y, x.equals(y) should return true if and only if y.equals(x) returns
true.
It is transitive: for any non-null reference values x, y, and z, if x.equals(y) returns true and y.equals(z) returns true, then
x.equals(z) should return true.
It is consistent: for any non-null reference values x and y, multiple invocations of x.equals(y) consistently return true or
consistently return false, provided no information used in equals
comparisons on the objects is modified.
For any non-null reference value x, x.equals(null) should return false.
Yes, just overload equals to do something silly. e.g.
class Foo {
#Override
boolean equals(Object rhs) { return false; }
}
It is obviously possible to write code that does this, as other answers have pointed out.
However, it is also always a logical error in the code, since it violates the implicit general contract of the equals() function.
An object should always be equal to itself, so if (a==b) then a.equals(b) should always return true.
Ya we can overload the .equals function to give the desired output. but there is no case where == returns true while .equals returns false.
class s {
int a;
}
class dev {
public static void main(String args[]) {
s a = new s();
s b = new s();
System.out.println(a == b);
System.out.println(a.equals(b));
}
}
Output
false
false
Yes, easily:
public class Wrong{
public boolean equals(Object other)
{
return false;
}
}
Of course, this completely breaks the normal rules for implementing .equals() - see the Javadocs - but there's nothing to stop you other than good sense.
The equals method can be overridden in order to provide custom functionality other than the typical == method. Also, some types such as Strings will not return true when using the == method. You have to use .equals or .compareTo (equal when returns 0).
This should provide some additional help
package com.stackOverFlow;
import java.util.Date;
public class SampleClass {
public static int myint = 1;
private SampleClass() {
};
public Integer returnRandom() {
return myint++;
}
private static SampleClass _sampleClass;
public static SampleClass getInstance() {
if (_sampleClass == null) {
_sampleClass = new SampleClass();
}
return _sampleClass;
}
#Override
public boolean equals(Object other) {
if (this.returnRandom().equals(((SampleClass) other).returnRandom())) {
return true;
}
return false;
}
}
package com.stackOverFlow;
public class Run {
/**
* #param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
SampleClass a = SampleClass.getInstance();
SampleClass b = SampleClass.getInstance();
if(a==b){
System.out.println("references are same as they are pointing to same object");
if(!a.equals(b)){
System.out.println("two random number Instance will never be same");
}
}
}
}
You can understand this by Practical example. where a singeleton class will always return you same reference, and you can override equals method to get something that is not class variable. example a value from database at two particular time , or a random number. Hope that clears your case

Categories

Resources