W Javie 8 istnieje nowa metoda, String.chars()
która zwraca strumień int
s ( IntStream
) reprezentujący kody znaków. Sądzę, że wiele osób spodziewałoby się tutaj strumienia char
s. Jaka była motywacja do zaprojektowania API w ten sposób?
198
CharStream
nie istnieje, jaki byłby problem z jego dodaniem?Odpowiedzi:
Jak już wspomnieli inni, podstawą projektu było zapobieżenie eksplozji metod i klas.
Mimo to osobiście uważam, że była to bardzo zła decyzja, i dlatego, biorąc pod uwagę, że nie chcą podejmować
CharStream
, co jest rozsądne, różnych metod zamiastchars()
, pomyślałbym:Stream<Character> chars()
, co daje strumień pudełkowych postaci, które będą miały niewielką utratę wydajności.IntStream unboxedChars()
, który miałby zostać użyty do kodu wydajności.Jednak zamiast skupiać się na tym, dlaczego odbywa się to w ten sposób obecnie, myślę, że ta odpowiedź powinna skupić się na wskazaniu sposobu, aby to zrobić za pomocą interfejsu API, który otrzymaliśmy w Javie 8.
W Javie 7 zrobiłbym to tak:
Sądzę, że rozsądną metodą wykonania tego w Javie 8 jest:
Tutaj otrzymuję
IntStream
i mapuję go na obiekt za pomocą lambdai -> (char)i
, to automatycznie umieści go w aStream<Character>
, a następnie możemy zrobić, co chcemy, i nadal używać referencji metod jako plus.Pamiętaj jednak, że musisz to zrobić
mapToObj
, jeśli zapomnisz i użyjeszmap
, nic nie będzie narzekać, ale nadal skończy się naIntStream
i możesz nie zastanawiać się, dlaczego wypisuje wartości całkowite zamiast ciągów reprezentujących znaki.Inne brzydkie alternatywy dla Java 8:
Pozostając w
IntStream
i chcąc je ostatecznie wydrukować, nie możesz już używać odwołań do metod do drukowania:Co więcej, używanie referencji metod do własnej metody już nie działa! Rozważ następujące:
i wtedy
Daje to błąd kompilacji, ponieważ możliwa jest stratna konwersja.
Wniosek:
Interfejs API został zaprojektowany w ten sposób, ponieważ nie chcę dodawać
CharStream
, osobiście uważam, że metoda powinna zwrócić aStream<Character>
, a obejściem jest obecnie użyciemapToObj(i -> (char)i)
,IntStream
aby móc poprawnie z nimi pracować.źródło
codePoints()
zamiastchars()
, a wiele funkcji bibliotecznych już akceptujeint
punkt kodowy opróczchar
, np. Wszystkich metod,java.lang.Character
jakStringBuilder.appendCodePoint
, itp. Wsparcie to istnieje od tego czasujdk1.5
.String
lubchar[]
. Założę się, że większośćchar
nieudolnych par kodu zastępczego.void print(int ch) { System.out.println((char)ch); }
a następnie możesz użyć odwołań do metod.Stream<Character>
został odrzucony.Odpowiedź od skiwi pokryte wielu głównych punktów już. Wypełnię nieco więcej tła.
Projekt dowolnego API to szereg kompromisów. W Javie jednym z trudnych problemów jest podejmowanie decyzji projektowych, które zostały podjęte dawno temu.
Prymitywy są w Javie od 1.0. Sprawiają, że Java jest „nieczystym” językiem obiektowym, ponieważ prymitywy nie są obiektami. Dodanie prymitywów było, moim zdaniem, pragmatyczną decyzją o poprawie wydajności kosztem obiektowej czystości.
Jest to kompromis, z którym nadal żyjemy dzisiaj, prawie 20 lat później. Funkcja autoboxowania dodana w Javie 5 w większości eliminowała potrzebę zaśmiecania kodu źródłowego wywołaniami metod boxowania i rozpakowywania, ale narzut nadal istnieje. W wielu przypadkach nie jest to zauważalne. Jeśli jednak wykonasz boksowanie lub rozpakowanie w wewnętrznej pętli, zobaczysz, że może to nałożyć znaczne obciążenie procesora i odśmiecania.
Podczas projektowania interfejsu API Streams było jasne, że musimy wspierać operacje podstawowe. Narzut związany z boksem / rozpakowaniem zabiłby jakąkolwiek korzyść z wydajności wynikającą z równoległości. Nie chcieliśmy jednak obsługiwać wszystkich prymitywów, ponieważ spowodowałoby to mnóstwo bałaganu w interfejsie API. (Czy naprawdę widzisz zastosowanie
ShortStream
?) „Wszystko” lub „brak” to wygodne miejsca na projekt, ale żadne z nich nie było do zaakceptowania. Musieliśmy więc znaleźć rozsądną wartość „niektórych”. Skończyło się z prymitywnych specjalizacjeint
,long
orazdouble
. (Osobiście bym to pominął,int
ale to tylko ja.)Dla
CharSequence.chars()
rozważaliśmy powrótStream<Character>
(wczesny prototyp może wdrożyły ten), ale została ona odrzucona z powodu boksu napowietrznych. Biorąc pod uwagę, że Łańcuch machar
wartości jako prymitywy, błędem byłoby bezwarunkowe narzucanie boksu, gdy osoba wywołująca prawdopodobnie po prostu trochę przetworzyłaby tę wartość i rozpakowała ją z powrotem do łańcucha.Rozważaliśmy również
CharStream
prymitywną specjalizację, ale jej użycie wydaje się być dość wąskie w porównaniu z ilością, jaką dodałoby do API. Dodanie go nie wydawało się opłacalne.Kara nakładana na osoby dzwoniące polega na tym, że muszą wiedzieć, że
IntStream
zawierachar
wartości reprezentowane jakoints
i że rzutowanie musi odbywać się w odpowiednim miejscu. Jest to podwójnie mylące, ponieważ istnieją przeciążone wywołania API, takie jakPrintStream.print(char)
iPrintStream.print(int)
które różnią się znacznie swoim zachowaniem. Prawdopodobnie powstaje dodatkowy błąd, ponieważcodePoints()
wywołanie również zwraca an,IntStream
ale wartości, które zawiera, są zupełnie inne.Sprowadza się to zatem do pragmatycznego wyboru spośród kilku alternatyw:
Nie moglibyśmy zapewnić prymitywnych specjalizacji, co skutkowałoby prostym, eleganckim, spójnym API, ale które narzuca wysoką wydajność i ogólne obciążenie GC;
moglibyśmy zapewnić pełny zestaw prymitywnych specjalizacji, kosztem zaśmiecania interfejsu API i nakładania obciążeń konserwacyjnych na programistów JDK; lub
moglibyśmy podać podzbiór prymitywnych specjalizacji, dając API o średniej wielkości i wysokiej wydajności, które nakładają stosunkowo niewielkie obciążenie na osoby dzwoniące w dość wąskim zakresie przypadków użycia (przetwarzanie znaków).
Wybraliśmy ostatni.
źródło
chars()
, jedna zwracającaStream<Character>
(z niewielką utratą wydajności) i druga istotaIntStream
, czy to również zostało wzięte pod uwagę? Jest całkiem prawdopodobne, że ludzie i tak skończą na mapowaniu,Stream<Character>
jeśli uważają, że warto przekonać się nad karą za wydajność.chars()
metoda, która zwraca wartości char wIntStream
, to nie dodaje wiele do wywołania API, które otrzymuje te same wartości, ale w formie pudełkowej. Dzwoniący może bez problemu wpisać wartości. Na pewno wygodniej byłoby nie robić tego w (prawdopodobnie rzadkim) przypadku, ale kosztem dodawania bałaganu do API.chars()
powrótIntStream
nie jest dużym problemem, zwłaszcza biorąc pod uwagę fakt, że ta metoda rzadko była używana. Jednak dobrze byłoby mieć wbudowany sposób na powrótIntStream
doString
. Można to zrobić.reduce(StringBuilder::new, (sb, c) -> sb.append((char)c), StringBuilder::append).toString()
, ale to naprawdę długo.collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append).toString()
. Wydaje mi się, że nie jest tak naprawdę krótszy, ale użycie punktów kodowych pozwala uniknąć(char)
rzutów i pozwala na użycie odwołań do metod. Ponadto prawidłowo obsługuje parametry zastępcze.IntStream
nie mającollect()
metody, która wymagaCollector
. Mają tylkocollect()
metodę trzech argumentów , jak wspomniano w poprzednich komentarzach.