Is there a parameter I can use in Java that works with all for-each loops?
Asked Answered
E

7

8

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.

Epigenesis answered 20/4, 2009 at 18:25 Comment(1)
Unfortunately the answer is no, as arrays do not implement Iterable.Lymphangitis
N
10

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).

Nightly answered 20/4, 2009 at 18:33 Comment(4)
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().Flagstad
Yeah. OTOH, the implementation might conceivable want the collection size before iterating, and most code still uses Collection.Nightly
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.Epigenesis
(added a paragraph about asList vs toArray)Nightly
F
5

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.

Frivol answered 20/4, 2009 at 18:26 Comment(2)
If you use Iterable as the parameter, then you get compile errors when you try to pass in primitive arrays.Epigenesis
Well, I guess this is OO-related. Shouldn't you have to write a wrapper around the arrays?Epps
D
3

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

Dire answered 20/4, 2009 at 18:57 Comment(0)
P
1

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).

Proudlove answered 20/4, 2009 at 19:35 Comment(3)
"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."Frivol
Ok, then I didn't read the question well enough. I hope this one is better.Proudlove
Yeah, there's really no other answer. All the "answers" are just workarounds or advice.Frivol
H
0

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.

Hartebeest answered 20/4, 2009 at 19:28 Comment(0)
T
0
public static void myFun(Iterable<?> iterable){
    for(Object o:iterable){
        // System.out.println(o);
        // Process it here. 
    }
}

This method should work for you. It takes an iterable object as a parameter. The ? wildcard character means that the method can accept any type of iterable object.

Tubulure answered 3/4 at 17:50 Comment(0)
E
-3
public static void myFun(Collection<MyClass> collection) {
    for (MyClass mc : collection) {
        // Stuff is processed here
    }
}
Erminia answered 20/4, 2009 at 18:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.