Могу ли я написать цикл for, который перебирает как коллекции, так и массивы? - программирование
Подтвердить что ты не робот

Могу ли я написать цикл for, который перебирает как коллекции, так и массивы?

Есть ли возможность проверить, является ли объект массивом или коллекцией с одним предложением? Чего я пытаюсь достичь:

Предполагая, что массивы реализуют Iterable, и предполагая, что Object foo может быть либо массивом, либо коллекцией, я хотел бы использовать фрагмент кода, подобный следующему:

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

К сожалению, массив не может быть приведен к Iterable. Он также не реализует коллекцию. Есть ли другие возможности для обработки обоих в одном цикле, как указано выше? Вместо - конечно - использования if-else if-условия и двух циклов (что было бы неплохо).

Изменение: в ответ на эти ответы. Я знаю о методе isArray(), но в этом случае приведение

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

не удастся. Это было бы жаль и избыточность кода, поскольку мне пришлось бы использовать два цикла, хотя цикл foreach работает как с коллекциями, так и с массивами.

4b9b3361

Ответ 1

Остальные ответы все стараются ответить на первоначальный заглавный вопрос:

Есть ли общий интерфейс или суперкласс для массивов и коллекций?

Но ваш настоящий вопрос в теле:

Есть ли другие возможности для обработки обоих в одном цикле, как указано выше?

Ответ таков: нет, нет способа написать единственный цикл for который выполняет итерации как для коллекций, так и для массивов.

Вы можете перепрыгнуть через кучу обручей, чтобы превратить массивы в списки, но вы почти наверняка получите больший беспорядок, чем если бы вы просто написали два (или более) цикла. Вызов getClass().isArray() сообщает вам, что у вас есть, но вы все равно не сможете работать с ним без какого-либо приведения. Arrays.asList() не работает для массивов примитивов.

Ответ 2

Относительно условия, чтобы проверить, является ли 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

Что касается общего цикла, который будет работать как с массивами, так и с коллекциями:

Вы просто не можете написать точную конструкцию, чтобы справиться с этим: Collection (или Iterable) и Object[] имеют мало общего (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);
    }
}

Я считаю, что приведенный выше подход довольно универсален. Вы не зависите от того, как определенный источник повторяется, вы можете выборочно изменять их.

Ответ 3

В зависимости от того, что вы пытаетесь сделать, вы можете реализовать два похожих метода:

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

}

Я не уверен, поможет ли это вам, потому что у вас, кажется, уже есть объект в переменной где-то. Но если вы сможете решить эту проблему на один уровень выше, это может быть полезно.

Ответ 4

Вы можете проверить, является ли объект массивом, используя 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
}

Ответ 5

Вы можете написать вспомогательный метод для этого:

@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

Ответ 6

Массивы являются объектами:

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

Ответ 7

В java массив имеет Object качестве суперкласса, поэтому вы не можете просто проверить с помощью instanceOf. Я думаю, что вам нужно что-то вроде этого: fooobar.com/questions/92301/...