Чому масив не призначається Iterable?


186

з Java5 ми можемо написати:

Foo[] foos = ...
for (Foo foo : foos) 

або просто за допомогою Iterable в циклі for. Це дуже зручно.

Однак ви не можете записати загальний метод для такого вибору:

public void bar(Iterable<Foo> foos) { .. }

і викликає його масивом, оскільки він не є взаємодіючим:

Foo[] foos = { .. };
bar(foos);  // compile time error 

Мені цікаво з причин цього дизайнерського рішення.


8
Arrays.asList досить хороший, я думаю
dfa

17
це філософське питання
dfa

2
вагомою причиною для вирішення масивів у Java 5+ є методи varargs.
Джефф Уокер

2
@Torsten: правда, але якщо ви передаєте його методу, який приймає Iterable, ви, швидше за все, не вносите жодних змін.
Майкл Майєрс

5
Насправді, Arrays.asList недостатньо хороший, тому що він не працює на масивах примітивних типів. Єдиний вбудований спосіб загальної ітерації (коробки) елементів примітивних типів - це відображення, використання java.lang.reflect.Array, але його ефективність є слабкою. Однак ви можете написати власні ітератори (або реалізації Списку!), Щоб обернути масиви примітивних типів, якщо хочете.
Боан

Відповіді:


78

Масиви можуть реалізовувати інтерфейси ( Cloneableта java.io.Serializable). То чому б і ні Iterable? Я думаю, що Iterableсили додають iteratorметод, а масиви не реалізують методи. char[]навіть не перекриває toString. У будь-якому випадку масиви посилань слід вважати менш ідеальними - використовувати Lists. Як зауважує dfa, Arrays.asListконверсія зробить для вас явно.

(Сказавши це, ви можете зателефонувати cloneна масиви.)


23
> "... і масиви не реалізують методи." Я думаю, це ще одне філософське питання; масиви не були ніколи примітивними типами, і філософія Java зазначає, що "Все є об'єктом (крім примітивних типів)". Чому тоді масиви не реалізують методів, хоча є газільйонні операції, для яких хотілося б використовувати масив з самого початку. О, це правильно, масиви були єдиними сильно типізованими колекціями до того, як дженерики з'явилися як жалюгідний огляд.
fatuhoku

2
Якщо у вас є дані в масиві, можливо, ви виконуєте критичні роботи низького рівня, такі як робота з читанням байтів [] з потоків. Неможливість ітерації масивів, ймовірно, випливає з дженерики Java, яка не підтримує примітиви як аргументи типу, як говорить @Gareth нижче.
Дрю Ноакс

2
@FatuHoku Запропонувати дженерики, на жаль, заперечення невірно. Бажаність дженериків завжди цінувалася. Масиви не примітивні (я не казав, що це було), але вони низькорівневі. Єдине, що ви хочете зробити з масивами - це використовувати їх як деталі реалізації для векторних структур.
Том Хотін - тайклін

1
Iterator<T>також вимагає remove(T), хоча це дозволяється кидати UnsupportedOperationException.
wchargin

Масиви дійсно реалізують методи: вони реалізують усі методи java.lang.Object.
mhsmith

59

Масив - це Об'єкт, але його елементів може бути не так. Масив може містити примітивний тип типу int, з яким Iterable не може впоратися. Принаймні, це я вважаю.


3
Це означає, що для підтримки Iterableінтерфейсу примітивні масиви повинні бути спеціалізовані для використання класів обгортки. Але нічого з цього насправді не велика справа, оскільки параметри типу все одно підроблені.
thejoshwolfe

8
Це не завадить масивам об'єктів реалізувати Iterable. Це також не завадить примітивним масивам реалізовувати Iterable для обгортаного типу.
Боан

1
Автобоксинг міг би впоратися з цим
Тім Бют

Я думаю, це правильна причина. Це не буде задовільно для масивів примітивних типів, поки Generics не підтримує примітивні типи (наприклад, List<int>замість List<Integer>тощо). Злом можна зробити за допомогою обгортки, але при втраті продуктивності - і що ще важливіше - якщо цей злом був зроблений, це запобіжить його правильному впровадженню в Java в майбутньому (наприклад int[].iterator(), назавжди буде заблоковано повернення, Iterator<Integer>а не Iterator<int>). Можливо, майбутні типи значень + загальна спеціалізація для Java (проект valhalla) змусить масиви реалізуватися Iterable.
Bjarke

16

Масиви повинні підтримувати Iterable, вони просто ні, з тієї ж причини, що .NET-масиви не підтримують інтерфейс, який дозволяє лише випадковий доступ за позицією (немає такого інтерфейсу, який визначений як стандартний). В основному, рамки часто мають набридливі невеликі прогалини в них, які не варто нікому виправляти. Не має значення, чи могли ми самі їх виправити якимсь оптимальним способом, але часто ми не можемо.

ОНОВЛЕННЯ: Щоб бути рівномірним, я згадав масиви .NET, які не підтримують інтерфейс, який підтримує випадковий доступ за позицією (див. Також мій коментар). Але в .NET 4.5 цей точний інтерфейс був визначений і підтримується масивами та List<T>класом:

IReadOnlyList<int> a = new[] {1, 2, 3, 4};
IReadOnlyList<int> b = new List<int> { 1, 2, 3, 4 };

Все ще не зовсім ідеально, оскільки інтерфейс змінного списку IList<T>не успадковує IReadOnlyList<T>:

IList<int> c = new List<int> { 1, 2, 3, 4 };
IReadOnlyList<int> d = c; // error

Можливо, є можлива зворотна сумісність із такою зміною.

Якщо в нових версіях Java є якийсь прогрес щодо подібних речей, мені було б цікаво дізнатись у коментарях! :)


8
.NET-масиви реалізують інтерфейс IList
Том Гіллен

2
@Aphid - Я сказав тільки для читання довільного доступу. IList<T>виставляє операції щодо модифікації. Було б чудово , якби IList<T>успадкували що - щось на зразок IReadonlyList<T>інтерфейсу, який би тільки що був Countі T this[int]і успадкованим IEnumerable<T>(який вже підтримує тільки для читання перерахування). Ще однією чудовою річчю буде інтерфейс для отримання переліку обернених порядків, до якого Reverseможе розібратися метод розширення (подібно до того, як Countзапитує метод розширення для ICollectionоптимізації самого себе)
Daniel Earwicker

Так, було б набагато краще, якби все було розроблено саме так. Інтерфейс IList визначає властивості IsReadOnly та IsFixedSize, які належним чином реалізуються масивом. Це завжди вражало мене як дуже поганий спосіб зробити це, хоча, оскільки він не вимагає часу компіляції, перевіряючи, чи вказаний вам список насправді читається лише зараз, і я дуже рідко бачу код, який перевіряє ці властивості.
Том Гіллен

1
Масиви в .NET впроваджувати IList& ICollectionз .NET 1.1 IList<T>і ICollection<T>з .NET 2.0. Це ще один випадок, коли Java значно відстає від конкуренції.
Амір Абірі

@TomGillen: Моя найбільша проблема в IListтому, що вона не надає більше атрибутів, що піддаються пошуку. Я б сказав, що правильний набір повинен включати IsUpdateable, IsResizable, IsReadOnly, IsFixedSize та ExistingElementsAreImmutable для початківців. Питання про те, чи може посилання коду без зміни клавіш змінювати список, є окремим від питань, чи може код, який містить посилання на список, який він не повинен змінювати, може безпечно ділитися цим посиланням безпосередньо із зовнішнім кодом, чи можна сміливо припускати, що якийсь аспект списку ніколи не зміниться.
supercat

14

На жаль, масиви не є « classнедостатньо». Вони не реалізують Iterableінтерфейс.

У той час як масиви тепер є об'єктами, які реалізують Clonable і Serializable, я вважаю, що масив не є об'єктом у нормальному розумінні та не реалізує інтерфейс.

Причина, по якій ви можете використовувати їх для кожної петлі, полягає в тому, що Sun додає в якийсь синтатичний цукор для масивів (це особливий випадок).

Оскільки масиви почалися як «майже об’єкти» з Java 1, змінити їх було б занадто різко, щоб зробити їх справжніми об’єктами на Java.


14
Тим не менш, є цукор для кожної петлі, так чому ж не може бути цукру для Iterable?
Майкл Майерс

8
@mmyers: Цукор, що використовується для кожного, - це цукор, який складається за час . Це набагато простіше зробити, ніж цукор ВМ . Сказавши, які .NET масиви значно кращі в цій області ...
Джон Скіт

12
Масиви можуть реалізовувати інтерфейси. Вони реалізують Cloneableта Serializableінтерфейси.
notnoop

34
масиви java - це об'єкти у всіх сенсах. Видаліть цю частину дезінформації. Вони просто не реалізують Iterable.
ікаганович

5
Масив - об’єкт. Він підтримує корисні : P методи, такі як wait (), wait (n), wait (n, m), notify (), notifyAll (), finalize (), безглузда реалізація toString () Єдиним корисним методом є getClass () .
Пітер Лорі

1

Компілятор фактично переводить for eachмасив на простий forцикл із змінною лічильника.

Складання наступного

public void doArrayForEach() {
    int[] ints = new int[5];

    for(int i : ints) {
        System.out.println(i);
    }
}

а потім декомпілювати вихід файлів .class

public void doArrayForEach() {
    int[] ints = new int[5];
    int[] var2 = ints;
    int var3 = ints.length;

    for(int var4 = 0; var4 < var3; ++var4) {
        int i = var2[var4];
        System.out.println(i);
    }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.