Dlaczego kolekcje Java nie mogą bezpośrednio przechowywać typów pierwotnych?

123

Kolekcje Java przechowują tylko obiekty, a nie typy pierwotne; jednak możemy przechowywać klasy opakowania.

Dlaczego to ograniczenie?

JavaUser
źródło
2
To ograniczenie jest do niczego, gdy masz do czynienia z prymitywami i chcesz używać kolejek do wysyłania, a stawki wysyłania są bardzo szybkie. Mam teraz do czynienia z tym problemem, ponieważ autoboxing zajmuje zbyt dużo czasu.
JPM
Technicznie rzecz biorąc, typy prymitywne to obiekty (dokładnie takie pojedyncze instancje), po prostu nie są one definiowane przez classmaszynę JVM. Instrukcja int i = 1definiuje wskaźnik do pojedynczej instancji obiektu, który definiuje intw JVM, ustawioną na wartość 1zdefiniowaną gdzieś w JVM. Tak, wskaźniki w Javie - jest to po prostu oddzielone od użytkownika przez implementację języka. Prymitywy nie mogą być używane jako typy ogólne, ponieważ język przewiduje, że wszystkie typy ogólne muszą być typu nadrzędnego Object- stąd dlaczego A<?>kompiluje się do A<Object>w czasie wykonywania.
Robert E Fry
1
Typy prymitywne @RobertEFry nieobiektami w Javie, więc wszystko, co napisałeś o pojedynczych instancjach, jest zasadniczo błędne i mylące. Proponuję przeczytać rozdział „Typy, wartości i zmienne” specyfikacji języka Java, który definiuje, czym jest obiekt: „Obiekt (§4.3.1) to dynamicznie utworzona instancja typu klasy lub dynamicznie tworzona tablica. "
typeracer

Odpowiedzi:

99

To była decyzja projektowa w Javie i niektórzy uważają ją za błąd. Kontenery chcą Objects i prymitywy nie pochodzą od Object.

Jest to jedyne miejsce, którego projektanci .NET nauczyli się od JVM i zaimplementowali typy wartości i typy ogólne, tak że w wielu przypadkach wyeliminowano boks. W środowisku CLR kontenery ogólne mogą przechowywać typy wartości jako część podstawowej struktury kontenera.

Java zdecydowała się na dodanie ogólnej obsługi w 100% w kompilatorze bez wsparcia ze strony JVM. JVM jest tym, czym jest, nie obsługuje obiektu „niebędącego obiektem”. Java generics pozwala udawać, że nie ma opakowania, ale nadal płacisz cenę wydajności boksu. Jest to WAŻNE w przypadku niektórych klas programów.

Boks to techniczny kompromis i wydaje mi się, że to szczegóły implementacji przeciekają do języka. Autoboxing jest fajnym cukrem syntaktycznym, ale nadal stanowi spadek wydajności. Jeśli już, chciałbym, aby kompilator ostrzegał mnie, gdy uruchamia się autobox. (Z tego co wiem, może teraz, napisałem tę odpowiedź w 2010 roku).

Dobre wyjaśnienie SO o boksie: Dlaczego niektóre języki wymagają boksu i rozpakowywania?

I krytyka dotycząca typów generycznych Javy: dlaczego niektórzy twierdzą, że implementacja typów ogólnych w Javie jest zła?

W obronie Javy łatwo jest spojrzeć wstecz i krytykować. JVM przetrwał próbę czasu i pod wieloma względami jest dobrym projektem.

codenheim
źródło
6
To nie pomyłka, starannie dobrany kompromis, który moim zdaniem bardzo dobrze służył Javie.
DJClayworth
13
Wystarczającym błędem było to, że .NET nauczył się od niego i zaimplementował autoboxing od samego początku, a generics na poziomie VM bez narzutu bokserskiego. Sama próba korekty w Javie była tylko rozwiązaniem na poziomie składni, które nadal cierpi z powodu wydajności autoboxingu w porównaniu z brakiem boksu. Implementacja Javy wykazała słabą wydajność przy dużych strukturach danych.
codenheim,
2
@mrjoltcola: IMHO, domyślny autoboxing jest błędem, ale powinien istnieć sposób na oznaczanie zmiennych i parametrów, które powinny automatycznie zaznaczać podane im wartości. Nawet teraz myślę, że powinien być dodany środek określający, że pewne zmienne lub parametry nie powinny akceptować nowo-auto-boxowanych wartości [np. Powinno być dozwolone przekazywanie Object.ReferenceEqualsreferencji typu, Objectktóre identyfikują liczby całkowite w pudełku, ale nie powinno być dozwolone przekazać wartość całkowitą]. Automatyczne rozpakowywanie Java jest po prostu nieprzyjemne.
supercat
18

Ułatwia wdrażanie. Ponieważ prymitywy Java nie są uważane za obiekty, należy utworzyć oddzielną klasę kolekcji dla każdego z tych prymitywów (brak kodu szablonu do współużytkowania).

Możesz to oczywiście zrobić, po prostu zobacz GNU Trove , Apache Commons Primitives lub HPPC .

O ile nie masz naprawdę dużych kolekcji, narzuty związane z opakowaniami nie mają wystarczającego znaczenia, aby ludzie mogli się nimi zająć (a jeśli masz naprawdę duże prymitywne kolekcje, możesz poświęcić wysiłek na przyjrzenie się użyciu / zbudowaniu dla nich wyspecjalizowanej struktury danych ).

Thilo
źródło
11

To połączenie dwóch faktów:

  • Prymitywy Java nie są typami referencyjnymi (np. An intnie jest Object)
  • Java robi generyczne, używając wymazywania typów typów referencyjnych (np. A List<?>jest naprawdę a List<Object>w czasie wykonywania)

Ponieważ obie te wartości są prawdziwe, ogólne kolekcje Java nie mogą bezpośrednio przechowywać typów pierwotnych. Dla wygody wprowadzono autoboxing, aby umożliwić automatyczne umieszczanie typów pierwotnych w ramkach jako typów referencyjnych. Nie popełnij jednak błędu, kolekcje i tak przechowują odniesienia do obiektów.

Czy można było tego uniknąć? Być może.

  • Jeśli a intjest Object, to w ogóle nie ma potrzeby stosowania typów pudełek.
  • Jeśli typy generyczne nie są wykonywane przy użyciu wymazywania typów, wówczas prymitywy mogły zostać użyte dla parametrów typu.
smary wielogenowe
źródło
8

Istnieje pojęcie auto-boxingu i auto-unboxing. Jeśli spróbujesz zapisać plik intw pliku, List<Integer>kompilator Java automatycznie przekonwertuje go na plik Integer.

Jeremy
źródło
1
Autoboxing został wprowadzony w Javie 1.5 wraz z Generics.
Jeremy
1
Ale to kwestia czasu kompilacji; cukier składniowy bez korzyści dla wydajności. Kompilator Java jest autoboxami, stąd spadek wydajności w porównaniu z implementacjami maszyn wirtualnych, takimi jak .NET, które generyczne nie obejmują boksu.
codenheim,
1
@mrjoltcola: O co ci chodzi? Po prostu dzieliłem się faktami, a nie kłóciłem się.
Jeremy
3
Chodzi mi o to, że ważne jest wskazanie różnicy między składnią a wydajnością. Uważam również, że moje uwagi dotyczą wymiany faktów, a nie argumentów. Dzięki.
codenheim,
2

To naprawdę nie jest ograniczenie, prawda?

Zastanów się, czy chcesz utworzyć kolekcję przechowującą wartości pierwotne. Jak napisałeś kolekcję, która może przechowywać int, float lub char? Najprawdopodobniej skończysz z wieloma kolekcjami, więc będziesz potrzebować intlisty, listy charlist itp.

Korzystając z obiektowego charakteru Javy, kiedy piszesz klasę kolekcji, może ona przechowywać dowolny obiekt, więc potrzebujesz tylko jednej klasy kolekcji. Ten pomysł, polimorfizm, jest bardzo potężny i znacznie upraszcza projektowanie bibliotek.

Vincent Ramdhanie
źródło
7
„Jak napisałeś kolekcję, która może przechowywać int, float lub char?” - Z odpowiednio zaimplementowanymi rodzajami / szablonami, takimi jak inne języki, które nie płacą kary za udawanie, że wszystko jest przedmiotem.
codenheim
Prawie nigdy przez sześć lat Java nie chciała przechowywać kolekcji prymitywów. Nawet w nielicznych przypadkach, w których mógłbym chcieć, aby dodatkowe koszty związane z czasem i przestrzenią korzystania z obiektów odniesienia były znikome. W szczególności stwierdzam, że wiele osób uważa, że ​​chce Map <int, T>, zapominając, że tablica bardzo dobrze zrobi tę sztuczkę.
DJClayworth
2
@DJClayworth Działa to dobrze tylko wtedy, gdy wartości kluczy nie są rzadkie. Oczywiście możesz użyć tablicy pomocniczej do śledzenia kluczy, ale ma to swoje własne problemy: stosunkowo wydajny dostęp wymagałby posortowania obu tablic w oparciu o kolejność kluczy, aby umożliwić wyszukiwanie binarne, co z kolei spowodowałoby wstawianie i usuwanie nieefektywne, chyba że wstawianie / usuwanie jest wzorowane tak, że wstawione elementy prawdopodobnie skończą tam, gdzie był poprzednio usunięty element i / lub niektóre bufory są przeplatane w tablicach itp. Są dostępne zasoby, ale byłoby miło mieć je w Sama Java.
JAB
@JAB Właściwie działa dobrze, jeśli klucze są rzadkie, po prostu potrzebuje więcej pamięci niż gdyby nie były rzadkie. A jeśli ich klucze są rzadkie, oznacza to, że nie ma ich wiele, a użycie liczby całkowitej jako klucza działa dobrze. Użyj dowolnej metody wymagającej najmniejszej ilości pamięci. Lub cokolwiek masz ochotę, jeśli cię to nie obchodzi.
DJClayworth
0

Myślę, że możemy zobaczyć postęp w tej przestrzeni w JDK prawdopodobnie w Javie 10 opartej na tym JEP - http://openjdk.java.net/jeps/218 .

Jeśli chcesz dziś uniknąć prymitywów boksu w kolekcjach, istnieje kilka alternatyw innych firm. Oprócz wspomnianych wcześniej opcji stron trzecich dostępne są również Eclipse Collections , FastUtil i Koloboke .

Niedawno opublikowano również porównanie pierwotnych map pod tytułem: Large HashMap overview: JDK, FastUtil, Goldman Sachs, HPPC, Koloboke, Trove . Biblioteka GS Collections (Goldman Sachs) została przeniesiona do Eclipse Foundation i obecnie nosi nazwę Eclipse Collections.

Donald Raab
źródło
0

Głównym powodem jest strategia projektowania w Javie. ++ 1) kolekcje wymagają obiektów do manipulacji, a prymitywy nie są wyprowadzane z obiektu, więc może to być inny powód. 2) Prymitywne typy danych Java nie są typami referencyjnymi dla np. int nie jest przedmiotem.

Przezwyciężyć:-

mamy koncepcję auto-boxingu i auto-unboxingu. więc jeśli próbujesz przechowywać prymitywne typy danych, kompilator automatycznie przekonwertuje je na obiekt tej pierwotnej klasy danych.

user5693566
źródło