2

I have 2 classes

A:

  public class A {
    private int intValue;
    private String stringValue;

    public A(int intValue, String stringValue) {
      this.intValue = intValue;
      this.stringValue = stringValue;
    }

    int getIntValue() {
      return intValue;
    }

    String getStringValue() {
      return stringValue;
    }
  }

and B:

  public class B {
    private int intValue;
    private String stringValue;

    B(int intValue, String stringValue) {
      this.intValue = intValue;
      this.stringValue = stringValue;
    }

    int getIntValue() {
      return intValue;
    }

    String getStringValue() {
      return stringValue;
    }
  }

and some pretty big array of A objects.

And I want to convert A[] to ArrayList<B> effectively. I know several ways to do this:

final A[] array = {new A(1, "1"), new A(2, "2")/*...*/};

// 1 - old-school
final List<B> list0 = new ArrayList<>(array.length);
for (A a : array) {
  list0.add(new B(a.getIntValue(), a.getStringValue()));
}

// 2 - pretty mush same as 1
final List<B> list1 = new ArrayList<>(array.length);
Arrays.stream(array).forEach(a -> list1.add(new B(a.getIntValue(), a.getStringValue())));

// 3 - lambda-style
final List<B> list2 = Arrays.stream(array).map(a -> new B(a.getIntValue(), a.getStringValue())).collect(Collectors.toList());

// 4 - lambda-style with custom Collector
final List<B> list3 = Arrays.stream(array)
    .map(a -> new B(a.getIntValue(), a.getStringValue()))
    .collect(Collector.of((Supplier<List<B>>)() -> new ArrayList(array.length), List::add, (left, right) -> {
      left.addAll(right);
      return left;
    }));

AFAIK 1 is most efficient. But with java 8 features it's possible to make it shorter. 2 is pretty much the same as 1, but with foreach loop replaced by stream foreach. Not sure about it's effectiveness. 3 is the shortest way, but default Collectors.toList() collector uses default ArrayList::new constructor, which means that array in ArrayList will be resized at least once if we have pretty big initial array. So it's not so efficient. And 4, as I understood it (never used this way though) is pretty much the same as 3, but with single memory allocation for array in ArrayList. But it looks ugly.

So, my question is. Am I right about these 4 ways and their effectiveness? Is there any other short and effective way to do this?

esin88
  • 3,091
  • 30
  • 35

2 Answers2

4

Your #4 could be written a bit more shortly:

final List<B> list4 = Arrays.stream(array)
    .map(a -> new B(a.getIntValue(), a.getStringValue()))
    .collect(toCollection(() -> new ArrayList<>(array.length)));

You could also extract the mapping in a separate method:

final List<B> list4 = Arrays.stream(array)
    .map(SomeClass::mapA2B)
    .collect(toCollection(() -> new ArrayList<>(array.length)));    

private static B mapA2B(A a) {
  return new B(a.getIntValue(), a.getStringValue());
}
assylias
  • 321,522
  • 82
  • 660
  • 783
1

If you are interested in the most efficient solution, you may consider

List<B> list=Arrays.asList(Arrays.stream(array)
    .map(a -> new B(a.getIntValue(), a.getStringValue()))
    .parallel().toArray(B[]::new));

Unlike, collect, which works with the Collector abstraction, toArray is an intrinsic operation of the Stream. Since the stream returned by Arrays.asList has a known size, the implementation will create one target array and write into distinct regions of that array when processing in parallel. In contrast, collect will create multiple lists, process them in parallel and merge them afterwards which is unsuitable for an operation as simple as this one.

Even for the single threaded use, it’s likely to perform better as it is just filling an array (“working on the raw metal”) and creating a lightweight List view of it afterwards.

This idea is a direct consequence of this comment made by Brian Goetz

Community
  • 1
  • 1
Holger
  • 285,553
  • 42
  • 434
  • 765
  • Thanks! But unfortunately I can't use `parallel` stream. And for regular `stream()` that will work same, AFAIK. And `Arrays.asList()` returns not quite `ArrayList` I need :) – esin88 Mar 12 '15 at 19:08
  • Right, if your requirement is to get exactly an `ArrayList` instead of just a `List` (i.e really need the ability to change its size later on), that won’t help. And for sequential streams the impact is not so big if you are already using a custom `Supplier` returning an `ArrayList` with the right capacity. – Holger Mar 12 '15 at 19:20