Могу ли я написать цикл 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 работает как с коллекциями, так и с массивами.
Ответы
Ответ 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/...
Ответ 8
Вы можете посмотреть это в Javadocs. Я считаю, что Array расширяет объект. Посмотрите сами (проверьте, какую версию Java вы используете)
https://docs.oracle.com/javase/8/docs/api/java/util/Arrays.html