42

I have a List of object and the list is very big. The object is

class Sample {
    String value1;
    String value2;
    String value3;
    String value4;
    String value5;
 }

Now I have to search for a specific value of an object in the list. Say if value3=='three' I have to return those objects (My search is not always based on value3)

The list is

List<Sample> list = new ArrayList<Sample>();

What is the efficient way of doing it?

Thanks.

Yves M.
  • 29,855
  • 23
  • 108
  • 144
Jeevi
  • 2,962
  • 6
  • 39
  • 60

7 Answers7

63

You can give a try to Apache Commons Collections.

There is a class CollectionUtils that allows you to select or filter items by custom Predicate.

Your code would be like this:

Predicate condition = new Predicate() {
   boolean evaluate(Object sample) {
        return ((Sample)sample).value3.equals("three");
   }
};
List result = CollectionUtils.select( list, condition );

Update:

In java8, using Lambdas and StreamAPI this should be:

List<Sample> result = list.stream()
     .filter(item -> item.value3.equals("three"))
     .collect(Collectors.toList());

much nicer!

Esteve
  • 1,789
  • 18
  • 23
  • 1
    The CollectionUtils method gives me this error : `java.lang.NoSuchMethodError: No interface method stream()Ljava/util/stream/Stream; in class Ljava/util/List; or its super classes (declaration of 'java.util.List' appears in /system/framework/core-libart.jar` , and I can't use Lambdas as Android SDK doesn't support Java 8. – Shailesh Nov 29 '16 at 12:41
  • @Yankee getting the same issue. did you get it to work? – ono Mar 29 '17 at 16:20
  • @ono I used the Java8 method, worked great. But remember you'll have to add some Java 8 specific dependencies in your Gradle file – Shailesh Mar 29 '17 at 16:23
  • @Yankee i have the gradle imports but im still getting it. you have these two right? `compileOptions {sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8}` – ono Mar 29 '17 at 16:37
  • @ono Yes. These are in my build.gradle(Module:app) and there's `classpath 'me.tatarka:gradle-retrolambda:3.4.0'` in my build.gradle (project) – Shailesh Mar 29 '17 at 16:47
  • @Yankee turns out this was only happening on an emulator with Genymotion. Does it still happen for you on emulators? – ono Mar 31 '17 at 20:04
  • @ono I never use emulators, I directly use a device. – Shailesh Mar 31 '17 at 20:08
51

Using Java 8

With Java 8 you can simply convert your list to a stream allowing you to write:

import java.util.List;
import java.util.stream.Collectors;

List<Sample> list = new ArrayList<Sample>();
List<Sample> result = list.stream()
    .filter(a -> Objects.equals(a.value3, "three"))
    .collect(Collectors.toList());

Note that

  • a -> Objects.equals(a.value3, "three") is a lambda expression
  • result is a List with a Sample type
  • It's very fast, no cast at every iteration
  • If your filter logic gets heavier, you can do list.parallelStream() instead of list.stream() (read this)


Apache Commons

If you can't use Java 8, you can use Apache Commons library and write:

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.Predicate;

Collection result = CollectionUtils.select(list, new Predicate() {
     public boolean evaluate(Object a) {
         return Objects.equals(((Sample) a).value3, "three");
     }
 });

// If you need the results as a typed array:
Sample[] resultTyped = (Sample[]) result.toArray(new Sample[result.size()]);

Note that:

  • There is a cast from Object to Sample at each iteration
  • If you need your results to be typed as Sample[], you need extra code (as shown in my sample)



Bonus: A nice blog article talking about how to find element in list.

Community
  • 1
  • 1
Yves M.
  • 29,855
  • 23
  • 108
  • 144
  • 2
    Well `a.value3.equals("three")` would look more natural, but your way is also "null safe", so keep that :D. Thanks for the update, but I still wonder why no one else noticed it. Since you're more into other languages (according to your tags) I can't/won't blame you, because String comparison might work differently there. But nevermind, it is fixed now. – Tom Nov 04 '15 at 15:21
  • 1
    @Tom "three".equals(((Sample) a).value3) would also work and is "null safe". – alfoks Dec 29 '16 at 10:35
  • Also add `import java.util.Objects;` – jrbe228 Aug 18 '22 at 17:53
5

If you always search based on value3, you could store the objects in a Map:

Map<String, List<Sample>> map = new HashMap <>();

You can then populate the map with key = value3 and value = list of Sample objects with that same value3 property.

You can then query the map:

List<Sample> allSamplesWhereValue3IsDog = map.get("Dog");

Note: if no 2 Sample instances can have the same value3, you can simply use a Map<String, Sample>.

assylias
  • 321,522
  • 82
  • 660
  • 783
  • 2
    No.. My search is not always based on value3.. It can be based on any field in object – Jeevi Oct 30 '12 at 12:34
  • @Sun It is a matter of compromise between performance and memory foot print: maintaining one map per field will be faster but use more memory, looping over the list every time you need to search will be slower but use less memory. – assylias Oct 30 '12 at 12:48
  • Alternatively, if the structure of your object is flat (only String fields) you could have a look at Guava's table - I have never used it though. – assylias Oct 30 '12 at 12:52
3

I modifie this list and add a List to the samples try this

Pseudocode

Sample {
   List<String> values;
   List<String> getList() {
   return values}
}



for(Sample s : list) {
   if(s.getString.getList.contains("three") {
      return s;
   }
}
Yves M.
  • 29,855
  • 23
  • 108
  • 144
MemLeak
  • 4,456
  • 4
  • 45
  • 84
0

As your list is an ArrayList, it can be assumed that it is unsorted. Therefore, there is no way to search for your element that is faster than O(n).

If you can, you should think about changing your list into a Set (with HashSet as implementation) with a specific Comparator for your sample class.

Another possibility would be to use a HashMap. You can add your data as Sample (please start class names with an uppercase letter) and use the string you want to search for as key. Then you could simply use

Sample samp = myMap.get(myKey);

If there can be multiple samples per key, use Map<String, List<Sample>>, otherwise use Map<String, Sample>. If you use multiple keys, you will have to create multiple maps that hold the same dataset. As they all point to the same objects, space shouldn't be that much of a problem.

brimborium
  • 9,362
  • 9
  • 48
  • 76
0

I propose for+if.

Object result; 
for (Object o: objects){ 
  if (o.value3.equals("three")){ 
    result=o; 
    break;
  }
}

no streams, no guavas, I think it's simple.

Yunnosch
  • 26,130
  • 9
  • 42
  • 54
Barrrettt
  • 645
  • 7
  • 15
0

You can filter the list:

list.stream().filter(
              sample -> sample.getValue4().equals("4")
              ).forEach(System.out::println)
Mattia Surricchio
  • 1,362
  • 2
  • 21
  • 49