У Java 8 є новий метод, String.chars()який повертає потік ints ( IntStream), що представляють коди символів. Я думаю, що багато людей очікують потоку chars замість цього. Якою була мотивація розробити API таким чином?
У Java 8 є новий метод, String.chars()який повертає потік ints ( IntStream), що представляють коди символів. Я думаю, що багато людей очікують потоку chars замість цього. Якою була мотивація розробити 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його немає, у чому проблема була б додати його?