Od wersji Java 5 mamy boxing / unboxing typów pierwotnych, więc int
jest to opakowane java.lang.Integer
, i tak dalej i tak dalej.
Ostatnio widzę wiele nowych projektów Java (które zdecydowanie wymagają JRE co najmniej w wersji 5, jeśli nie 6), które używają int
raczej niż java.lang.Integer
, chociaż korzystanie z tego drugiego jest znacznie wygodniejsze, ponieważ ma kilka pomocniczych metod konwersji do long
wartości et al.
Dlaczego niektórzy nadal używają typów pierwotnych w Javie? Czy są jakieś wymierne korzyści?
java
primitive
primitive-types
autoboxing
jdk1.5
Naftuli Kay
źródło
źródło
new IntegeR(5) == new Integer(5)
zgodnie z zasadami należy oceniać jako fałsz.Odpowiedzi:
W Efektywnej Javie Joshua Blocha , pozycja 5: „Unikaj tworzenia niepotrzebnych obiektów”, zamieszcza następujący przykład kodu:
i trwa 43 sekundy. Biorąc Long na prymityw, sprowadzamy go do 6,8 sekundy ... Jeśli to jakaś wskazówka, dlaczego używamy prymitywów.
Problemem jest również brak rodzimej równości wartości (
.equals()
jest dość rozwlekły w porównaniu z==
)dla biziclop:
Prowadzi do:
EDYTUJ Dlaczego (3) zwraca
true
i (4) zwracafalse
?Ponieważ są to dwa różne obiekty. 256 liczb całkowitych najbliżej zera [-128; 127] są buforowane przez maszynę JVM, więc zwracają dla nich ten sam obiekt. Jednak poza tym zakresem nie są buforowane, więc tworzony jest nowy obiekt. Aby jeszcze bardziej skomplikować sprawę, JLS wymaga buforowania co najmniej 256 balastów. Osoby wdrażające JVM mogą dodać więcej, jeśli chcą, co oznacza, że może to działać w systemie, w którym najbliższe 1024 są buforowane i wszystkie zwracają true ... #awkward
źródło
i
zostały ogłoszoneLong
!==
operator przeprowadza porównania tożsamości referencyjnej naInteger
wyrażeniach i porównania równości wartości naint
wyrażeniach.Integer.equals()
istnieje właśnie z tego powodu. Nigdy nie należy używać==
do porównywania wartości w żadnym innym typie niż pierwotny. To jest Java 101.Autounboxing może prowadzić do trudnych do wykrycia NPE
W większości sytuacji zerowe przypisanie do
in
jest dużo mniej oczywiste niż powyżej.źródło
Typy pudełkowe mają gorszą wydajność i wymagają więcej pamięci.
źródło
Typy pierwotne:
Teraz oceń:
To jest
true
. Nic dziwnego. Teraz wypróbuj wersje pudełkowe:Teraz oceń:
To jest
false
. Prawdopodobnie. Zależy od środowiska wykonawczego. Czy to wystarczający powód?źródło
Oprócz problemów z wydajnością i pamięcią chciałbym wymyślić inny problem:
List
interfejs byłby zepsuty bezint
.Problemem jest
remove()
metoda przeciążona (remove(int)
vs.remove(Object)
).remove(Integer)
zawsze zdecydowałoby się wywołać to drugie, więc nie można usunąć elementu według indeksu.Z drugiej strony istnieje pułapka podczas próby dodania i usunięcia
int
:źródło
Vector
miałremoveElementAt(int)
od samego początku.remove(int)
został wprowadzony wraz ze strukturą kolekcji w Javie 1.2.List
projektowano API, nie istniały Generics ani Autoboxing, więc nie było szansy na pomieszanieremove(int)
iremove(Object)
…Czy naprawdę możesz sobie wyobrazić
pętla z java.lang.Integer zamiast tego? Element java.lang.Integer jest niezmienny, więc każdy inkrementacja wokół pętli utworzyłby nowy obiekt java na stercie, zamiast po prostu zwiększyć wartość int na stosie za pomocą pojedynczej instrukcji JVM. Przedstawienie byłoby diaboliczne.
Naprawdę nie zgodziłbym się, że korzystanie z trybu java.lang.Integer jest dużo wygodniejsze niż int. Przeciwnie. Autoboxing oznacza, że możesz użyć int, gdzie w innym przypadku byłbyś zmuszony do użycia Integer, a kompilator java zajmie się wstawieniem kodu, aby utworzyć nowy obiekt Integer za Ciebie. Autoboxing polega na umożliwieniu używania int, w przypadku gdy oczekiwana jest liczba całkowita, z kompilatorem wstawiającym odpowiednią konstrukcję obiektu. W żaden sposób nie usuwa ani nie zmniejsza potrzeby int w pierwszej kolejności. Dzięki autoboxowi uzyskujesz to, co najlepsze z obu światów. Otrzymujesz liczbę całkowitą utworzoną automatycznie, gdy potrzebujesz obiektu Java opartego na stercie, a szybkość i wydajność int, gdy wykonujesz tylko obliczenia arytmetyczne i lokalne.
źródło
Typy prymitywne są znacznie szybsze:
Integer (wszystkie liczby, a także String) jest typem niezmiennym : raz utworzony nie można go zmienić. Gdyby
i
było Integer,i++
to stworzyłoby nowy obiekt Integer - znacznie droższy pod względem pamięci i procesora.źródło
i++
na innej zmiennej, więc Integer musi być niezmienny, aby móc to zrobić (lub przynajmnieji++
musiałoby to utworzyć nowy obiekt Integer). (A prymitywne wartości też są niezmienne - po prostu nie zauważaj tego, ponieważ nie są one obiektami.)++
jest tu czerwonym śledziem. Wyobraź sobie, że Java została ulepszona, aby obsługiwać przeciążanie operatorów w naprawdę prosty sposób, tak że jeśli klasa (na przykładInteger
ma metodęplus
, toi + 1
zamiast tego można pisaći.plus(1)
.) Załóżmy również, że kompilator jest wystarczająco inteligentny, aby rozszerzyć sięi++
nai = i + 1
. Teraz można powiedzieći++
i efektywnie „zwiększaj zmienną i” bezInteger
jejPrzede wszystkim nawyk. Jeśli programujesz w Javie przez osiem lat, gromadzisz znaczną ilość bezwładności. Po co zmieniać, jeśli nie ma ku temu ważnego powodu? To nie jest tak, że używanie pudełkowych prymitywów ma jakieś dodatkowe zalety.
Innym powodem jest stwierdzenie, że
null
nie jest to prawidłowa opcja. Byłoby bezcelowe i mylące zadeklarowanie sumy dwóch liczb lub zmiennej pętli jakoInteger
.Jest to również aspekt wydajnościowy, podczas gdy różnica w wydajności nie jest krytyczna w wielu przypadkach (chociaż kiedy jest, jest dość zła), nikt nie lubi pisać kodu, który można by napisać równie łatwo i szybciej, my już przyzwyczajony.
źródło
Nawiasem mówiąc, Smalltalk ma tylko obiekty (bez prymitywów), a mimo to zoptymalizował swoje małe liczby całkowite (używając nie wszystkich 32 bitów, tylko 27 lub podobnych), aby nie przydzielać żadnej przestrzeni sterty, ale po prostu używali specjalnego wzorca bitowego. Również inne typowe obiekty (prawda, fałsz, null) miały tutaj specjalne wzorce bitowe.
Tak więc przynajmniej na 64-bitowych maszynach JVM (z 64-bitową przestrzenią nazw wskaźników) powinno być możliwe, aby w ogóle nie było żadnych obiektów typu Integer, Character, Byte, Short, Boolean, Float (i small Long) (poza tymi utworzonymi przez wyraźne
new ...()
), tylko specjalne wzorce bitowe, którymi normalni operatorzy mogliby dość skutecznie manipulować.źródło
Nie mogę uwierzyć, że nikt nie wspomniał o tym, co moim zdaniem jest najważniejszym powodem: „int” jest takie, o wiele łatwiejsze do wpisania niż „Integer”. Myślę, że ludzie nie doceniają wagi zwięzłej składni. Wydajność nie jest tak naprawdę powodem, aby ich unikać, ponieważ większość czasu, gdy używa się liczb, znajduje się w indeksach pętli, a inkrementacja i porównywanie ich nic nie kosztuje w żadnej nietrywialnej pętli (niezależnie od tego, czy używasz int czy Integer).
Drugim podanym powodem było to, że możesz uzyskać NPE, ale jest to niezwykle łatwe do uniknięcia w przypadku typów pudełkowych (i gwarantuje się unikanie tego, o ile zawsze inicjujesz je na wartości inne niż null).
Drugim powodem było to, że (new Long (1000)) == (new Long (1000)) jest fałszem, ale to tylko inny sposób na powiedzenie, że „.equals” nie obsługuje składni dla typów pudełkowych (w przeciwieństwie do operatorów <,> , = itd.), więc wracamy do powodu „prostszej składni”.
Myślę, że przykład nieprymitywnej pętli Steve'a Yegge'a bardzo dobrze ilustruje mój punkt widzenia: http://sites.google.com/site/steveyegge2/language-trickery-and-ejb
Pomyśl o tym: jak często używasz typów funkcji w językach, które mają dobrą składnię (jak każdy język funkcjonalny, Python, Ruby, a nawet C) w porównaniu z Javą, gdzie musisz symulować je za pomocą interfejsów, takich jak Runnable i Callable oraz bezimienne klasy.
źródło
Kilka powodów, aby nie pozbywać się prymitywów:
Jeśli zostanie wyeliminowany, żadne stare programy nawet nie będą działać.
Cała JVM musiałaby zostać przepisana, aby obsługiwała tę nową rzecz.
Musisz zapisać wartość i odniesienie, które zużywa więcej pamięci. Jeśli masz ogromną tablicę bajtów, użycie
byte
's jest znacznie mniejsze niż użycieByte
' s.Zadeklarowanie,
int i
a następnie zrobienie rzeczy zi
nie spowodowałoby żadnych problemów, ale zadeklarowanieInteger i
i zrobienie tego samego spowodowałoby NPE.Rozważ ten kod:
Byłoby fałszywe. Operatorzy musieliby być przeciążeni, a to spowodowałoby poważne przepisanie rzeczy.
Opakowania obiektów są znacznie wolniejsze niż ich prymitywne odpowiedniki.
źródło
Obiekty są znacznie cięższe niż typy pierwotne, więc typy pierwotne są znacznie wydajniejsze niż wystąpienia klas opakowujących.
Typy pierwotne są bardzo proste: na przykład int ma 32 bity i zajmuje dokładnie 32 bity w pamięci i można nim bezpośrednio manipulować. Obiekt Integer to kompletny obiekt, który (jak każdy obiekt) musi być przechowywany na stercie i jest dostępny tylko poprzez odniesienie (wskaźnik) do niego. Najprawdopodobniej zajmuje również więcej niż 32 bity (4 bajty) pamięci.
To powiedziawszy, fakt, że Java rozróżnia typy prymitywne i nieprymitywne, jest również oznaką wieku języka programowania Java. Nowsze języki programowania nie mają tego rozróżnienia; kompilator takiego języka jest wystarczająco inteligentny, aby samodzielnie dowiedzieć się, czy używasz prostych wartości, czy bardziej złożonych obiektów.
Na przykład w Scali nie ma typów pierwotnych; istnieje klasa Int dla liczb całkowitych, a Int jest rzeczywistym obiektem (na którym można stosować metody itp.). Kiedy kompilator kompiluje twój kod, używa za kulisami pierwotnych int, więc używanie int jest tak samo wydajne, jak używanie prymitywnych int w Javie.
źródło
Oprócz tego, co powiedzieli inni, prymitywne zmienne lokalne nie są przydzielane ze stosu, ale ze stosu. Ale obiekty są przydzielane ze sterty i dlatego muszą być zbierane jako śmieci.
źródło
Typy pierwotne mają wiele zalet:
źródło
Trudno powiedzieć, jakie optymalizacje zachodzą pod osłonami.
Do użytku lokalnego, gdy kompilator ma wystarczającą ilość informacji, aby dokonać optymalizacji z wyłączeniem możliwości wartości null, spodziewam się, że wydajność będzie taka sama lub podobna .
Jednak tablice prymitywów są najwyraźniej bardzo różne od kolekcji prymitywów pudełkowych. Ma to sens, biorąc pod uwagę, że w kolekcji jest możliwych bardzo niewiele optymalizacji.
Ponadto
Integer
ma znacznie wyższy koszt logiczny w porównaniu zint
: teraz musisz się martwić o to, czyint a = b + c;
zgłosi wyjątek.Używałbym prymitywów tak często, jak to możliwe i polegałbym na metodach fabrycznych i autoboxingu, aby zapewnić mi silniejsze semantycznie typy pudełkowe, gdy są potrzebne.
źródło
Na marginesie, nie miałbym nic przeciwko, gdyby coś takiego znalazło drogę do Javy.
Gdzie pętla for automatycznie zwiększa wartość loop1 od 0 do 1000 lub
Gdzie pętla for automatycznie zmniejsza wartość loop1 z 1000 do 0.
źródło
Powinieneś zapytać, dlaczego wymagany jest typ klasy / obiektu
Powodem posiadania typu Object jest ułatwienie nam życia, gdy mamy do czynienia z kolekcjami. Prymitywy nie mogą być dodawane bezpośrednio do listy / mapy, ale musisz napisać klasę opakowania. Gotowe klasy typu Integer pomagają w tym, a ponadto mają wiele metod użytkowych, takich jak Integer.pareseInt (str)
źródło
Zgadzam się z poprzednimi odpowiedziami, używanie prymitywnych obiektów opakowujących może być kosztowne. Jeśli jednak wydajność nie jest krytyczna w Twojej aplikacji, unikniesz przepełnień podczas korzystania z obiektów. Na przykład:
Wartość
bigNumber
to -2147483647 i można by oczekiwać, że będzie to 2147483649. Jest to błąd w kodzie, który można naprawić, wykonując:I
bigNumber
byłoby to 2147483649. Tego rodzaju błędy czasami można łatwo przeoczyć i mogą prowadzić do nieznanego zachowania lub luk w zabezpieczeniach (patrz CWE-190 ).Jeśli używasz obiektów opakowujących, odpowiadający im kod nie zostanie skompilowany.
Dlatego łatwiej jest zatrzymać tego rodzaju problemy, używając pierwotnych obiektów opakowujących.
Twoje pytanie zostało już tak udzielone, że odpowiadam tylko po to, aby dodać trochę więcej informacji nie wymienionych wcześniej.
źródło
Ponieważ JAVA wykonuje wszystkie operacje matematyczne w typach pierwotnych. Rozważmy ten przykład:
W tym przypadku operacje przypominające i jednoargumentowe plus nie mogą być stosowane na typie Integer (Reference), kompilator wykonuje rozpakowywanie i wykonuje operacje.
Więc upewnij się, ile operacji autoboxingu i rozpakowywania ma miejsce w programie java. Ponieważ wykonanie tej operacji zajmuje trochę czasu.
Ogólnie lepiej jest zachować argumenty typu Reference i wynik typu pierwotnego.
źródło
Te prymitywne typy są znacznie szybciej i wymaga dużo mniej pamięci . Dlatego wolelibyśmy ich używać.
Z drugiej strony, aktualna specyfikacja języka Java nie pozwala na użycie typów pierwotnych w typach parametryzowanych (rodzajach), w kolekcjach Java lub w interfejsie API Reflection.
Gdy nasza aplikacja potrzebuje kolekcji z dużą liczbą elementów, powinniśmy rozważyć użycie tablic o jak najbardziej „ekonomicznym” typie.
* Aby uzyskać szczegółowe informacje, patrz źródło: https://www.baeldung.com/java-primitives-vs-objects
źródło
Krótko mówiąc: typy prymitywne są szybsze i wymagają mniej pamięci niż wersje pudełkowe
źródło