How to make HashMap work with Arrays as key? - java

I am using boolean arrays as keys for a HashMap. But the problem is HashMap fails to get the keys when a different array is passed as key, although the elements are same. (As they are different objects).
How can I make it work with arrays as keys ?
Here is the code :
public class main {
public static HashMap<boolean[], Integer> h;
public static void main(String[] args){
boolean[] a = {false, false};
h = new HashMap<boolean[], Integer>();
h.put(a, 1);
if(h.containsKey(a)) System.out.println("Found a");
boolean[] t = {false, false};
if(h.containsKey(t)) System.out.println("Found t");
else System.out.println("Couldn't find t");
}
}
Both the arrays a and t contain the same elements, but HashMap doesn't return anything for t.
How do I make it work ?

You cannot do it this way. Both t and a will have different hashCode() values because the the java.lang.Array.hashCode() method is inherited from Object, which uses the reference to compute the hash-code (default implementation). Hence the hash code for arrays is reference-dependent, which means that you will get a different hash-code value for t and a. Furthermore, equals will not work for the two arrays because that is also based on the reference.
The only way you can do this is to create a custom class that keeps the boolean array as an internal member. Then you need to override equals and hashCode in such a way that ensures that instances that contain arrays with identical values are equal and also have the same hash-code.
An easier option might be to use List<Boolean> as the key. Per the documentation the hashCode() implementation for List is defined as:
int hashCode = 1;
Iterator<E> i = list.iterator();
while (i.hasNext()) {
E obj = i.next();
hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode());
}
As you can see, it depends on the values inside your list and not the reference, and so this should work for you.

It is not possible to do this with arrays, as any two different arrays don't compare equals, even if they have the same elements.
You need to map from container class, for example ArrayList<Boolean> (or simply List<Boolean>. Perhaps BitSet would be even more appropriate.

Map implementations relies on key's equals and hashCode methods. Arrays in java are directly extends from Object, they use default equals and hashCode of Object which only compares identity.
If I were you, I would create a class Key
class Key {
private final boolean flag1;
private final boolean flag2;
public Key(boolean flag1, boolean flag2) {
this.flag1 = flag1;
this.flag2 = flag2;
}
#Override
public boolean equals(Object object) {
if (!(object instanceof Key)) {
return false;
}
Key otherKey = (Key) object;
return this.flag1 == otherKey.flag1 && this.flag2 == otherKey.flag2;
}
#Override
public int hashCode() {
int result = 17; // any prime number
result = 31 * result + Boolean.valueOf(this.flag1).hashCode();
result = 31 * result + Boolean.valueOf(this.flag2).hashCode();
return result;
}
}
After that, you can use your key with Map:
Map<Key, Integer> map = new HashMap<>();
Key firstKey = new Key(false, false);
map.put(firstKey, 1);
Key secondKey = new Key(false, false) // same key, different instance
int result = map.get(secondKey); // --> result will be 1
Reference:
Java hash code from one field

Problems
As others have said, Java arrays inherit .hashcode() and .equals() from Object, which uses a hash of the address of the array or object, completely ignoring its contents. The only way to fix this is to wrap the array in an object that implements these methods based on the contents of the array. This is one reason why Joshua Bloch wrote Item 25: "Prefer lists to arrays." Java provides several classes that do this or you can write your own using Arrays.hashCode() and Arrays.equals() which contain correct and efficient implementations of those methods. Too bad they aren't the default implementations!
Whenever practical, use a deeply unmodifiable (or immutable) class for the keys to any hash-based collection. If you modify an array (or other mutable object) after storing it as a key in a hashtable, it will almost certainly fail future .get() or .contains() tests in that hashtable. See also Are mutable hashmap keys a dangerous practice?
Specific Solution
// Also works with primitive: (boolean... items)
public static List<Boolean> bList(Boolean... items) {
List<Boolean> mutableList = new ArrayList<>();
for (Boolean item : items) {
mutableList.add(item);
}
return Collections.unmodifiableList(mutableList);
}
ArrayList implements .equals() and .hashCode() (correctly and efficiently) based on its contents, so that every bList(false, false) has the same hashcode as, and will be equal to every other bList(false, false).
Wrapping it in Collections.unmodifiableList() prevents modification.
Modifying your example to use bList() requires changing just a few declarations and type signatures. It is as clear as, and almost as brief as your original:
public class main {
public static HashMap<List<Boolean>, Integer> h;
public static void main(String[] args){
List<Boolean> a = bList(false, false);
h = new HashMap<>();
h.put(a, 1);
if(h.containsKey(a)) System.out.println("Found a");
List<Boolean> t = bList(false, false);
if(h.containsKey(t)) System.out.println("Found t");
else System.out.println("Couldn't find t");
}
}
Generic Solution
public <T> List<T> bList(T... items) {
List<T> mutableList = new ArrayList<>();
for (T item : items) {
mutableList.add(item);
}
return Collections.unmodifiableList(mutableList);
}
The rest of the above solution is unchanged, but this will leverage Java's built-in type inference to work with any primitive or Object (though I recommend using only with immutable classes).
Library Solution
Instead of bList(), use Google Guava's ImmutableList.of(), or my own Paguro's vec(), or other libraries that provide pre-tested methods like these (plus immutable/unmodifiable collections and more).
Inferior Solution
This was my original answer in 2017. I'm leaving it here because someone found it interesting, but I think it's second-rate because Java already contains ArrayList and Collections.unmodifiableList() which work around the problem. Writing your own collection wrapper with .equals() and .hashCode() methods is more work, more error-prone, harder to verify, and therefore harder to read than using what's built-in.
This should work for arrays of any type:
class ArrayHolder<T> {
private final T[] array;
#SafeVarargs
ArrayHolder(T... ts) { array = ts; }
#Override public int hashCode() { return Arrays.hashCode(array); }
#Override public boolean equals(Object other) {
if (array == other) { return true; }
if (! (other instanceof ArrayHolder) ) {
return false;
}
//noinspection unchecked
return Arrays.equals(array, ((ArrayHolder) other).array);
}
}
Here is your specific example converted to use ArrayHolder:
// boolean[] a = {false, false};
ArrayHolder<Boolean> a = new ArrayHolder<>(false, false);
// h = new HashMap<boolean[], Integer>();
Map<ArrayHolder<Boolean>, Integer> h = new HashMap<>();
h.put(a, 1);
// if(h.containsKey(a)) System.out.println("Found a");
assertTrue(h.containsKey(a));
// boolean[] t = {false, false};
ArrayHolder<Boolean> t = new ArrayHolder<>(false, false);
// if(h.containsKey(t)) System.out.println("Found t");
assertTrue(h.containsKey(t));
assertFalse(h.containsKey(new ArrayHolder<>(true, false)));
I used Java 8, but I think Java 7 has everything you need for this. I tested hashCode and equals using TestUtils.

You could create a class that contains the array. Implements the hashCode() and equals() methods for that class, based on values:
public class boolarray {
boolean array[];
public boolarray( boolean b[] ) {
array = b;
}
public int hashCode() {
int hash = 0;
for (int i = 0; i < array.length; i++)
if (array[i])
hash += Math.pow(2, i);
return hash;
}
public boolean equals( Object b ) {
if (!(b instanceof boolarray))
return false;
if ( array.length != ((boolarray)b).array.length )
return false;
for (int i = 0; i < array.length; i++ )
if (array[i] != ((boolarray)b).array[i])
return false;
return true;
}
}
You can then use:
boolarray a = new boolarray( new boolean[]{ true, true } );
boolarray b = new boolarray( new boolean[]{ true, true } );
HashMap<boolarray, Integer> map = new HashMap<boolarray, Integer>();
map.put(a, 2);
int c = map.get(b);
System.out.println(c);

Probably it is because equals() method for Array returns acts different then you expect. You should think about implementing your own collecting and override equals() and hashCode().

boolean[] t;
t = a;
If you give this, instead of boolean[] t = {false, false};, then you'll get the desired output.
This is because the Map stores the reference as the key, and in your case, though t has the same values, it doesn't have the same reference as a.
Hence, when you give t=a, it'll work.
Its very similar to this:-
String a = "ab";
String b = new String("ab");
System.out.println(a==b); // This will give false.
Both a & b hold the same value, but have different references. Hence, when you try to compare the reference using ==, it gives false.
But if you give, a = b; and then try to compare the reference, you'll get true.

Map uses equals() to test if your keys are the same.
The default implementation of that method in Object tests ==, i.e. reference equality. So, as your two arrays are not the same array, equals always returns false.
You need to make the map call Arrays.equals on the two arrays to check for equality.
You can create an array wrapper class that uses Arrays.equals and then this will work as expected:
public static final class ArrayHolder<T> {
private final T[] t;
public ArrayHolder(T[] t) {
this.t = t;
}
#Override
public int hashCode() {
int hash = 7;
hash = 23 * hash + Arrays.hashCode(this.t);
return hash;
}
#Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final ArrayHolder<T> other = (ArrayHolder<T>) obj;
if (!Arrays.equals(this.t, other.t)) {
return false;
}
return true;
}
}
public static void main(String[] args) {
final Map<ArrayHolder<Boolean>, Integer> myMap = new HashMap<>();
myMap.put(new ArrayHolder<>(new Boolean[]{true, true}), 7);
System.out.println(myMap.get(new ArrayHolder<>(new Boolean[]{true, true})));
}

You could use a library that accepts an external hashing and comparing strategy (trove).
class MyHashingStrategy implements HashingStrategy<boolean[]> {
#Override
public int computeHashCode(boolean[] pTableau) {
return Arrays.hashCode(pTableau);
}
#Override
public boolean equals(boolean[] o1, boolean[] o2) {
return Arrays.equals(o1, o2);
}
}
Map<boolean[], T> map = new TCustomHashMap<boolean[],T>(new MyHashingStrategy());

Related

Custom key generation and collision in a hashMap

I have a method that is expected to save an object in a hashmap (used as a cache) that has as a key a String.
The objects that are passed in the method have either fields that are “volatile” i.e. they can change on a next refresh from the data store or are the same across all objects except for 3 fields.
Those fields are 2 of type double and 1 field of type String.
What I am doing now is I use:
Objects.hash(doubleField1, doubleField2, theString)
and convert the result to String.
Basically I am generating a key for that object based on the immutable state.
This seems to work but I have the following question:
If we exclude the case that we have 2 objects with the exact same fields (which is impossible) how likely is that I could end up with a collision that won’t be able to be verified properly?
I mean that if I have a hashmap with keys e.g. strings etc if there is a collision on the hashCode the actual value of the key is compared to verify if it is the same object and not a collision.
Would using keys the way I have described create problems in such verification?
Update:
If I have e.g. a hashmap with key a Person and the hashCode is generated using fullName and ssn or dateOfBirth if there is a collision then the hashmap implementation uses equals to verify if it is the actual object being searched for.
I was wondering if the approach I describe could have some issue in that part because I generate the actual key directly
Here is a simple demo for a hashMap key implementation. When retrieving the object I construct the fields piecemeal to avoid any possibility of using cached Strings or Integers. It makes a more convincing demo.
Map<MyKey, Long> map = new HashMap<>();
map.put(new MyKey(10,"abc"), 1234556L);
map.put(new MyKey(400,"aefbc"), 548282L);
int n = 380;
long v = map.get(new MyKey(n + 20, "ae" + "fbc")); // Should get 548282
System.out.println(v);
prints
548282
The key class
class MyKey {
privat eint v;
private String s;
private int hashcode;
public MyKey(int v, String s) {
Objects.requireNonNull(s, "String must be provided");
this.v = v;
this.s = s;
// this class is immutable so no need to keep
// computing hashCode
hashcode = Objects.hash(s,v);
}
#Override
public int hashCode() {
return hashcode;
}
#Override
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (o == null) {
return false;
}
if (o instanceof MyKey) {
MyKey mk = (MyKey)o;
return v == mk.v && s.equals(mk.s);
}
return false;
}
}

What immutable object works with List.contains?

I want to create a list of coordinates and be able to check if this list contains a new coordinate.
I have tried implementing Pair, but using List.contains() with a Pair doesn't work, always returning false.
What other objects can I use that I will be able to check against List.contains()?
This is probably because your implementation of Pair doesn't provide an overridden equals method. I was able to reproduce your issue using the following code:
//a plain POJO, in Pair.java
public class Pair<A, B> {
private A a;
private B b;
public Pair(A a, B b) {
this.a = a;
this.b = b;
}
public A getA() {
return a;
}
public B getB() {
return b;
}
}
//... Main.java
public class Main {
public static void main(String[] args) {
List<Pair<Integer, String>> list = new ArrayList<>();
Pair<Integer, String> one = new Pair<>(1, "hello");
Pair<Integer, String> two = new Pair<>(1, "hello");
list.add(one);
System.out.println(list.contains(two));
}
}
This prints out false, because List.contains uses object equality as a test (the default equals method in class Object). With the above code, for example, one.equals(two) evaluates to false, because they're not the same object. To fix this, you have to provide an equals method that looks at each field and compares them individually:
//in class Pair
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Pair<?, ?> pair = (Pair<?, ?>) o;
return Objects.equals(a, pair.a) &&
Objects.equals(b, pair.b);
}
You can delegate the somewhat tedious and error-prone task of writing this code to your IDE. I'm using Intellij, and it's only a matter of clicking Code/Generate/equals() and hashcode() . You don't need hashcode() for this particular case, but it's always a good idea to keep equals() and hashcode() together. Now, when List.contains tries to find an element that is equal to the one you provided, it will use this new and more appropriate method. If you run the main method again, you'll see it evaluates to true.
List.contains checks to see if two objects are equal using the Object.equals method. Therefore, it only works with objects that have an Object.equals method implemented. If you are writing your own Pair class, make sure you give it an equals method.
I think your Pair contains the same value, but the object isn't the same. That's because it doesn't work. You could use a Map instead.
How you can use a map:
Map<String, Integer> map = new HashMap<>();
map.put("Hello", 0); // add key an value to map (Key: "Hello", Value: 0)
map.put("World", 1); // add key an value to map (Key: "World", Value: 1)
if (map.containsKey("World")) { // check if map contains key: "World"
System.out.println(map.get("World")); // try return value by key: returns 1
System.out.println(map.get("world")); // try return value by key: returns null
}
if (map.containsKey("Hello")) { // check if map contains key: "Hello"
System.out.println(map.get("Hello")); // try return value by key: returns 0
System.out.println(map.get("hello")); // try return value by key: returns null
}

What is the best way to compare two objects having multiple list attributes

I have a POJO/DTO class with multiple list attribute like
class Boo {
private List<Foo> foos;
private List<Integer> pointers;
}
I want to compare if both lists contain the same values ignoring the order of the lists. Is it possible to achieve this without opening the object and ordering the lists?
Help would be appreciated. Thanks in Advance
"I want to compare if both contains same values instead of the order of list."
There is not a universal equality operator. Sometimes you want compare objects by certain properties. Probably the canonical example could be comparing strings, sometimes "computer" is equal or not than "Computer" or "Vesterålen" is equal or not than "Vesteralen".
In Java, you can redefine the default equivalence relation between objects (modifying the default behavior!).
The object List use as default equivalence relation the default equivalence relation of the contained objects and checking that equality in order.
The following example ignore the elements order only in one property:
class My {
private final List<String> xs;
private final List<Integer> ys;
My(List<String> xs, List<Integer> ys) {
this.xs = xs;
this.ys = ys;
}
public List<Integer> getYs() {
return ys;
}
public List<String> getXs() {
return xs;
}
#Override
public int hashCode() {
return xs.hashCode() + 7 * ys.hashCode();
}
#Override
public boolean equals(Object obj) {
if(!(obj instanceof My))
return false;
My o = (My) obj;
return
// ignoring order
getXs().stream().sorted().collect(toList()).equals(o.getXs().stream().sorted().collect(toList()))
// checking order
&& getYs().equals(o.getYs());
}
}
public class Callme {
public static void main(String... args) {
My m1 = new My(asList("a", "b"), asList(1, 2));
My m2 = new My(asList("b", "a"), asList(1, 2));
My m3 = new My(asList("a", "b"), asList(2, 1));
System.out.println(m1.equals(m2));
System.out.println(m1.equals(m3));
}
}
with output
true
false
But I can't define YOUR required equivalence relation, for example I do not ignore if one list contains more elements than the other but maybe you wish (eg. to you is equal {a, b, a} than {b, a}).
So, define you equivalence relation for your object and override hashCode and equals.
This boils down to comparing the lists. If the order of the items is irrelevant anyways you might fare better using Set instead of List.
Your equals then would look like
public boolean equals(object other) {
//here be class and null checks
return foos.equals(other.foos) && pointers.equals(other.pointers);
}
If you cannot use Set - either because you can have the same item multiple times or because order matters - you have can do the same as above with a reciprocal containsAll() call. This still would not take duplicate entries into consideration but will work quite fine otherwise.
You state that you cannot edit the class Boo. One solution would be to have a service class which does this for you a bit similar to Objects.equals().
class BooComparer {
public static bool equals(Boo a, Boo b) {
//again do some null checks here
return a.foos.containsAll(b.foos)
&& b.foos.containsAll(a.foos)
&& a.pointers.containsAll(b.pointers)
&& b.pointers.containsAll(a.pointers)
}
}
If this works for you - fine. Maybe you have to compare other members, too. And again: this will ignore if one of the lists has an entry twice.

Java Map<> with intrisic types possible?

How is this possible:
HashMap<byte[], byte[]> and what is hash() of byte[]?
Yes, it is possible (with a big caveat, see below), but byte[] is not an "intrinsic type". First, there's no such thing, you probably mean a "primitive type". Second: byte[] is not a primitive type, byte is. An array is always a reference type.
Arrays don't have specific hashCode implementations, so they'll just use the hashCode of Object, which means that the hashCode will be the indentity-hashCode, which is independent from the actual content.
In other words: a byte[] is a very bad Map key, because you can only retrieve the value with the exact same instance.
If you need a content-based hashCode() based on an array, you can use Arrays.hashCode(), but that won't help you (directly) with the Map. There's also Arrays.equals() to check for content equality.
You could wrap your byte[] in a thin wrapper object that implements hashCode() and equals() (using the methods mentioned above):
import java.util.Arrays;
public final class ArrayWrapper {
private final byte[] data;
private final int hash;
public ArrayWrapper(final byte[] data) {
// strictly speaking we should make a defensive copy here,
// but I *assume* (and should document) that the argument
// passed in here should not be changed
this.data = data;
this.hash = Arrays.hashCode(data);
}
#Override
public int hashCode() {
return hash
}
#Override
public boolean equals(Object o) {
if (!(o instanceof ArrayWrapper)) {
return false;
}
ArrayWrapper other = (ArrayWrapper) o;
return this.hash == other.hash && Arrays.equals(this.data, other.data);
}
// don't add getData to prevent having to do a defensive copy of data
}
Using this class you can then use a Map<ArrayWrapper,byte[]>.
For arrays hashCode() uses the default implementation from Object - typically some form of internal object address. As a result, key in this HashMap is considered unique if it is a different array, not if array contents are equal.
byte[] a = { 2, 3 };
byte[] b = { 2, 3 };
System.out.println(a.equals(b)); // false
Map<byte[], String> map = new HashMap<byte[], String>();
map.put(a, "A");
map.put(b, "B");
System.out.println(map); // {[B#37d2068d=B, [B#7ecec0c5=A}

ArrayList not using the overridden equals

I'm having a problem with getting an ArrayList to correctly use an overriden equals. the problem is that I'm trying to use the equals to only test for a single key field, and using ArrayList.contains() to test for the existence of an object with the correct field. Here is an example
public class TestClass {
private static class InnerClass{
private final String testKey;
//data and such
InnerClass(String testKey, int dataStuff) {
this.testKey =testKey;
//etc
}
#Override
public boolean equals (Object in) {
System.out.println("reached here");
if(in == null) {
return false;
}else if( in instanceof String) {
String inString = (String) in;
return testKey == null ? false : testKey.equals(inString);
}else {
return false;
}
}
}
public static void main(String[] args) {
ArrayList<InnerClass> objectList = new ArrayList<InnerClass>();
//add some entries
objectList.add(new InnerClass("UNIQUE ID1", 42));
System.out.println( objectList.contains("UNIQUE ID1"));
}
}
What worries me is that not only am I getting false on the output, but I'm also not getting the "reached here" output.
Does anyone have any ideas why this override is being completely ignored? Is there some subtlety with overrides and inner classes I don't know of?
Edit:
Having problems with the site so I cant seem to mark the answered.
Thanks for the quick response: yes an oversight on my part that it is the String .equals thta is called, not my custom one. I guess it's old fashioned checks for now
If you check sources of ArrayList, you will see that it calls equals of other object. In your case it will call equals of String "UNIQUE ID1" which will check that other object is not of type String and just returns false:
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
public int indexOf(Object o) {
...
for (int i = 0; i < size; i++)
if (o.equals(elementData[i]))
return i;
...
return -1;
}
For your case call contains with InnerClass that only contains id:
objectList.contains(new InnerClass("UNIQUE ID1"))
Don't forget to implement equals for InnerClass which compares id only.
According to the JavaDoc of List.contains(o), it is defined to return true
if and only if this list contains at least one element e such that (o==null ? e==null : o.equals(e)).
Note that this definition calls equals on o, which is the parameter and not the element that is in the List.
Therefore String.equals() will be called and not InnerClass.equals().
Also note that the contract for Object.equals() states that
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.
But you violate this constraint, since new TestClass("foo", 1).equals("foo") returns true but "foo".equals(new TestClass("foo", 1)) will always return false.
Unfortunately this means that your use case (a custom class that can be equal to another standard class) can not be implemented in a completely conforming way.
If you still want to do something like this, you'll have to read the specification (and sometimes the implementation) of all your collection classes very carefully and check for pitfalls such as this.
You're invoking contains with an argument that's a String and not an InnerClass:
System.out.println( objectList.contains("UNIQUE ID1"))
In my JDK:
public class ArrayList {
public boolean contains(Object o) {
return indexOf(o) >= 0;
}
public int indexOf(Object o) {
if (o == null) {
// omitted for brevity - aix
} else {
for (int i = 0; i < size; i++)
if (o.equals(elementData[i])) // <<<<<<<<<<<<<<<<<<<<<<
return i;
}
return -1;
}
}
Note how indexOf calls o.equals(). In your case, o is a String, so your objectList.contains will be using String.equals and not InnerClass.equals.
Generally, you need to also override hashCode() but this is not the main problem here. You are having an asymmetric equals(..) method. The docs make it clear that it should be symmetric:
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.
And what you observe is an unexpected behaviour due to broken contract.
Create an utility method that iterates all items and verifies with equals(..) on the string:
public static boolean containsString(List<InnerClass> items, String str) {
for (InnerClass item : items) {
if (item.getTestKey().equals(str)) {
return true;
}
}
return false;
}
You can do a similar thing with guava's Iterables.any(..) method:
final String str = "Foo";
boolean contains = Iterables.any(items, new Predicate<InnerClass>() {
#Override
public boolean apply(InnerClass input){
return input.getTestKey().equals(str);
}
}
Your equals implementation is wrong. Your in parameter should not be a String. It should be an InnerClass.
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof InnerClass) return false;
InnerClass that = (InnerClass)o;
// check for null keys if you need to
return this.testKey.equals(that.testKey);
}
(Note that instanceof null returns false, so you don't need to check for null first).
You would then test for existence of an equivalent object in your list using:
objectList.contains(new InnerClass("UNIQUE ID1"));
But if you really want to check for InnerClass by String key, why not use Map<String,InnerClass> instead?
Although not answering your question, many Collections use hashcode(). You should override that too to "agree" with equals().
Actually, you should always implement both equals and hashcode together, and they should always be consistent with each other. As the javadoc for Object.equals() states:
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.
Specifically, many Collections rely on this contract being upheld - behaviour is undefined otherwise.
There are a few issues with your code. My suggestion would be to avoid overriding the equals entirely if you are not familiar with it and extend it into a new implementation like so...
class MyCustomArrayList extends ArrayList<InnerClass>{
public boolean containsString(String value){
for(InnerClass item : this){
if (item.getString().equals(value){
return true;
}
}
return false;
}
}
Then you can do something like
List myList = new MyCustomArrayList()
myList.containsString("some string");
I suggest this because if you override the equals should also override the hashCode and it seems you are lacking a little knowledge in this area - so i would just avoid it.
Also, the contains method calls the equals method which is why you are seeing the "reached here". Again if you don't understand the call flow i would just avoid it.
in the other way, your equal method gets called if you change your code as follows. hope this clears the concept.
package com.test;
import java.util.ArrayList;
import java.util.List;
public class TestClass {
private static class InnerClass{
private final String testKey;
//data and such
InnerClass(String testKey, int dataStuff) {
this.testKey =testKey;
//etc
}
#Override
public boolean equals (Object in1) {
System.out.println("reached here");
if(in1 == null) {
return false;
}else if( in1 instanceof InnerClass) {
return ((InnerClass) this).testKey == null ? false : ((InnerClass) this).testKey.equals(((InnerClass) in1).testKey);
}else {
return false;
}
}
}
public static void main(String[] args) {
ArrayList<InnerClass> objectList = new ArrayList<InnerClass>();
InnerClass in1 = new InnerClass("UNIQUE ID1", 42);
InnerClass in2 = new InnerClass("UNIQUE ID1", 42);
//add some entries
objectList.add(in1);
System.out.println( objectList.contains(in2));
}
}
As many posts have said, the problem is that list.indexOf(obj) function calls "equals" of the obj, not the items on the list.
I had the same problem and "contains()" didn't satisfy me, as I need to know where is the element!. My aproach is to create an empty element with just the parameter to compare, and then call indexOf.
Implement a function like this,
public static InnerClass empty(String testKey) {
InnerClass in = new InnerClass();
in.testKey =testKey;
return in;
}
And then, call indexOf like this:
ind position = list.indexOf(InnerClass.empty(key));
There are two errors in your code.
First:
The "contains" method called on "objectList" object should pass a new InnerClass object as the parameter.
Second:
The equals method (should accept the parameter as Object, and is correct) should handle the code properly according to the received object.
Like this:
#Override
public boolean equals (Object in) {
System.out.println("reached here");
if(in == null) {
return false;
}else if( in instanceof InnerClass) {
String inString = ((InnerClass)in).testKey;
return testKey == null ? false : testKey.equals(inString);
}else {
return false;
}
}
This post was first written before Java 8 was available but now that it's 2017 instead of using the List.containts(...) method you can use the new Java 8 way like this:
System.out.println(objectList.stream().filter(obj -> obj.getTestKey().equals("UNIQUE ID1")).findAny().isPresent());
And give your TestClass a getter for your testKey field:
public String getTestKey() {
return testKey;
}
The benefit of this approach is that you don't have to modify the equals or hash method and you'll look like a boss to your peers!

Categories