Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to print a nested list using java stream where the Object holds a list of references to itself

I have an object which looks similar to what's shown below:

public class Obj {
  private List<Obj> objs;
  private String objId;

  public List<Obj> getObjs() {
    return objs;
  }

  public String getobjId() {
    return objId;
  }

  @Override
  public String toString() {
    return "Obj [objs=" + objs + ", objId=" + objId + "]";
  }

}

How can I print the list of objId using streams?

EDIT

Obj can contain a list of Obj and it's children can contain a list of obj objects. If the depth is 5 levels, is it possible to print all the objId values from the top most obj to the values of the child at 5th level. I want to avoid nested for loops.

like image 289
Rosa14 Avatar asked Oct 13 '18 22:10

Rosa14


2 Answers

You should use recursion. One possible way to do it with streams is as follows:

private Stream<Obj> allObjs() {
    return Stream.concat(
        Stream.of(this), 
        objs == null ? Stream.empty() : objs.stream().flatMap(Obj::allObjs));
}

@Override
public String toString() {
    return allObjs()
        .map(Obj::getobjId)
        .collect(Collectors.joining(", "));
}

Note that this works well as long as your Obj instances are arranged in a tree-like structure. If you have a specific Obj instance that is both a parent at some level and a child in some lower level (i.e. if your Obj instances form a graph), this solution won't work and you'll get a huge StackOverflowError.


If you can't modify the Obj class, you could achieve the same with helper methods that receive an instance of Obj i.e. in a ObjService class:

public static Stream<Obj> allObjs(Obj o) {
    if (o == null) return Stream.empty(); // in case the argument is null
    return Stream.concat(
            Stream.of(o), 
            o.getObjs() == null ?
                Stream.empty() :
                o.getObjs().stream().flatMap(ObjService::allObjs));
}

public static String deepToString(Obj o) {
    return ObjService.allObjs(o)
        .map(Obj::getobjId)
        .collect(Collectors.joining(", "));
}
like image 174
fps Avatar answered Oct 15 '22 01:10

fps


Well, using Stream you can start from here:

objs.stream().
            map(Obj::getObjId).
            forEachOrdered(System.out::println);

And improving a little bit:

    List<String> collect = objs.stream()
            .filter(Objects::nonNull) // Filter only nonNull objects. Avoid NullPointerException
            .map(Obj::getObjId)
            .peek(System.out::println) // Print the ObjId value from Obj
            .collect(Collectors.toList()); // Return the result to a List, if you need.

Now you have the basic way to get values from children. Learn a little about Stream and improve the code example ;)

Some nice links:

  • A Guide to Streams in Java 8
  • Processing Data with Java SE 8 Streams
  • The Java 8 Stream API Tutorial

Hope this helps!

like image 22
Juliano Macedo Avatar answered Oct 15 '22 01:10

Juliano Macedo