В 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
, тогда ничто не будет жаловаться, но вы все равно останетесь с результатом 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 представляет собой серию компромиссов. В Java одна из сложных проблем связана с проектными решениями, которые были приняты давно.
Примитивы были в Java с 1.0. Они делают Java «нечистым» объектно-ориентированным языком, поскольку примитивы не являются объектами. Я полагаю, что добавление примитивов было прагматичным решением улучшить производительность за счет объектно-ориентированной чистоты.
Это компромисс, с которым мы все еще живем сегодня, почти 20 лет спустя. Функция автобоксирования, добавленная в Java 5, по большей части избавила от необходимости загромождать исходный код вызовами методов упаковки и распаковки, но накладные расходы все еще присутствуют. Во многих случаях это не заметно. Однако, если бы вы выполняли упаковку или распаковку внутри внутреннего цикла, вы бы увидели, что это может привести к значительным накладным расходам ЦП и сборке мусора.
При разработке Streams API было ясно, что мы должны поддерживать примитивы. Затраты на упаковку / распаковку убили бы любую выгоду производительности от параллелизма. Мы не хотели поддерживать все примитивы, поскольку это добавило бы огромное количество беспорядка в 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 умеренного размера, который накладывает относительно небольшую нагрузку на вызывающих абонентов в довольно узком диапазоне вариантов использования (обработка символов).
Мы выбрали последний.
chars()
: один, который возвращает a Stream<Character>
(с небольшим снижением производительности), а другой IntStream
- это тоже было рассмотрено? Вполне вероятно, что люди все Stream<Character>
равно будут в конечном итоге сопоставлять его, если они считают, что удобство стоит того, чтобы снизить производительность.
chars()
метод, который возвращает значения типа char в an 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()
метода, который принимает Collector
. У них есть только collect()
метод с тремя аргументами, как упомянуто в предыдущих комментариях.
CharStream
не существует, что было бы проблемой, чтобы добавить его?