Dlaczego tablicy nie można przypisać do Iterable?

186

z Java5 możemy napisać:

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

lub po prostu używając Iterable w pętli for. To jest bardzo przydatne.

Nie można jednak napisać ogólnej metody iterowalnej:

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

i wywoływanie go tablicą, ponieważ nie jest to Iterable:

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

Zastanawiam się nad przyczynami tej decyzji projektowej.

dfa
źródło
8
Arrays.asList jest wystarczająco dobry, przypuszczam
dfa
17
jest to pytanie filozoficzne
dfa
2
dobrym powodem do radzenia sobie z tablicami w Javie 5+ są metody varargs.
Jeff Walker,
2
@Torsten: prawda, ale jeśli podajesz metodę, która akceptuje Iterable, prawdopodobnie i tak nie wprowadzasz żadnych zmian.
Michael Myers
5
W rzeczywistości Arrays.asList nie jest wystarczająco dobry, ponieważ nie działa na tablicach typów pierwotnych. Jedynym wbudowanym sposobem generalnego iteracji (pudełkowanych) elementów typów pierwotnych jest użycie odbicia java.lang.reflect.Array, ale jego wydajność jest słaba. Możesz jednak pisać własne iteratory (lub implementacje List!), Aby zawijać tablice typów pierwotnych, jeśli chcesz.
Boann

Odpowiedzi:

78

Tablice mogą implementować interfejsy ( Cloneablei java.io.Serializable). Więc dlaczego nie Iterable? Myślę, że Iterablewymusza dodanie iteratormetody, a tablice nie implementują metod. char[]nawet nie zastępuje toString. W każdym razie tablice referencji powinny być uważane za mniej niż idealne - użyj Lists. Jak komentuje dfa, Arrays.asListzrobi to za Ciebie, jawnie.

(Powiedziawszy to, możesz wywoływać clonetablice).

Tom Hawtin - tackline
źródło
23
> „... a tablice nie implementują metod.” Myślę, że to kolejne pytanie filozoficzne; tablice nigdy nie były typami prymitywnymi, a filozofia Java głosi, że „wszystko jest przedmiotem (z wyjątkiem typów prymitywnych)”. Dlaczego więc tablice nie implementują metod, mimo że istnieją operacje gazillionowe, dla których od początku chcielibyśmy użyć tablicy? Och, zgadza się, tablice były jedynymi kolekcjami o silnym typie, zanim pojawiły się ogólne, jako przykra perspektywa.
fatuhoku
2
Jeśli masz dane w tablicy, możliwe, że wykonujesz prace na niskim poziomie, mające krytyczne znaczenie dla wydajności, takie jak odczyt bajtów [] ze strumieni. Niemożność iteracji tablic prawdopodobnie wynika z tego, że generics Java nie obsługują prymitywów jako argumentów typu, jak napisano poniżej @Gareth.
Drew Noakes,
2
@FatuHoku Sugerowanie, że ogólne zasady były przykrością, jest niepoprawne. Zawsze doceniano celowość leków generycznych. Tablice nie są prymitywne (nie powiedziałem, że były), ale są na niskim poziomie. Jedną rzeczą, którą chcesz zrobić z tablicami, jest użycie ich jako szczegółów implementacji struktur podobnych do wektorowych.
Tom Hawtin - tackline
1
Iterator<T>wymaga również remove(T), choć wolno rzucać UnsupportedOperationException.
wchargin
Tablice implementują metody: implementują wszystkie metody java.lang.Object.
mhsmith
59

Tablica jest Obiektem, ale jej elementy mogą nie być. Tablica może zawierać prymitywny typ, taki jak int, z którym Iterable nie może sobie poradzić. Przynajmniej tak myślę.

Gareth Adamson
źródło
3
Oznacza to, że aby obsługiwać Iterableinterfejs, pierwotne tablice muszą być wyspecjalizowane w użyciu klas opakowujących. Nie jest to jednak nic wielkiego, ponieważ parametry typu i tak są fałszywe.
thejoshwolfe
8
Nie uniemożliwiłoby to tablicom obiektów implementacji Iterable. Nie uniemożliwiłoby to również prymitywnym tablicom implementacji Iterable dla typu opakowanego.
Boann
1
Autoboxing mógłby sobie z tym poradzić
Tim Büthe,
Myślę, że to właściwy powód. Nie będzie działał zadowalająco dla tablic typów pierwotnych, dopóki Rodzajowe nie obsługują typów pierwotnych (np. List<int>Zamiast List<Integer>itp.). Hack można zrobić z opakowania, ale ze stratą wydajności - i co ważniejsze - czy to hack, zostało zrobione, it'ld zapobiec wdrażaniu go poprawnie w języku Java w przyszłości (na przykład int[].iterator()na zawsze zostać zablokowane, aby powrócić Iterator<Integer>zamiast Iterator<int>). Być może nadchodzące typy wartości + ogólna specjalizacja dla Java (projekt valhalla) wprowadzą tablice Iterable.
Bjarke
16

Tablice powinny obsługiwać Iterable, po prostu nie, z tego samego powodu, dla którego tablice .NET nie obsługują interfejsu, który umożliwia tylko losowy dostęp według pozycji (nie ma takiego interfejsu zdefiniowanego jako standard). Zasadniczo frameworki często mają irytujące małe luki, których naprawienie nie jest warte nikogo. Nie miałoby znaczenia, czy sami moglibyśmy je naprawić w optymalny sposób, ale często nie możemy.

AKTUALIZACJA: Aby być zręcznym, wspomniałem o macierzach .NET, które nie obsługują interfejsu, który obsługuje losowy dostęp według pozycji (patrz także mój komentarz). Ale w .NET 4.5 ten interfejs został zdefiniowany i jest obsługiwany przez tablice i List<T>klasę:

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

Wszystko wciąż nie jest do końca idealne, ponieważ interfejs listy zmiennych IList<T>nie dziedziczy IReadOnlyList<T>:

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

Być może istnieje taka gotowa kompatybilność wsteczna z taką zmianą.

Jeśli pojawią się jakieś postępy w podobnych sprawach w nowszych wersjach Javy, chciałbym wiedzieć w komentarzach! :)

Daniel Earwicker
źródło
8
Macierze .NET implementują interfejs IList
Tom Gillen
2
@Aphid - powiedziałem tylko do odczytu losowy dostęp. IList<T>ujawnia operacje modyfikacji. Byłoby wspaniale, gdyby IList<T>odziedziczyła coś jak IReadonlyList<T>interfejs, który właśnie miał Counti T this[int]i odziedziczonej IEnumerable<T>(który już obsługuje wyliczanie tylko do odczytu). Kolejną świetną rzeczą byłby interfejs do uzyskiwania modułu wyliczającego w odwrotnym porządku, o który Reversemetoda rozszerzenia mogłaby zapytać (podobnie jak Countmetoda rozszerzenia pyta się o ICollectionjej optymalizację).
Daniel Earwicker 25.11.11
Tak, byłoby znacznie lepiej, gdyby rzeczy zostały zaprojektowane w ten sposób. Interfejs IList definiuje właściwości IsReadOnly i IsFixedSize, które są odpowiednio implementowane przez tablicę. To zawsze wydawało mi się bardzo złym sposobem na zrobienie tego, ponieważ nie oferuje czasu kompilacji sprawdzania, czy podana lista jest w rzeczywistości tylko do odczytu, a bardzo rzadko widzę kod, który sprawdza te właściwości.
Tom Gillen
1
Tablice w .NET implementują IListi ICollectionod .NET 1.1 IList<T>i ICollection<T>od .NET 2.0. To kolejny przypadek, w którym Java jest daleko w tyle za konkurencją.
Amir Abiri
@TomGillen: Moim największym problemem IListjest to, że nie dostarcza więcej zapytań o atrybuty. Powiedziałbym, że odpowiedni zestaw powinien zawierać IsUpdateable, IsResizable, IsReadOnly, IsFixedSize i ExistingElementsAreImmutable na początek. Pytanie, czy kod referencyjny może, bez rzutowania, modyfikować listę, jest odrębne od pytania, czy kod, który zawiera referencję do listy, której nie powinien modyfikować, może bezpiecznie udostępniać to odniesienie bezpośrednio z kodem zewnętrznym, czy też można bezpiecznie założyć, że jakiś aspekt listy nigdy się nie zmieni.
supercat
14

Niestety tablice nie są „ classwystarczające”. Nie implementują Iterableinterfejsu.

Podczas gdy tablice są teraz obiektami, które implementują Klonowalne i Serializowalne, uważam, że tablica nie jest obiektem w normalnym znaczeniu i nie implementuje interfejsu.

Powodem, dla którego można ich używać w pętlach dla każdej pętli, jest to, że Słońce dodało cukier syntetyczny do tablic (jest to szczególny przypadek).

Ponieważ tablice zaczęły się jako „prawie obiekty” w Javie 1, byłoby zbyt drastyczne zmiany, aby uczynić je prawdziwymi obiektami w Javie.

jjnguy
źródło
14
Nadal jest cukier dla każdej pętli, więc dlaczego nie może być cukru dla Iterable?
Michael Myers
8
@mmyers: Cukier stosowany w każdym z nich to cukier w czasie kompilacji . Jest to o wiele łatwiejsze niż cukier VM . Powiedziawszy to, macierze .NET są znacznie lepsze w tym obszarze ...
Jon Skeet
12
Tablice mogą implementować interfejsy. Implementują Cloneablei Serializableinterfejsy.
notnoop
34
tablice java są obiektami pod każdym względem. Usuń ten fragment dezinformacji. Po prostu nie implementują iterowalności.
ykaganovich
5
Tablica jest obiektem. Obsługuje przydatne : metody P, takie jak wait (), wait (n), wait (n, m), powiadomienie (), powiadomienie All (), finalizacja (), bezcelowa implementacja toString () Jedyną przydatną metodą jest getClass () .
Peter Lawrey,
1

Kompilator faktycznie tłumaczy for eachtablicę na prostą forpętlę ze zmienną licznika.

Kompilowanie następujących

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

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

a następnie dekompilacja pliku .class daje

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);
    }
}
das Keks
źródło