Существуют ли стандартные классы Java, реализующие Iterable без реализации Collection?

У меня есть загадка, которая заставила меня задуматься о том, существуют ли стандартные классы Java, которые реализуют Iterable<T>, не реализуя Collection<T>. Я реализую один интерфейс, который требует от меня определить метод, который принимает Iterable<T>, но для этого объекта, который я использую для поддержки этого метода, требуется Collection<T>.

У меня есть некоторый действительно kludgy код чувства, который дает некоторые непроверенные предупреждения при компиляции.

public ImmutableMap<Integer, Optional<Site>> loadAll(
        Iterable<? extends Integer> keys
) throws Exception {
    Collection<Integer> _keys;
    if (keys instanceof Collection) {
        _keys = (Collection<Integer>) keys;
    } else {
        _keys = Lists.newArrayList(keys);
    }

    final List<Site> sitesById = siteDBDao.getSitesById(_keys);
    // snip: convert the list to a map

Изменение моей итоговой коллекции для использования более генерируемого типа Collection<? extends Integer> не устраняет непроверенного предупреждения для этой строки. Кроме того, я не могу изменить подпись метода, чтобы принять Collection вместо Iterable, потому что тогда он больше не переопределяет супер-метод и не будет вызван при необходимости.

Там не похоже на проблему с этой литой или копией: другие вопросы заданы здесь в другом месте и, похоже, глубоко внедрены в Java родовых и типов стирающих систем. Но я спрашиваю, есть ли когда-нибудь какие-либо классы, которые могут реализовать Iterable<T>, которые также не реализуют Collection<T>? Я просмотрел Iterable JavaDoc, и, конечно же, все, что я ожидаю, будет передано моему интерфейсу, будет фактически коллекцией. Я бы предпочел использовать ранее написанный класс in-the-wild, предварительно написанный, так как это гораздо вероятнее всего будет передаваться как параметр и сделает unit test намного более ценным.

Я уверен, что бит, созданный мной или копией, который я написал, работает с типами, которые я использую в моем проекте, из-за некоторых модульных тестов, которые я пишу. Но я бы хотел написать unit test для некоторого ввода, который является итерируемым, но не является коллекцией, и до сих пор все, что я смог придумать, сам реализует реализацию класса фиктивного теста.


Для любопытного метода, который я реализую, является Guava CacheLoader<K, V>.loadAll(Iterable<? extends K> keys), а метод backing - это объект, созданный с помощью JDBI-объекта, который требует, чтобы коллекция использовалась как тип параметра для @BindIn интерфейса. Я думаю, что я прав, думая, что это касается вопроса, но на всякий случай кто-то хочет попробовать боковое мышление по моей проблеме. Я знаю, что я мог просто разветкить проект JDBI и переписать аннотацию @BindIn, чтобы принять итеративный...

Ответ 1

После прочтения отличных ответов и предоставленных документов я пошарил еще в нескольких классах и нашел, что выглядит победителем, как с точки зрения простоты для тестового кода, так и для прямого заголовка вопроса. Java main ArrayList реализация содержит этот драгоценный камень:

public Iterator<E> iterator() {
    return new Itr();
}

Где Itr - частный внутренний класс с оптимизированной, настраиваемой реализацией Iterator<E>. К сожалению, Iterator сам не реализует Iterable, поэтому, если я хочу, чтобы он загружал его в мой вспомогательный метод для проверки пути кода, который не выполняет бросок, я должен обернуть его в свой собственный класс нежелательной почты, реализует Iterable (а не Collection) и возвращает Itr. Это удобный способ легко превратить коллекцию в Iterable без необходимости самостоятельно писать итерационный код.

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

@GwtCompatible(serializable = true)
public static <E> ArrayList<E> More ...newArrayList(Iterable<? extends E> elements) {
  checkNotNull(elements); // for GWT
  // Let ArrayList sizing logic work, if possible
  if (elements instanceof Collection) {
    @SuppressWarnings("unchecked")
    Collection<? extends E> collection = (Collection<? extends E>) elements;
    return new ArrayList<E>(collection);
  } else {
    return newArrayList(elements.iterator());
  }
}

Ответ 2

Хотя нет класса, который бы сразу соответствовал вашим потребностям и был интуитивно понятен читателям вашего тестового кода, вы можете легко создать свой собственный анонимный класс, который легко понять:

static Iterable<Integer> range(final int from, final int to) {
    return new Iterable<Integer>() {
        public Iterator<Integer> iterator() {
            return new Iterator<Integer>() {
                int current = from;
                public boolean hasNext() { return current < to; }
                public Integer next() {
                    if (!hasNext()) { throw new NoSuchElementException(); }
                    return current++;
                }
                public void remove() { /*Optional; not implemented.*/ }
            };
        }
    };
}

Демоверсия

Эта реализация анонимна и не реализует Collection<Integer>. С другой стороны, он создает непустую перечислимую последовательность целых чисел, которую вы можете полностью контролировать.

Ответ 3

Чтобы ответить на вопрос в соответствии с заголовком:

Существуют ли стандартные классы Java, реализующие Iterable без реализации Collection?

Из текста:

Если существуют какие-либо классы, которые могут реализовать Iterable<T>, которые также не реализуют Collection<T>?

Ответ:

Да

См. следующую страницу javadoc: https://docs.oracle.com/javase/8/docs/api/java/lang/class-use/Iterable.html

В любом разделе, который говорит Classes in XXX that implement Iterable, будут перечислены стандартные классы Java, реализующие интерфейс. Многие из них не реализуют Collection.

Ответ 4

Kludgy, да, но я думаю, что код

Collection<Integer> _keys;
if (keys instanceof Collection) {
    _keys = (Collection<Integer>) keys;
} else {
    _keys = Lists.newArrayList(keys);
}

отлично звучит. Интерфейс Collection<T> расширяет Iterable<T>, и вам не разрешается реализовывать один и тот же интерфейс с двумя разными параметрами типа, поэтому нет возможности реализовать класс Collection<String> и Iterable<Integer>.

Класс Integer является окончательным, поэтому разница между Iterable<? extends Integer> и Iterable<Integer> в значительной степени академическая.

Взятые вместе, последние 2 параграфа доказывают, что если что-то есть как Iterable<? extends Integer> и a Collection, оно должно быть Collection<Integer>. Поэтому ваш код гарантированно будет безопасным. Компилятор не может быть уверен в этом, поэтому вы можете подавить предупреждение, написав

@SuppressWarnings("unchecked")

над выражением. Вы также должны указать комментарий аннотации, чтобы объяснить, почему код безопасен.

Что касается вопроса о том, существуют ли какие-либо классы, реализующие Iterable, но не Collection, так как другие указали, что ответ да. Однако я думаю, что вы действительно спрашиваете, есть ли смысл иметь два интерфейса. Многие другие спрашивают об этом. Часто, когда метод имеет аргумент Collection (например, addAll(), он может и, вероятно, должен быть Iterable.

Edit

@Andreas указал в комментариях, что Iterable был введен только в Java 5, тогда как Collection был введен в Java 1.2, и большинство существующих методов, принимающих Collection, не могли быть модернизированы, чтобы принять Iterable по причинам совместимости.

Ответ 5

В основных API-интерфейсах единственными типами Iterable, но не Collection -

interface java.nio.file.Path

interface java.nio.file.DirectoryStream
interface java.nio.file.SecureDirectoryStream

class java.util.ServiceLoader

class java.sql.SQLException (and subclasses)

Возможно, это все плохие проекты.

Ответ 6

Как упоминалось в @bayou.io, одна из таких реализаций для Iterable - это новый класс Path для обхода файловой системы, представленный на Java 7.

Если вы оказались на Java 8, Iterable был модифицирован (т.е. с учетом метода default) spliterator() (обратите внимание на его примечание к реализации), которое позволяет использовать его в сочетании с StreamSupport:

public static <T> Collection<T> convert(Iterable<T> iterable) {
    // using Collectors.toList() for illustration, 
    // there are other collectors available
    return StreamSupport.stream(iterable.spliterator(), false)
                        .collect(Collectors.toList());
}

Это происходит с небольшим расходом, что любой аргумент, который уже является реализацией Collection, проходит через ненужную операцию "поток и сбор". Вероятно, вы должны использовать его только в том случае, если желание стандартизованного подхода JDK перевешивает потенциальный удар производительности по сравнению с вашими оригинальными методами литья или Guava, которые, вероятно, будут спорными, поскольку вы уже используете Guava CacheLoader.

Чтобы проверить это, рассмотрите этот фрагмент и образец вывода:

// Snippet
System.out.println(convert(Paths.get(System.getProperty("java.io.tmpdir"))));
// Sample output on Windows
[Users, MyUserName, AppData, Local, Temp]