У Java 8 є новий метод, String.chars()
який повертає потік int
s ( IntStream
), що представляють коди символів. Я думаю, що багато людей очікують потоку char
s замість цього. Якою була мотивація розробити API таким чином?
У Java 8 є новий метод, String.chars()
який повертає потік int
s ( IntStream
), що представляють коди символів. Я думаю, що багато людей очікують потоку char
s замість цього. Якою була мотивація розробити API таким чином?
Відповіді:
Як уже згадували інші, проектне рішення, що стоїть за цим, полягало у запобіганні вибуху методів та класів.
І все-таки особисто я вважаю, що це було дуже поганим рішенням, і слід, враховуючи, що вони не хочуть приймати CharStream
, що є розумним, замість цього різні методи chars()
, я б подумав:
Stream<Character> chars()
, що дає потік символів, який матиме певний штрафний показник.IntStream unboxedChars()
, який би використовувався для коду продуктивності.Однак замість того, щоб зосередитись на тому, чому це робиться таким чином в даний час, я думаю, що ця відповідь повинна зосередитись на тому, щоб показати спосіб зробити це за допомогою API, який ми отримали з Java 8.
У Java 7 я зробив би це так:
for (int i = 0; i < hello.length(); i++) {
System.out.println(hello.charAt(i));
}
І я думаю, що розумним методом зробити це в Java 8 є такий:
hello.chars()
.mapToObj(i -> (char)i)
.forEach(System.out::println);
Тут я отримую IntStream
і відмічаю його до об’єкта за допомогою лямбда i -> (char)i
, це автоматично позначає його в a Stream<Character>
, і тоді ми можемо робити все, що хочемо, і досі використовувати посилання методів як плюс.
Будьте в курсі, що ви повинні зробити mapToObj
, якщо ви забудете і використаєте map
, то нічого не поскаржиться, але ви все одно закінчитеся з an IntStream
, і вам може залишитися цікаво, чому він друкує цілі значення замість рядків, що представляють символи.
Інші потворні альтернативи для Java 8:
Залишившись у IntStream
і бажаючи надрукувати їх у кінцевому рахунку, ви більше не можете використовувати посилання методів для друку:
hello.chars()
.forEach(i -> System.out.println((char)i));
Більше того, використання посилань на ваш власний метод більше не працює! Розглянемо наступне:
private void print(char c) {
System.out.println(c);
}
і потім
hello.chars()
.forEach(this::print);
Це призведе до помилки компіляції, оскільки можлива конверсія у збитків.
Висновок:
API був розроблений таким чином через те CharStream
, що я не хочу додавати , я особисто вважаю, що метод повинен повернути a Stream<Character>
, і наразі вирішення проблеми полягає в тому, щоб використовувати mapToObj(i -> (char)i)
на, IntStream
щоб мати змогу належним чином працювати з ними.
codePoints()
замість цього, chars()
і ви знайдете безліч функцій бібліотеки, які вже приймають int
кодову точку додатково char
, наприклад, всі методи java.lang.Character
, а також StringBuilder.appendCodePoint
і т.д. Ця підтримка існує з тих пір jdk1.5
.
String
або char[]
. Я б обміняв, що більшість char
кодів з обробки неправильно позначає сурогатні пари.
void print(int ch) { System.out.println((char)ch); }
а потім ви можете використовувати посилання на методи.
Stream<Character>
відхилено.
Відповідь від skiwi покриті багато з основних моментів вже. Я заповню трохи більше тла.
Дизайн будь-якого API - це ряд компромісів. На Яві одним із складних питань є вирішення дизайнерських рішень, прийнятих давно.
Примітиви були на Яві з 1.0. Вони роблять Java "нечистою" об'єктно-орієнтованою мовою, оскільки примітиви не є об'єктами. Додавання примітивів було, я вважаю, прагматичним рішенням покращити продуктивність за рахунок об'єктно-орієнтованої чистоти.
Це компроміс, з яким ми живемо сьогодні, майже через 20 років. Функція автобоксингу, додана в Java 5, здебільшого усунула необхідність захаращувати вихідний код за допомогою викликів методів боксу та розблокування, але накладні витрати все ще є. У багатьох випадках це не помітно. Однак, якщо ви виконували бокс або розпакування у внутрішньому циклі, ви побачите, що це може накласти значні витрати на процесор і збирання сміття.
При розробці API Streams було зрозуміло, що ми повинні підтримувати примітиви. Накладні бокс / розблокування знищить будь-яку користь від паралелізму. Однак ми не хотіли підтримувати всіх примітивів, оскільки це додало б величезної кількості неприємностей API. (Ви дійсно можете побачити використання для ShortStream
?) "Усі" або "жоден" - це зручні місця для дизайну, але жодне не було прийнятним. Тож нам довелося знайти розумне значення "дещо". Ми закінчили з примітивними спеціалізаціями для int
, long
і double
. (Особисто я б залишився, int
але це тільки я.)
Для CharSequence.chars()
ми вважали повернення Stream<Character>
(ранній прототип міг би реалізувати це) , але він був відхилений з - за бокс накладних витрат. Враховуючи, що у String є char
значення примітивів, здавалося б, помилкою є нав'язування боксу беззастережно, коли абонент, ймовірно, просто трохи обробить значення та розпакує його прямо у рядок.
Ми також вважали CharStream
примітивну спеціалізацію, але її використання, здавалося б, було досить вузьким порівняно з великою частиною, яку вона додала б до API. Не здавалося додати його.
Покарання, яке накладається на абонентів, полягає в тому, що вони повинні знати, що IntStream
містяться char
значення, представлені як ints
і що кастинг повинен бути зроблений у відповідному місці. Це удвічі заплутане , тому що перевантажені API виклики , як PrintStream.print(char)
і PrintStream.print(int)
що значно відрізняється за своєю поведінкою. Можливий додатковий момент плутанини, оскільки codePoints()
дзвінок також повертає, IntStream
але значення, які він містить, зовсім інші.
Отже, це зводиться до вибору прагматично серед кількох альтернатив:
Ми не могли б надати примітивних спеціалізацій, що призвело до простого, елегантного, послідовного API, але це накладає високу продуктивність та загальні витрати на GC;
ми могли б забезпечити повний набір примітивних спеціалізацій за рахунок збивання API та накладення навантаження на розробників JDK; або
ми могли б надати підмножину примітивних спеціалізацій, надаючи API з помірними розмірами, високоефективними API, що накладає відносно невелике навантаження на абонентів у досить вузькому діапазоні випадків використання (обробка символів).
Ми вибрали останню.
chars()
одного, який повертає Stream<Character>
(з невеликим покаранням продуктивності) та іншого IntStream
, чи це також було враховано? Цілком ймовірно, що люди в кінцевому підсумку відобразять його на карту, Stream<Character>
якщо вони вважають, що зручність заради цього покарання.
chars()
метод, який повертає значення знаків char у IntStream
, він не додає багато, щоб мати ще один виклик API, який отримує ті самі значення, але у коробці. Абонент може встановити значення без великих проблем. Звичайно, було б зручніше не робити цього в цьому (мабуть, рідкісному) випадку, але ціною додавання безладу в API.
chars()
повернення IntStream
не є великою проблемою, особливо зважаючи на те, що цей метод він взагалі рідко застосовував. Однак було б добре мати вбудований спосіб перетворення назад IntStream
до String
. Це можна зробити за допомогою .reduce(StringBuilder::new, (sb, c) -> sb.append((char)c), StringBuilder::append).toString()
, але це дійсно довго.
collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append).toString()
. Я думаю, що це не зовсім коротше, але використання точок коду дозволяє уникнути (char)
кастингу та дозволяє використовувати посилання методів. Плюс це правильно поводиться з сурогатами.
IntStream
не мають collect()
методу, який приймає a Collector
. Вони мають лише collect()
метод трьох аргументів, як згадувалося в попередніх коментарях.
CharStream
його немає, у чому проблема була б додати його?