4

I have a method which looks like this:

void foo (List<String> list, ...) {
  ...
  for (String s : list) { // this is the only place where `list` is used
    ...
  }
  ...
}

the exact same code would work if I replace List<String> list with String[] list, however, to avoid spaghetti code, I keep the single method, and when I need to call it on an array a, I do it like this: foo(Arrays.asList(a)).

I wonder if this is The Right Way.

Specifically,

  • What is the overhead of Arrays.asList()?
  • Is there a way to write a method which would accept both arrays and lists, just like the for loop does?

Thanks!

sds
  • 58,617
  • 29
  • 161
  • 278

5 Answers5

6

Arrays.asList() has a small overhead. There is no real way to implement one method for both List and arrays.

But you can do the following:

void foo (List<String> list, ...) {
  ...
  for (String s : list) { // this is the only place where *list* is used
    ...
  }
  ...
}

void foo (String[] arr, ...) {
  if ( arr != null ) {
      foo(Arrays.asList(arr),...);
  }
}
Jérôme Verstrynge
  • 57,710
  • 92
  • 283
  • 453
  • This is okay if the type is fix. But if you write generic code, you will end in a foo(T[]) method. You shouldn't do that. Ever tried to create a generic array?! :-D – Marcel Jaeschke Jun 05 '12 at 16:37
3

From the source code of openjdk, Arrays.asList:

public static <T> List<T> asList(T... a) {
   return new ArrayList<>(a);
}

furthermore:

ArrayList(E[] array) {
   if (array==null)
      throw new NullPointerException();
   a = array;
}

So basically all that happens in an assignment, so the overhead should be negligible.

Tudor
  • 61,523
  • 12
  • 102
  • 142
  • 1
    `ArrayList` is private inner class in `Arrays` not `java.util.ArrayList`. `T[]` is not a `Collection`. – Piotr Praszmo Jun 05 '12 at 16:22
  • @Banthar: Thanks, I've edited my answer. I wonder why they gave this inner class the same name as the other `ArrayList` class. – Tudor Jun 05 '12 at 16:24
  • I'm shocked to learn that Arrays.asList() has no overhead but after checking I realized it's true! Changing the list will also end up changing the original array so the list indeed references the original array. Good to know! – Saeid Nourian Apr 20 '23 at 16:20
1

The overhead is that it converts an array to a list--how it does so would be implementation-dependent, it only needs to fulfill the contract.

IMO you should write two methods if you're concerned about the potential runtime overhead: that is the nature of Java; methods have type signatures, and they must be obeyed.

Dave Newton
  • 158,873
  • 26
  • 254
  • 302
1

Do avoid this I just use and allow Lists, Sets and Maps (like Joshua Bloch told us). There is no way to merge both "collection types".

An alternative is to use guava (Iterators/Iteratables). So you can iterarte over your collections without a deep copy of them.

Marcel Jaeschke
  • 707
  • 7
  • 24
1

Good question.

This is a very common case, and is often dealt with by writing two separate methods. However code duplication is really a bad idea, and whenever you find yourself duplicating code, you should start looking for opportunities to factor your code better. (As you are doing right now!)

Now if you look into the source of java.util.Arrays, you will notice that Arrays.asList retruns an instance of a private inner class Arrays.ArrayList which is just a thin wrapper over plain arrays, and delegates all relevant method calls to it. (This is known as a projection or view of a data structure.) Therefore the overhead incurred is insignificant (unless you are striving to extract every last bit of performance), and in my opinion, you should go ahead and use this method without worrying about performance.


The solution I personally use is as follows.

I have a class named RichIterable in my personal utils. As the name indicates the class wraps over Iterable and provides some additional useful methods not already present. The class also has a factory method that creates an RichIterable from an array. Here is the class definition.

public class RichIterable<A> implements Iterable<A> {
  private Iterable<A> xs;

  private RichIterable(Iterable<A> xs) {
    this.xs = xs;
  }

  public static <A> RichIterable<A> from(Iterable<A> xs) {
    if (xs instanceof RichIterable) {
      return (RichIterable<A>) xs;
    } else {
      return new RichIterable<A>(xs);
    }
  }

  public static <A> RichIterable<A> from(final Enumeration<A> xs) {
    Iterable<A> iterable = new Iterable<A>() {
      @Override
      public Iterator<A> iterator() {
        return new Iterator<A>() {
          @Override
          public boolean hasNext() {
            return xs.hasMoreElements();
          }

          @Override
          public A next() {
            return xs.nextElement();
          }

          @Override
          public void remove() {
            throw new UnsupportedOperationException(
              "Cannot remove an element from an enumeration.");
          }
        };
      }
    };
    return RichIterable.from(iterable);
  }

  public static <A> RichIterable<A> from(final A[] xs) {
    Iterable<A> iterable = new Iterable<A>() {
      @Override
      public Iterator<A> iterator() {
        return new Iterator<A>() {
          private int i = 0;

          @Override
          public boolean hasNext() {
            return i < xs.length;
          }

          @Override
          public A next() {
            A x = xs[i];
            i++;
            return x;
          }

          @Override
          public void remove() {
            throw new UnsupportedOperationException(
              "Cannot remove an element from an array.");
          }
        };
      }
    };
    return RichIterable.from(iterable);
  }

  public boolean isEmpty() {
    if (xs instanceof Collection) {
      return ((Collection) xs).isEmpty();
    }
    for (A x : xs) {
      return false;
    }
    return true;
  }

  public int size() {
    if (xs instanceof Collection) {
      return ((Collection) xs).size();
    }
    int size = 0;
    for (A x : xs) {
      size++;
    }
    return size;
  }

  public ArrayList<A> toArrayList() {
    ArrayList<A> ys = new ArrayList<A>();
    for (A x : xs) {
      ys.add(x);
    }
    return ys;
  }

  public <B> RichIterable<B> map(F1<A, B> f) {
    List<B> ys = new ArrayList<B>();
    for (A x : xs) {
      ys.add(f.apply(x));
    }
    return RichIterable.from(ys);
  }

  public RichIterable<A> filter(F1<A, Boolean> pred) {
    List<A> ys = new ArrayList<A>();
    Arrays.asList();
    for (A x : xs) {
      if (pred.apply(x)) {
        ys.add(x);
      }
    }
    return RichIterable.from(ys);
  }

  public boolean exists(F1<A, Boolean> pred) {
    for (A x : xs) {
      if (pred.apply(x)) {
        return true;
      }
    }
    return false;
  }

  public boolean forall(F1<A, Boolean> pred) {
    for (A x : xs) {
      if (!pred.apply(x)) {
        return false;
      }
    }
    return true;
  }

  public Maybe<A> find(F1<A, Boolean> pred) {
    for (A x : xs) {
      if (pred.apply(x)) {
        return Just.of(x);
      }
    }
    return Nothing.value();
  }

  public String mkString(String beg, String sep, String end) {
    Iterator<A> i = xs.iterator();
    if (!i.hasNext()) {
      return beg + end;
    }
    StringBuilder sb = new StringBuilder();
    sb.append(beg);
    while (true) {
      A e = i.next();
      sb.append(e.toString());
      if (!i.hasNext()) {
        return sb.append(end).toString();
      }
      sb.append(sep);
    }
  }

  public String mkString(String sep) {
    return mkString("", sep, "");
  }

  public String mkString() {
    return this.mkString(", ");
  }

  public Iterable<A> getRaw() {
    return xs;
  }

  @Override
  public Iterator<A> iterator() {
    return xs.iterator();
  }
}
missingfaktor
  • 90,905
  • 62
  • 285
  • 365
  • JFYI: I would recommend you to use guava instead, which does the same and much more. The advantage in using this lib: you don't have to maintain the code and guava is an open lib. So other thirdparty module can use the lib. – Marcel Jaeschke Jun 06 '12 at 11:01
  • @MarcelJaeschke, Guava is good, but not functional enough for my taste. – missingfaktor Jun 06 '12 at 11:31
  • I have my own util lib too, but I removed everything which is covered by guava. So I have less code to maintain. BTW: I think the map(tranform) and filter approach in guave is better (do not create a copy). – Marcel Jaeschke Jun 06 '12 at 18:52