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

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

Предполагая, что массивы реализуют 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 работает как с коллекциями, так и с массивами.

Ответы [ 6 ]

18 голосов
/ 09 апреля 2019

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

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

10 голосов
/ 09 апреля 2019

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

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

Но ваш реальныйвопрос в теле:

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

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

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

6 голосов
/ 09 апреля 2019

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

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

}

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

5 голосов
/ 09 апреля 2019

Вы можете проверить, является ли объект массивом, используя isArray() метод из Class

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

}

Edit:

С точки зрения перебора этого 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 голосов
/ 09 апреля 2019

Массивы - это объекты:

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());
4 голосов
/ 09 апреля 2019

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

@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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...