Java 11: новое в String

    Всем привет! С момента выхода Java 11 прошли сутки, и вот уже наконец стали появляться первые обзоры релиза. Я же посвящу свою небольшую статью незаметному для официальных релизов и потому обделённому вниманием обновлению класса String, тем более, что оно не упоминается в официальной документации 11-й Java (я, во всяком случае, там информации об этом не нашёл).

    Действительно, если мы заглянем в класс String, то среди множества знакомых нам методов мы найдём несколько, помеченных как "@since 11". И да, официально в Java они появились только вчера.

    Конечно, в полезности каждой из функций вполне могут быть большие сомнения, поскольку самые полезные и необходимые функции уже были написаны в предыдущих версиях Java, но кому-то и эти пригодиться могут. Статья вышла небольшой, но в этом не только моя вина, но и вина Oracle — они включили в релиз всего 4 (+2) метода, что, конечно, немного.

    Приступим.

    strip();


    Этот метод убирает все пробелы, находящиеся до первого не-пробела и после последнего. Например:

    String withSpaces = "     a     ";
    String withoutSpaces = withSpaces.strip();
    
    String OUTPUT_TEMPLATE = "<%s>"
    System.out.println(String.format(OUTPUT_TEMPLATE, withSpaces));
    System.out.println(String.format(OUTPUT_TEMPLATE, withoutSpaces));

    Результат, выведенный на экран, будет:

    original: <     a     >
    strip: <a>

    У метода strip() есть два двоюродных брата — stripLeading() и stripTrailing(). Первый — убирает пробелы только спереди, перед первым не-пробелом. Второй — только сзади.

    String leading = withSpaces.stripLeading();
    String trailing = withSpaces.stripTrailing();

    Получаем результат:

    stripLeading: <a     >
    stripTrailing: <     a>

    UPD.


    Тут в комментариях подсказывают, что не помешало бы посмотреть, какова разница с тем же методом trim(), который, по сути, делает то же самое.

    Смотрим. Отличия, действительно, есть.

    public static String trim(byte[] value) {
            int len = value.length;
            int st = 0;
            while ((st < len) && ((value[st] & 0xff) <= ' ')) {
                st++;
            }
            while ((st < len) && ((value[len - 1] & 0xff) <= ' ')) {
                len--;
            }
            return ((st > 0) || (len < value.length)) ?
                newString(value, st, len - st) : null;
        }

    Как мы видим, в старой реализации при помощи двух итераций сначала вычисляется индекс первого не-пробела, а потом индекс последнего не-пробела, и потом по этим данным нарезается и возвращается новая строка. Отсекаются просто пробелы, заметим это.

    Теперь смотрим на метод strip().

        public static String strip(byte[] value) {
            int left = indexOfNonWhitespace(value);
            if (left == value.length) {
                return "";
            }
            int right = lastIndexOfNonWhitespace(value);
            return ((left > 0) || (right < value.length)) ? newString(value, left, right - left) : null;
        }
    
        public static int indexOfNonWhitespace(byte[] value) {
            int length = value.length;
            int left = 0;
            while (left < length) {
                char ch = (char)(value[left] & 0xff);
                if (ch != ' ' && ch != '\t' && !Character.isWhitespace(ch)) {
                    break;
                }
                left++;
            }
            return left;
        }
    
        public static int lastIndexOfNonWhitespace(byte[] value) {
            int length = value.length;
            int right = length;
            while (0 < right) {
                char ch = (char)(value[right - 1] & 0xff);
                if (ch != ' ' && ch != '\t' && !Character.isWhitespace(ch)) {
                    break;
                }
                right--;
            }
            return right;
        }

    Новый метод определяет вообще все случаи, когда символа не видно, будь то пробел, табуляция и проч. (желающие могут залезть в дебри реализации isWhiteSpace).

    Таким образом, новый метод предпочтительнее, если Вы хотите отсечь не только пробелы, но и вообще все невидимые символы.

    isBlank();


    Метод возвращает результат запроса, является ли эта строка «пустой», не содержащих никаких символов, кроме пробелов, табуляций и прочих невидимых символов.

    То есть, если мы исполним такой код:

    String blank = "     ";
    Boolean isBlank = blank.isBlank();

    Результат будет:

    true

    Внутри самого метода существует две реализации — для латинских символов и для строки в кодировке UTF-16.

        public boolean isBlank() {
            return indexOfNonWhitespace() == length();
        }
    
        private int indexOfNonWhitespace() {
            if (isLatin1()) {
                return StringLatin1.indexOfNonWhitespace(value);
            } else {
                return StringUTF16.indexOfNonWhitespace(value);
            }
        }

    repeat();


    Этот метод копирует содержимое строки заданное количество раз и возвращает результат в одной строке.

    Например, выполнив код:

    String sample = "(^_^) ";
    String multiple = sample.repeat(10);

    Мы получим:

    (^_^) (^_^) (^_^) (^_^) (^_^) (^_^) (^_^) (^_^) (^_^) (^_^) 

    Если же количество итераций равно нулю, то строка не будет содержать символов вообще.

    String blank = sample.repeat(0);

    Результат:

    length: 0

    lines();


    Странно было бы ожидать от Oracle, что они выпустят обновление String, не включив в класс какую-нибудь реализацию Stream API. И они-таки включили функционал в класс String.

    Метод lines преобразует все строчки строки в соответствующий Stream. Выглядит это так:

    String lines = "Blind Text Generator is a useful tool\n" +
                    "which provides Lorem Ipsum and a number of alternatives.\n" +
                    "The number of characters, words, and paragraphs\n" +
                    "are easily controlled and you can set \n" +
                    "the font to appreciate how it’ll look in your design.";
    
            lines
                    .lines()
                    .map(l -> "next line: " + l)
                    .forEach(System.out::println);

    Получим результат:

    next line: Blind Text Generator is a useful tool
    next line: which provides Lorem Ipsum and a number of alternatives.
    next line: The number of characters, words, and paragraphs
    next line: are easily controlled and you can set 
    next line: the font to appreciate how it’ll look in your design.

    Мы получили полноценный стрим, с которым потом можем делать всё, что мы обычно делаем со стримами обычными. Применение этому может быть самое разное, и, хочется надеяться, что такая фича будет тепло принята разработчиками.

    Если посмотреть внутрь самого метода, то мы увидим, что для преобразования строки в Stream используются два сплитератора на выбор, в зависимости от того, в какой кодировке строка.

    public Stream<String> lines() {
            return isLatin1() ? StringLatin1.lines(value)
                              : StringUTF16.lines(value);
        }

    На этом список нововведений релиза в части String заканчивается. Если я что-то упустил, буду рад об этом узнать и добавить в обзор. Все представленные примеры кода Вы можете пощупать самостоятельно в ресурсе на гитхабе.

    Комментарии 50

      +1

      Дождались!
      Всегда лень было качать для этого апачевскую библиотеку или копировать их код

        0
        Качать


        Это добавить пару строчек в систему сборки? Совсем разленились. ;)
        +1
        Ого, неужели, глядишь, лет через восемь и перегрузку операторов добавят?
          0
          Очень надеюсь, что не добавят, иначе ведь получится ещё один C++ (такой же непонятный и неоднозначный ужос, когда глядя на строчку кода «c=a+b;» ты не знаешь, уничтожит ли она вселенную, или же всего лишь сложит два числа)
            +1
            Тогда почему же в C++ не превратился C#, в котором перегрузка операторов есть с незапамятных времен, а еще есть unsafe, PInvoke и другие страшные вещи? ;)
          +4
          Статья вышла бы больше, если бы вы, например, рассказали разницу между strip() и уже существующим trim().

          А вообще ничего страшного, в Java 12 String просто взорвётся новыми методами. Это так, пробный камень.
            0
            Это чем же он там взорвётся?
              0

              Пока что шесть новых методов ищется:


                0
                А почему на них нет JEPов? Или они являются частью одного / нескольких существующих?
                  0

                  Не на каждый API-метод JEP делают.

              +1
              Согласен про strip — тоже не увидел разницу между trim. Вот нагуглил: stackoverflow.com/questions/51266582/difference-between-string-trim-and-strip-methods-in-java-11
              Вкрадце — trim плохо работает с Unicode, воспринимает как пробельные символы только символы с кодом ≤20, но на самом делел в Unicode их много. Поэтому добавили Unicode версию trim — strip.
              Хотя на мой взгляд очень спорное решение — почему просто trim не исправили?!
                0
                почему просто trim не исправили?!

                Вы что, хипстер? Миллиард существующих программ сломается. Java — не тот язык, где можно вот так просто взять и исправить.

                • НЛО прилетело и опубликовало эту надпись здесь
                    0
                    А как перегрузить? trim() и strip() только именем отличаются.
                    • НЛО прилетело и опубликовало эту надпись здесь
                    0
                    Это понятно. Но с другой стороны — в Java строки UTF 16, но функция по работе с этими строками только на ASCII заточена. Т.ч. по мне так это сейчас trim не правильно работает и сделав поддержку Unicode они бы исправили её.
                  0

                  Спасибо, добавил. Отличия действительно есть.

                    0
                    если Вы хотите отсечь не только пробелы, но и вообще все невидимые символы

                    Не все. Character.isWhitespace() не считает Non-breaking space (0xA0) "невидимым".

                    0
                    вот тоже стало интересно зачем стрип, если есть трим.

                    UPD уже не актуально
                    0
                    Вообще да, в Java маловато встроенных возможностей по манипуляции со строками. Одобряю.
                      0

                      Про разные реализации для Latin-1 и юникодовских строк нет смысла писать, не упомянув Compact Strings.
                      А они, кстати, новинка для тех, кто переходит с Java 8.

                        0
                        Можно дурацкий вопрос — почему после Java 8 (или 9, что там было?) вышла сразу версия 11?
                            0
                            Тогда почему когда я искал последнюю версию Java, чтобы установить (JRE), на сайте самой свежей значилась версия 9? Это было примерно в январе-феврале текущего года. И то там при этом номер апдейта был так мал, что создавалось ощущение, что она только-только вышла.
                              –1
                              А сами как думаете?
                                0
                                Не знаю. На сайт заходил с Win 7 x64, не думаю, что там стоит автодетект версии ОС.

                                Только не надо говорить, что между версиями 9 и 10 прошло меньше года: версия 7 вышла ближе к осени 2011-ого, а версия 8 — в районе начала 2014-ого. 6-ая же вообще чуть ли не в начале 2006-ого появилась на свет. Промежутки должны по идее быть хотя бы примерно равными :)
                                  –1
                                  М-да… А на Википедии и в гугле Вас забанили?
                                0
                                JRE более выпускать не будут
                                Раз, два.
                                между версиями 9 и 10 прошло меньше года

                                Так и есть — «хром покусал», версии теперь будут менять кажется раз в пол-года.
                                  0
                                  Так и есть — «хром покусал», версии теперь будут менять кажется раз в пол-года.

                                  Ого. Интересно
                                  JRE более выпускать не будут
                                  Раз, два.

                                  Вот это просто рукалицо. Они правда думают, что теперь работать станет проще? Я про всю эту возню с JLink. Как по мне, старый подход хоть и требовал ради красоты оборачивать программу в exe-шник, но во-первых, для этой цели были сторонние продукты с GUI, и даже бесплатные; новый же требует ввода команд в консоль, и на выходе получается… батник (с кучей файлов .class рядом). Офигеть, блин.
                                    0
                                    За всех не скажу, но лично я никакими сторонними (мы ведь IDE не считаем, ведь так?) тулами с GUI для этого никогда не пользовался — maven «наше все»… обвешался плагинами для компиляции и сборки, отконфигурировал и собрал бандл эм, ну вы поняли (надо меньше возиться с npm).

                                    Удобно или нет — покажет время.
                                    Ну и таки да — у нас перед глазами есть IntelliJ IDEA, PhpStrom и т.д., они без установленного jre спокойно работают (на сколько я знаю).
                                      0
                                      Ну может они с собой его тянули, я без понятия.

                                      Я не настолько опытный Java разработчик. Рассказываю свой кейс (как я пишу код где-то начиная со 2 курса для своих проектов):

                                      Сначала веду разработку в NetBeans. Картинки в проект как ресурсы обычно не импортирую: сначала не знал как, потом узнал, но решил, что и не особо надо. Мой код загружает их из подкаталога.

                                      В коде есть две специальных функции: первая получает путь установки программы, вторая — путь к папке с картинками. Программа может быть установлена куда угодно, но есть два требования: имя последней папки с exe файлом не должно быть изменено, имя папки с картинками внутри неё тоже не должно быть изменено (мало ли кто ручками изменит).

                                      При этом код написан так, что пути определяются корректно и при запуске jar файла, лежащего в подкаталоге dist, и при запуске exe файла, лежащего на уровень выше.

                                      Плюс я обычно включаю в дистрибутив папку src с исходниками, ибо не жалко.

                                      Обёртку над jar делаю с помощью exe4J, чтобы включить иконку, информацию о версии и авторе, и прочее.

                                      Потом генерирую сторонним софтом инсталлятор, копирующий файлы программы в выбранный пользователем каталог и создающий ярлыки. В последнее время предпочитаю Smart Install Maker (раньше использовал Wise Installation Studio 9, но там всё очень криво, если пользователь удаляет программу вручную из системы, программу из списка установленных потом практически не убрать, если нет под рукой оригинального инсталлятора нужной версии моего продукта).

                                      Теперь, я так понимаю, мне придётся делать обёртку над bat скриптом, а заодно тащить с собой часть JDK, которая хоть и немного, но тоже что-то весит. Нафига так было делать — большой вопрос.
                          0
                          Метод возвращает результат запроса, содержит ли данная строка какие-то символы, кроме пробелов.

                          То есть, если мы исполним такой код:

                          String blank = " ";
                          Boolean isBlank = blank.isBlank();

                          Результат будет:

                          true

                          возможно я чего-то не понимаю, но покажите мне где в этом примере содержится символ кроме пробела
                            0
                            Спасибо, поправил.
                          0
                          «Внутри самого метода существует две реализации — для латинских символов и для строки в кодировке UTF-16.»
                          Эта UTF-16 где-нибудь реально используется, или такой же труп, как 1251?
                            0
                            Судя по тому, что я слышал, это как раз кодировка будущего, но я могу ошибаться.
                              0
                              Это кодировка темного идиотского прошлого.
                              Никакой совместимости с ASCII. Для программиста она — абсолютный вынос мозга. Как Вы будете программы на UTF-16 писать? Все программы пишутся в 8-и байтной ASCII. Не поэтому ли в виндах до сих пор остается такое говно мамонта, как cp1251, которая совместима с ASCII по первой половине?
                              Попробуйте распарсить utf-8 и utf-16 в k&r си, чтобы это все работало как на be, так и на le, а потом сравните, что у вас вышло. Хотя из под фреймворков, написанных с использованием фреймворков, написанных с использованием фреймворков, ..., и прочих земляных червяков это незаметно:-)
                              Да, utf-16 не поддерживает кодовых точек выше 10FFFF, причем с выпадением геморроя, если выше базовой плоскости (0000-FFFF), в отличие от utf-8, которая на регулярной основе поддерживает до FFFFFFFF.
                                0
                                <зануда>
                                8-и байтной ASCII

                                </зануда>
                                0
                                Когда Java сама себя начнет собирать из исходников, написанных на utf-16, не прибегая к услугам ASCII-софта, тогда и поговорим.
                                  0

                                  Так она вроде собирается с помощью G++, не? Не очень понимаю, как это относится к теме. Я сам поддерживаю UTF-8, да и не только я, вот сайт хороший есть о ней. Но что поделаешь, в джаве UTF-16, придётся жить с этим.

                                    0
                                    Алфавит UTF16 не совместим с алфавитом, в котором готовятся исходники для gcc, как впрочем, и для всех в мире компиляторов. Что касается gcc, то результатом компиляции будет сообщение о мусоре в исходнике:
                                    $ gcc -c -W -Wall a.c
                                    a.c:1:1: error: stray '\377' in program

                                      +2

                                      Я прекрасно знаю, как устроен UTF-16. Но я не понимаю, как связана сборка Java с помощью G++ и хранение строк в UTF-16 внутри Java. Это несколько разные области, и одна никак не обязывает другую поддерживать какие-то возможности. Никто ведь не требует, чтобы в GCC был garbage collector, верно?

                              0
                              Насчет String.lines. Не лучше ли было бы сделать метод String.splitAsStream?
                                0
                                Согласен.
                                  0

                                  А смысл?
                                  Ведь уже есть Pattern::splitAsStream().

                                    0
                                    Именно там я и подсмотрел название. Почему тогда не сделать Pattern::lines?
                                    Почему у String есть метод split, а splitToLines, который бы возвращал массив — нет? Да потому что это очень конкретный случай. Плюс руки пока ни у кого не отвалились передать 4 символа `\n`, ну или `\R` как аргумент String::split. Никто же не добавляет Math::pow2 чтоб в квадрат возводить. Пальцем у виска покрутят, если такое предложить.
                                    Например, нужно разбить длинную строку с разделителем ',' и всё это в стримом получить (ленивость и экономия памяти желательны, как и лаконичность кода). Если посмотрите JDK-8200425, в котором String::lines обсуждали, то там и показано как многословно всё это получается. Только вот решение было сделано для одного конкретного случая. Мне это не понятно.
                                  0
                                  А новая функция strip() символы '\u00A0', '\u2007', '\u202F' определяет как пробелы?
                                  Спрашиваю потому, что сталкивался с такой проблемой: старые реализации Character.isWhitespace данные символы пробелами не считали, хотя по факту это пробелы.
                                    0
                                    Говорят, не определяет.
                                    И опирается она на ту же Character.isWhitespace.
                                    0

                                    В Java когда нибудь появиться строковые шаблоны?

                                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                    Самое читаемое