是否可以编写一个循环访问集合和数组的 for 循环?

2022-09-02 05:36:47

是否有可能检查对象是数组还是具有一个子句的集合?我试图实现的目标:

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

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

遗憾的是,数组不能转换为可迭代。它也没有实现集合。有没有其他可能性可以在一个循环中处理两者,就像上面一样?而不是 - 当然 - 使用if-else if-子句和两个循环(这不会很好)。

编辑:回应这些答案。我知道 isArray() 方法,但在这种情况下,在

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

将失败。这很遗憾,而且代码冗余,因为我必须使用两个循环,尽管foreach循环同时适用于集合和数组。


答案 1

关于检查 foo 是集合还是数组的条件:

Class#isAssignableFrom 可能会派上用场。

Class<?> fooClass = foo.getClass();
boolean isArrayOrCollection = Collection.class.isAssignableFrom(fooClass) ||
                              Object[].class.isAssignableFrom(fooClass);

我合理地假设你不会在基元数组上测试它,因为你有只与包装类一起工作的集合。

我想你可以安全地替换为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

关于将同时运行数组和集合的泛型循环:

你根本无法编写一个准确的构造来处理这个问题:(或)并且几乎没有共同点(作为一个共同的父级,它的方法是不够的)。CollectionIterableObject[]Object

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

一个非常简化的例子是:

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);
    }
}

我相信上面的方法非常通用。您不依赖于如何迭代某个源,您可以有选择地修改它们。


答案 2

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

数组和集合是否有通用接口或超类?

但你真正的问题在身体里:

有没有其他可能性可以在一个循环中处理两者,就像上面一样?

答案是:不,没有办法编写一个同时迭代集合和数组的 for 循环。

你可以跳过一堆箍,把数组变成列表,但你几乎肯定会得到一个更大的混乱,而不是你只是写了两个(或更多)循环。呼叫会告诉您您拥有什么,但是如果没有某种演员,您仍然无法使用它。 不适用于基元数组。getClass().isArray()Arrays.asList()