Существуют ли стандартные классы 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]