我可以编写一个遍历集合和数组的for循环吗?

时间:2019-04-09 08:28:09

标签: java arrays collections

是否可以检查对象是数组还是带有一个子句的集合?我正在努力实现的目标:

假设数组实现Iterable,并假定对象foo可以是数组或集合,我想使用如下代码片段:

if (foo instanceof Iterable) {
  for (Object f : (Iterable) foo) {
    // do something with f
  }
}

不幸的是,不能将数组强制转换为Iterable。它也没有实现Collection。是否还有其他可能像上述那样在一个循环中处理这两者?当然不是使用if-else if-clause和两个循环(这不太好)。

编辑:针对这些答案。我知道isArray()方法,但是在这种情况下,强制转换

...
for (Object f : (Iterable) foo) {
...

将失败。遗憾的是,代码冗余,因为尽管foreach循环可同时用于Collections和Arrays,但我不得不使用两个循环。

6 个答案:

答案 0 :(得分:18)

关于检查Class<?> fooClass = foo.getClass(); boolean isArrayOrCollection = Collection.class.isAssignableFrom(fooClass) || Object[].class.isAssignableFrom(fooClass); 是集合还是数组的条件:

Class#isAssignableFrom可能会派上用场。

Object[].class.isAssignableFrom(fooClass)

我合理地假设您不会在原始数组上对其进行测试,因为您有仅适用于包装器类的集合。

我想您可以安全地将fooClass.isArray()替换为boolean isArrayOrCollection = Collection.class.isAssignableFrom(fooClass) || fooClass.isArray();

class Test {
    public static void main(String[] args) {
        Predicate<Class<?>> p = c -> Collection.class.isAssignableFrom(c) || 
                                     c.isArray();

        System.out.println(p.test(new int[0].getClass()));
        System.out.println(p.test(new Integer[0].getClass()));
        System.out.println(p.test(Collections.emptyList().getClass()));
        System.out.println(p.test(Collections.emptySet().getClass()));

        System.out.println(p.test(Collections.emptyMap().getClass()));
    }
}

,它也适用于原始数组类。


我已经进行了一次小“测试”

true
true
true
true
false

其中results in

Collection

关于将同时在两个数组和两个数组上运行的通用循环    集合:

您只是不能编写准确的结构来处理此问题:Iterable(或Object[])和Object几乎没有共同点({{1 }}作为普通的父代及其方法还不够)。

我认为构建自己的抽象是明智的,该抽象将以相同的方式处理集合和数组。没有特定的上下文,我可以简单地想到两个子类,每个子类定义应如何迭代其 source (集合或数组)。然后,对接口进行编程将有助于平均管理它们。

一个非常简化的示例是:

interface Abstraction<T> {
    void iterate(Consumer<? super T> action);

    static <T> Abstraction<T> of(Collection<T> collection) {
        return new CollectionAbstraction<>(collection);
    }
    static <T> Abstraction<T> of(T[] array) {
        return new ArrayAbstraction<>(array);
    }
    static IntArrayAbstraction of(int[] array) {
        return new IntArrayAbstraction(array);
    }
}

class CollectionAbstraction<T> implements Abstraction<T> {
    Collection<T> source;

    public CollectionAbstraction(Collection<T> source) {
        this.source = source;
    }

    @Override
    public void iterate(Consumer<? super T> action) {
        source.forEach(action);
    }
}

class ArrayAbstraction<T> implements Abstraction<T> {
    T[] source;

    public ArrayAbstraction(T[] source) {
        this.source = source;
    }

    @Override
    public void iterate(Consumer<? super T> action) {
        for (T t : source) {
            action.accept(t);
        }
    }
}

class IntArrayAbstraction implements Abstraction<Integer> {
    int[] source;

    public IntArrayAbstraction(int[] source) {
        this.source = source;
    }

    @Override
    public void iterate(Consumer<? super Integer> action) {
        for (int t : source) {
            action.accept(t);
        }
    }
}

class Test {
    public static void main(String[] args) {
        Abstraction.of(new Integer[] {1, 2, 3}).iterate(System.out::println);
        Abstraction.of(Arrays.asList(1, 2, 3)).iterate(System.out::println);
        Abstraction.of(new int[] {1, 2, 3}).iterate(System.out::println);
    }
}

我相信上述方法相当通用。您不必依赖特定来源的迭代方式,可以有选择地修改它们。

答案 1 :(得分:10)

其他答案都在努力回答原始标题问题:

  

数组和集合是否有公共接口或超类?

但是您真正的问题在体内:

  

还有没有其他可能像上述那样在一个循环中处理这两个问题?

答案是:否,无法编写一个遍历集合和数组的for循环。

您可以跳过一堆箍将数组转换为列表,但是几乎可以肯定,与仅编写两个(或多个)循环相比,最终会陷入更大的混乱。调用getClass().isArray()会告诉您所拥有的内容,但是如果没有某种类型的转换,您仍然无法使用它。 Arrays.asList()不适用于图元数组。

答案 2 :(得分:6)

根据您要执行的操作,您可能需要实现两种类似的方法:

public <T> void iterateOver(List<T> list) {
    // do whatever you want to do with your list
}

public <T> void iterateOver(T[] array) {
    this.iterateOver(Arrays.asList(array));
}

或者甚至为此有一个接口:

interface ExtendedIterableConsumer<T> {

    public void iterateOver(List<T> list);

    public default void iterateOver(T[] array) {
        this.iterateOver(Arrays.asList(array));

}

我不确定这是否对您有帮助,因为您似乎已经将某个对象放在某个变量中。但是,如果您可以将此问题再上一层解决,那么它可能会很有用。

答案 3 :(得分:5)

您可以使用isArray()中的Class方法来检查对象是否为数组

if (foo != null && (foo.getClass().isArray() || foo instanceof Collection<?>)){

}

编辑:

就迭代这个foo对象而言,没有简单的解决方案。但是,您可以尝试执行以下操作:

private void iterate(@NotNull Object foo) {
    if (foo instanceof Collection<?>) {
        for (Object o : ((Collection<?>) foo)) {
            chandleYourObject(o);
        }
    }

    if (foo.getClass().isArray()) {
        if (foo.getClass().isPrimitive()) {
            checkPrimitiveTypes(foo);
        }
        if (foo instanceof Object[]) {
            for (Object o : (Object[]) foo) {
                chandleYourObject(o);
            }
        }
    }
}

private void checkPrimitiveTypes(Object foo) {
    if (foo instanceof int[]) {
        for (int i : (int[]) foo) {

        }
    }
    //And the rest of primitive types
}

private void chandleYourObject(Object o ){
    //What you want to do with your object
}

答案 4 :(得分:4)

您可以为此编写辅助方法:

@SuppressWarnings("unchecked")
public static <E> void forEach(Object arrayOrIterable, Consumer<? super E> action) {
    Objects.requireNonNull(arrayOrIterable);
    if (arrayOrIterable instanceof Iterable) {
        for (Object o : (Iterable<?>) arrayOrIterable) {
            action.accept((E) o);
        }
    } else if (arrayOrIterable.getClass().isArray()) {
        int length = Array.getLength(arrayOrIterable);
        for (int i = 0; i < length; i++) {
            action.accept((E) Array.get(arrayOrIterable, i));
        }
    } else {
        throw new IllegalArgumentException("not an array nor iterable: " + arrayOrIterable.getClass());
    }
}

第二个分支利用提供帮助方法的java.reflect.Array类(可能很慢)来获取数组的length和给定索引的元素。

您可以这样称呼它:

int[] ints = {1, 2, 3, 4};
forEach(ints, (Integer i) -> System.out.println(i));

List<Integer> ints = Arrays.asList(1, 2, 3, 4);
forEach(ints, (Integer i) -> System.out.println(i));

由于泛型的性质,此方法可能会抛出ClassCastException,例如这个电话:

int[] ints = {1, 2, 3, 4};
forEach(ints, (String s) -> System.out.println(s));

会导致:

java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

答案 5 :(得分:4)

数组是对象:

https://docs.oracle.com/javase/specs/jls/se8/html/jls-10.html

AbstractCollections还扩展了对象:

https://docs.oracle.com/javase/8/docs/api/java/util/AbstractCollection.html

是的,有一个通用的超类,但是不幸的是,这实际上并不能帮助您。

我建议您最好使用:

List<> someList = Arrays.asList(sourceArray)

这会将您的数组转换为实现Iterable的集合。当然,您将需要事先确定初始对象是数组还是集合,并且仅在它是数组时才调用上面的对象,这里有一些实现此目的的选项:

boolean isArray = myArray.getClass().isArray();
boolean isCollection = Collection.class.isAssignableFrom(myList.getClass());