Check if an arrayList contains an array

Viewed 869

I have an arrayList that contains arrays. How do I check if the arrayList contains a specified array? I used .contains method and it returns false instead of expected true.

import java.util.ArrayList;
import java.util.Arrays;

public class main {
    public static void main(String[] args) {
        ArrayList<String[]> action = new ArrayList<String[]>();
        action.add(new String[]{"appple", "ball"});
        String[] items = new String[]{"appple", "ball"};
        if (action.contains(new String[]{"appple", "ball"})) {
            System.out.println("Yes");
        }
        System.out.println(action.contains(items)); // False
    }
}
4 Answers

As you are creating different arrays (even if the contents are the same), contains will result false.

However, if you do this:

 List<String[]> action = new ArrayList<String[]>();
 String[] items = new String[]{"apple","ball"};   
 action.add(items);
 if (action.contains(items)) 
     System.out.println("Yes");

This will print Yes. Also, some examples of the behaviour:

 String[] items = new String[]{"apple","ball"};   
 action.add(items);
 String[] clone = items.clone();
 String[] mirror = items;

 action.contains(clone); // false 
 action.contains(mirror); // true

 items[0]="horse";
 System.out.println(mirror[0]);        // "horse"
 System.out.println(clone[0]);         // "apple"
 System.out.println(action.get(0)[0]); // "horse"

 mirror[1]="crazy";
 System.out.println(clone[1]);         // "ball"
 System.out.println(action.get(0)[1]); // "crazy"
 System.out.println(items[1]);         // "crazy"

 clone[1]="yolo";
 System.out.println(action.get(0)[1]); // "crazy"
 System.out.println(items[1]);         // "crazy"
 System.out.println(mirror[1]);        // "crazy"

 System.out.println(action.get(0).hashCode());    //2018699554
 System.out.println(items.hashCode());            //2018699554
 System.out.println(clone.hashCode());            //1311053135
 System.out.println(mirror.hashCode());           //2018699554

Custom "contains"

The issue here is that if you want to search for an specific array afterwards, you'd lose the references and searching an item wouldn't be possible, not even replicating the array with the same exact values.

As a workaround, you could implement your own contains method. Something like:

If you wish to get the index:

static int indexOfArray(List<String[]> list, String[] twin)
{        
   for (int i=0;i<list.size();i++)
      if (Arrays.equals(list.get(i),twin))
           return i;
   return -1;
}

And then, call it like:

String[] toSearch = new String[]{"apple","ball"};
int index = indexOfArray(action, toSearch); 

if (index>0) 
    System.out.println("Array found at index "+index);
else
    System.out.println("Array not found");

If the index is bigger than -1, you can get your original array by just:

String[] myArray = action.get(index);

HashMap + identifier

An alternative would be storing the arrays into a HashMap by declaring an identifier for each array. For example:

Base64 ID

This will give the same result for the same values, as the encoded value is based on the entries, not the Object's reference.

 static String getIdentifier(String[] array)
 {
    String all="";
    for (String s : array)
        all+=s;
    return Base64.getEncoder().encodeToString(all.getBytes());
 }

And then you could:

Map<String, String[]> arrayMap= new HashMap<>();
String[] items = new String[]{"apple","pear", "banana"}; // *[1234] 
action.add(items);
arrayMap.put(getIdentifier(items), items); // id = QUJDYWFh
//....
//Directly finding the twin will fail
String[] toSearch = new String[]{"apple","pear", "banana"}; // *[1556]
System.out.println(action.contains(toSearch)); // false

//But if we get the identifier based on the values
String arrId = getIdentifier(toSearch); // id = QUJDYWFh
System.out.println(action.contains(arrayMap.get(arrId)));  //true

//arrayMap.get(arrId)->  *[1234]
//.....

Name.

Choose a representative name and use it as Id

Map<String, String[]> arrayMap= new HashMap<>();
String[] items = new String[]{"apple","pear", "banana"};
action.add(items);
arrayMap.put("fruits", items);
//...
System.out.println(action.contains(arrayMap.get("fruits"))); // true  
    

The 'contains' method compares equivalent hashCode values.

So if you make it like below*, it will pass.

public class main {
    public static void main(String[] args) {
        ArrayList<String[]> action = new ArrayList<String[]>();

        String[] items = new String[]{"appple","ball"};
        action.add(items);

        System.out.println("TO STRING");
        System.out.println("--"+action.get(0));
        System.out.println("--"+new String[]{"apple","ball"});

        System.out.println("HASHCODES");
        String[] sameValues = new String[]{"apple","ball"};
        System.out.println("--"+action.get(0).hashCode());    
        System.out.println("--"+items.hashCode());           
        System.out.println("--"+sameValues.hashCode());    
       
        System.out.println("CONTAINS");
        System.out.println("--"+action.contains(items));  // *this
        System.out.println("--"+action.contains(sameValues));
        System.out.println("--"+action.contains(new String[]{"apple","ball"}));
 
    }
}

result is:

TO STRING
--[Ljava.lang.String;@7b1d7fff
--[Ljava.lang.String;@299a06ac
HASHCODES
--1243554231
--1243554231
--2548778887
CONTAINS
--true
--false
--false

Regarding the code shown when printing the array, these don't override toString(), so you get:

getClass().getName() + '@' + Integer.toHexString(hashCode())

For example:

[Ljava.lang.String;@7b1d7fff

  • [ stands for single dimension array
  • Ljava.lang.String stands for the type
  • @
  • 7b1d7fff Hex representation of the hashcode

However, if you want to compare the values, there is the following method.

public class main {
    public static void main(String[] args) {

        String[] items = new String[]{"apple","ball"};

        ArrayList<String> action = new ArrayList<>(Arrays.asList(items));

        if (action.contains("apple")) {
            System.out.println("Yes");
        }
    }
}

You can iterate over this list and for each element, i.e. array, call Arrays.equals method to check equality of arrays until first match, or till the end of the list if none match. In this case it can return true for each element:

List<String[]> list = List.of(
        new String[]{"appple", "ball"},
        new String[]{"appple", "ball"});

String[] act = new String[]{"appple", "ball"};

System.out.println(list.stream()
        .anyMatch(arr -> Arrays.equals(arr, act))); // true

This method internally calls String#equals method for each element of the array, i.e. String, so this code also returns true:

List<String[]> list = List.of(
        new String[]{new String("appple"), new String("ball")},
        new String[]{new String("appple"), new String("ball")});

String[] act = new String[]{new String("appple"), new String("ball")};

System.out.println(list.stream()
        .anyMatch(arr -> Arrays.equals(arr, act))); // true

According to JavaDocs, "contains" method is using "equals" and "hashCode" methods in order to check whether an object is contained.

A leading question: Do you know what's the implementation of "equals" for arrays?

Check it and you will probably understand your code's execution result (hint: ==).

As "Hovercraft Full Of Eels" said, a better design will be using a list of some Collection which you DO understand / control it's "equals" and "hashCode" methods.

Related