views:

355

answers:

6

Suppose I've got a method that accepts an array and processes each element in it using Java's built in for-each loop, like this:

public static void myFun(SomeClass[] arr) {
    for (SomeClass sc : arr) {
        // Stuff is processed here
    }
}

This works just fine, but now I want to be able to pass the same method a List<SomeClass> instead. Am I destined to use Collection.toArray(T []), or is there a parameter I can use for myFun() that accepts any type that can be used in a for-each construct?

To clarify: I want a method signature that will accept any iterable object, be it a primitive array or a Collection. I can very easily write two methods, with one wrapping the other, but I'm just curious if there's a better way.

+4  A: 

Use Iterable. That's what it's for.

As you said, Iterable won't handle arrays.

You don't want to use multiple methods wrapping each other. That rules out Arrays.asList and Collection.toArray.

So the answer to your question is no, there isn't a way. But if you can use Lists, why would you ever use arrays?

I would still go with Iterable here. I like it better than Collection because in the past I've had classes that implemented Iterable but were not collections; this made it easy for them to lazily retrieve values as needed, and I could use the foreach loop on them.

Michael Myers
If you use Iterable as the parameter, then you get compile errors when you try to pass in primitive arrays.
Daniel Lew
Ooh, hadn't thought of that. Let me think a bit...
Michael Myers
Is Object[] Iterable??
Tom Hawtin - tackline
Well, I guess this is OO-related. Shouldn't you have to write a wrapper around the arrays?
schnaader
Arrays.asList(myArray) ?
Adam Paynter
The original question clearly doesn't have a primitive array...
Tom Hawtin - tackline
A: 
public static void myFun(Collection<MyClass> collection) {
    for (MyClass mc : collection) {
        // Stuff is processed here
    }
}
Dima
MyCalss .
Joe Philllips
This won't work with passing in an array
Steve Kuo
+6  A: 

I would suggest using Iterable, Collection or List as the parameter type.

IMO, collections should be preferred to reference arrays. If you happen to have an array Arrays.asList does the conversion nicely. Arrays.asList allows gets and sets back through to the array, but obviously not "structural" modifications which would change the array length.

myFun(Arrays.asList(arr));

You may have to use wildcards in extreme/general cases.

public static void myFun(Iterable<? extends SomeClass> somethings) {
    for (SomeClass something : somethings) {
        // something is processed here
    }
}

It is noteworthy that Collections.toArray and Arrays.asList work slightly differently. asList keeps the original array to back the collection, so changes to the collection will be reflected in the array. Collections.toArray makes a (shallow) copy of the collection data. Making a copy is often what you would want anyway if you are returning an array. Asymmetrically, if you are passing as an argument you generally do not copy (unless storing as a field).

Tom Hawtin - tackline
I agree; for cases like this I use Iterable since it imposes the least burden on implementors. And while it doesn't work with Object[] directly, it's trivial to bridge this with Arrays.asList().
erickson
Yeah. OTOH, the implementation might conceivable want the collection size before iterating, and most code still uses Collection.
Tom Hawtin - tackline
Given that there doesn't seem to be a way to avoid asList() or toArray(), I agree with this logic the most; it makes sense to use Iterable instead of Object[] if I'm expecting input from both.
Daniel Lew
(added a paragraph about asList vs toArray)
Tom Hawtin - tackline
+3  A: 

you cannot, java Arrays doesn't implements Iterable:

public static int sum(Iterable<Integer> elements) {
    int s = 0;

    for (int i : elements) {
        s += i;
    }

    return s;
}

public static void main(String[] args) {
    L1: System.out.println(sum(1,2,3));
    L2: System.out.println(sum(Arrays.asList(1,2,3)));
    L3: System.out.println(sum(new int[] { 1,2,3 }));
}

this results in two compile-time errors in (L1 and L3); so you must design your method to accept an Iterable (Collections) and/or an Array, at least one method must perform some conversion (to/from array)

WORKAROUND:

you may be try with an adapter:

public class ArrayIterator<T> implements Iterator<T> {

    private final T[] array;
    private int i;

    public ArrayIterator(T[] anArray) {
        array = anArray;
        i = 0;
    }

    public boolean hasNext() {
        return i < array.length;
    }

    public T next() {
        return array[i++];
    }

    public void remove() {
        throw new UnsupportedOperationException();
    }
}

private static int sum(final Integer ... elements) {
    return sum(new Iterable<Integer>() {

        public Iterator<Integer> iterator() {
            return new ArrayIterator<Integer>(elements);
        }
    });
}

you should pay attention only when dealing with primitive arrays; when you use only reference object (your case) ArrayIterator + anonymous class are cool

hope it helps

dfa
A: 

There's a little know feature of Java Generics in Java 1.5+ where you can use <? extends Subtype> in your method calls and constructors. You could use <? extends Object>, and then anything that deals with those would have access only to methods on Object. What you might really want is something more like this:

List<? extends MyCrustaceans> seaLife = new ArrayList<? extends MyCrustaceans>();
MyShrimp s = new MyShrimp("bubba");
seaLife.add(s);
DoStuff(seaLife);

...

public static void DoStuff(List<? extends MyCrustaceans> seaLife)
{
    for (MyCrustaceans c : seaLife) {
        System.out.println(c);
    }
}

So if you have a base class (like MyCrustaceans), you can use any methods of that base class in DoStuff (or of the Object class, if your parameter is <? extends Object>). There's a similar feature of <? super MyType>, where it accepts a parameter that is a supertype of the given type, instead of a subtype. There's some restrictions on what you can use "extends" and "super" for in this fashion. Here's a good place to find more info.

Ogre Psalm33
+1  A: 

Short answer: no, there's no single method signature that type-safely accepts both an Iterable and an array. Obviously you could just accept Object, but that would be a hack as well.

Long-ish answer: Since the enhanced for-loop is effectively defined twice (once for arrays and once for Iterable), you'll need to provide two overloaded methods as well:

public static void myFun(SomeClass[] array) {
    for (SomeClass sc : array) {
        doTheProcessing(sc);
    }
}

public static void myFun(Iterable<? extends SomeClass> iterable) {
    for (SomeClass sc : iterable) {
        doTheProcessing(sc);
    }
}

Although the source of the two methods looks exactly the same, you'll need to define it twice (unless of course you wrap the Array in your own Iterable as @dfa outlined).

Joachim Sauer
"To clarify: I want a method signature that will accept any iterable object, be it a primitive array or a Collection. I can very easily write two methods, with one wrapping the other, but I'm just curious if there's a better way."
Michael Myers
Ok, then I didn't read the question well enough. I hope this one is better.
Joachim Sauer
Yeah, there's really no other answer. All the "answers" are just workarounds or advice.
Michael Myers