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
Related
Say I create one object and add it to my ArrayList. If I then create another object with exactly the same constructor input, will the contains() method evaluate the two objects to be the same? Assume the constructor doesn't do anything funny with the input, and the variables stored in both objects are identical.
ArrayList<Thing> basket = new ArrayList<Thing>();
Thing thing = new Thing(100);
basket.add(thing);
Thing another = new Thing(100);
basket.contains(another); // true or false?
class Thing {
public int value;
public Thing (int x) {
value = x;
}
equals (Thing x) {
if (x.value == value) return true;
return false;
}
}
Is this how the class should be implemented to have contains() return true?
ArrayList implements the List Interface.
If you look at the Javadoc for List at the contains method you will see that it uses the equals() method to evaluate if two objects are the same.
I think that right implementations should be
public class Thing
{
public int value;
public Thing (int x)
{
this.value = x;
}
#Override
public boolean equals(Object object)
{
boolean sameSame = false;
if (object != null && object instanceof Thing)
{
sameSame = this.value == ((Thing) object).value;
}
return sameSame;
}
}
The ArrayList uses the equals method implemented in the class (your case Thing class) to do the equals comparison.
Generally you should also override hashCode() each time you override equals(), even if just for the performance boost. HashCode() decides which 'bucket' your object gets sorted into when doing a comparison, so any two objects which equal() evaluates to true should return the same hashCode value(). I cannot remember the default behavior of hashCode() (if it returns 0 then your code should work but slowly, but if it returns the address then your code will fail). I do remember a bunch of times when my code failed because I forgot to override hashCode() though. :)
It uses the equals method on the objects. So unless Thing overrides equals and uses the variables stored in the objects for comparison, it will not return true on the contains() method.
class Thing {
public int value;
public Thing (int x) {
value = x;
}
equals (Thing x) {
if (x.value == value) return true;
return false;
}
}
You must write:
class Thing {
public int value;
public Thing (int x) {
value = x;
}
public boolean equals (Object o) {
Thing x = (Thing) o;
if (x.value == value) return true;
return false;
}
}
Now it works ;)
Just wanted to note that the following implementation is wrong when value is not a primitive type:
public class Thing
{
public Object value;
public Thing (Object x)
{
this.value = x;
}
#Override
public boolean equals(Object object)
{
boolean sameSame = false;
if (object != null && object instanceof Thing)
{
sameSame = this.value == ((Thing) object).value;
}
return sameSame;
}
}
In that case I propose the following:
public class Thing {
public Object value;
public Thing (Object x) {
value = x;
}
#Override
public boolean equals(Object object) {
if (object != null && object instanceof Thing) {
Thing thing = (Thing) object;
if (value == null) {
return (thing.value == null);
}
else {
return value.equals(thing.value);
}
}
return false;
}
}
Other posters have addressed the question about how contains() works.
An equally important aspect of your question is how to properly implement equals(). And the answer to this is really dependent on what constitutes object equality for this particular class. In the example you provided, if you have two different objects that both have x=5, are they equal? It really depends on what you are trying to do.
If you are only interested in object equality, then the default implementation of .equals() (the one provided by Object) uses identity only (i.e. this == other). If that's what you want, then just don't implement equals() on your class (let it inherit from Object). The code you wrote, while kind of correct if you are going for identity, would never appear in a real class b/c it provides no benefit over using the default Object.equals() implementation.
If you are just getting started with this stuff, I strongly recommend the Effective Java book by Joshua Bloch. It's a great read, and covers this sort of thing (plus how to correctly implement equals() when you are trying to do more than identity based comparisons)
Shortcut from JavaDoc:
boolean contains(Object o)
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))
record overrides equals
You said:
another object with exactly the same constructor input
… and …
Assume the constructor doesn't do anything funny with the input, and the variables stored in both objects are identical.
As other Answers explain, you must override the Object#equals method for List#contains to work.
In Java 16+, the record feature automatically overrides that method for you.
A record is a brief way to write a class whose main purpose is to communicate data transparently and immutably. By default, you simply declare the member fields. The compiler implicitly creates the constructor, getters, equals & hashCode, and toString.
The logic of equals by default is to compare each and every member field of one object to the counterpart in another object of the same class. Likewise, the default implementations of hashCode and toString methods also consider each and every member field.
record Thing( int amount ) {} ;
That’s it, that is all the code you need for a fully-functioning read-only class with none of the usual boilerplate code.
Example usage.
Thing x = new Thing( 100 ) ;
Thing y = new Thing( 100 ) ;
boolean parity = x.equals( y ) ;
When run.
parity = true
Back to your List#contains question.
Thing x = new Thing( 100 );
List < Thing > things =
List.of(
new Thing( 100 ) ,
new Thing( 200 ) ,
new Thing( 300 )
);
boolean foundX = things.contains( x );
When run.
foundX = true
Bonus feature: A record can be declared locally, within a method. Or like a conventional class you can declare a record as a nested class, or as a separate class.
I have created a clone of the existing HashSet using clone() and then comparing their references like below:
HashSet<Employee> h = new HashSet<>();
HashSet<Employee> h1=(HashSet<Employee>) h.clone();
System.out.println(h==h1);
OUTPUT:
false
Shouldn't this be true since we are creating shallow copies?
HashSet overrides the clone() method of Object class
The general intent is that, for any object x, the expression:
x.clone() != x
will be true, and that the expression:
x.clone().getClass() == x.getClass()
will be true, but these are not absolute requirements. While it is typically the case that:
x.clone().equals(x)
will be true, this is not an absolute requirement.
By convention, the object returned by this method should be independent of this object (which is being cloned).
In Java, == checks for references not objects so, h==h1 is false in your case.
In java == for objects check if the object its the exact same object.
And if you go and check the clone method:
public Object clone() {
try {
HashSet<E> newSet = (HashSet<E>) super.clone();
newSet.map = (HashMap<E, Object>) map.clone();
return newSet;
} catch (CloneNotSupportedException e) {
throw new InternalError(e);
}
}
It easy to see it is creating a new object. So now you have TWO different objects that are shallowly equal
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
I was wondering which are the cases where a variable in java could not be equal
(using the equals() method) to itself.
I am not talking object here but the variable itself
(as long as the code compiles and return false when calling equals).
The only situation in which it does that I found so far is:
public class A {
public static void main(String args[]){
A a = new A();
System.out.println(a.equals((a = null)));
}
}
Is there any other case in which a.equals(a) would return false?
EDIT: no overriding of equals() is allowed but you can Modify (cast, inherit) a as much as you want as long as the variable a compare itself in the end.
It could return false in multithreaded contexts, even with an equals implementation that fulfills the equals contract:
class Test {
public static final A a = new A();
public static void main(String... args) throws Exception {
new Thread() {
#Override
public void run() {
while (true) {
a.x += 1;
}
}
}.start();
Thread.sleep(10);
System.out.println(a.equals(a)); // <---
}
}
class A {
int x;
#Override
public boolean equals(Object o) {
return (o instanceof A) && ((A)o).x == x;
}
}
false
From the Object documentation of Oracle:
public boolean equals(Object obj)
Indicates whether some other object is "equal to" this one.
The equals method implements an equivalence relation on non-null object references:
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.
The equals method for class Object implements the most discriminating possible equivalence relation on objects; that is, for any non-null reference values x and y, this method returns true if and only if x and y refer to the same object (x == y has the value true).
Note that it is generally necessary to override the hashCode method whenever this method is overridden, so as to maintain the general contract for the hashCode method, which states that equal objects must have equal hash codes.
Parameters:
obj - the reference object with which to compare.
Returns:
true if this object is the same as the obj argument; false otherwise.
So coming back to your question and analizing the documentation
It's false when a.equals(null); and when a and b (Objects of the classes A and B respectively) are compared, i.e. a.equals(b) will return false too.
In other cases it's true, because of:
It is reflexive: for any non-null reference value x, x.equals(x) should return true.
It clearly says that: not null reference to x (or a in this case):
a.equals(a); will be true
I support khale's and Frakcool's reply. In addition to that if you just need another case to get false try
System.out.println(a.equals((a = new A())));
The assignment essentially returns what is being assigned and that will equate to false if its not the calling object itself.
I don't think there is a way we can get this done, since calling equals to itself is always true. Let me explain what you're trying to achieve.
String foo = "";
bool a = foo.equals(foo); // Here true, the easy one
foo = "some value";
bool b = foo.equals(foo); // Here true, since it's changed and then compared to itself again
bool c = foo.equals(foo="some other value"); // Here should be true again, since the compiler takes first the arguments, makes the assignation, and then makes the equals method execution, so in compiler what happens is:
// 1. Make foo = "some other value"
// 2. Compares foo with foo
// Foo is already changed, so is equals to itself
I haven't tried myself, but that's what should happen.
If for some reason compiler breaks in line bool c = ... it's because equals does not receive String instantiation as a String parameter.
With a correctly implemented .equals(), a.equals(a) will never be false.
Passing an expression to the equals method:
a.equals(a = null);
is no more special than:
a.equals(b); or a.equals(null);
You're just comparing two different values, stuffing an expression into the equals calls doesn't change that.
A very interesting case is the one where you have a boxed Float, consider this code:
Float alpha = +0.0f;
Float beta = -0.0f;
boolean equal = alpha.equals(beta);
System.out.println("Equal: " + equal);
boolean equality = alpha.floatValue() == beta.floatValue();
System.out.println("Equality: " + equality);
This will print true for the first one and false for the second.
The opposite is true of the case of Float.NaN.
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;