Работа с текстовыми данными. Локализация

1 Теоретическая часть

1.1 Локализация

Интернационализация (internationalization, i18n) – это процесс проектирования программного обеспечения таким образом, чтобы оно могло быть адаптировано к различным языкам и регионам без конструктивных изменений. Локализация (localization, l10n) – это процесс адаптации программного обеспечения к национальным стандартам и правилам, которые определяют язык, формат представления чисел, даты и времени, валюты, направление написания текста и т.п.

В Java для установки и хранения информации о локализации используются объекты класса java.util.Locale. Для определения языков и стран класс Locale использует идентификаторы в соответствии со стандартом BCP 47 (IETF BCP 47, "Теги для идентификации Языка"). Для создания объекта типа Locale существует несколько вариантов. Можно получить локализацию, которая определена для виртуальной машины Java по умолчанию:

Locale locale = Locale.getDefault();

Можно определить локализацию с помощью конструкторов, например:

Locale Locale1 = new Locale("en", "US");
Locale Locale2 = new Locale("en", "GB");
Locale Locale3 = new Locale("ru");

Можно также воспользоваться статическим методом forLanguageTag(), например:

Locale Locale4 = Locale.forLanguageTag("en-US");    

Можно "построить" объект с помощью вложенного класса Locale.Builder:

Locale Locale5 = new Locale.Builder().setLanguage("en").setRegion("US").build();    

Платформа Java не требует использования только одной локализации для всей программы. Такая гибкость позволяет разрабатывать многоязычные приложения. Для некоторых стран региональные параметры устанавливаются с помощью констант, например: Locale.US, Locale.FRANCE, Locale.CANADA. Для других стран объект Locale нужно создавать с помощью конструктора, например, new Locale("ua", "UA").

Для локализации, определенной как new Locale("en", "US") следующий код позволяет получить информацию о регионе:

Locale currentLocale = new Locale("en", "US");
currentLocale.getCountry();//код региона
currentLocale.getDisplayCountry();//название региона
currentLocale.getLanguage();//код языка региона
currentLocale.getDisplayLanguage();//название языка региона

Класс SimpleTimeZone, реализующий абстрактный TimeZone, позволяет работать с часовыми поясами Григорианском календаре. Этот класс учитывает летнее время.

Класс GregorianCalendar имеет конструкторы, позволяющие определить календарь по часовому поясу и локализации:

GregorianCalendar(Locale locale) 
GregorianCalendar(TimeZone timeZone) 
GregorianCalendar(TimeZone timeZone, Locale locale)

1.2 Использование пакета java.text. Сортировка строк

Пакет java.text предоставляет классы и интерфейсы для обработки текста, дат и числовых значений независимо от языка и других национальных особенностей. Это означает, что приложение может быть написано для независимой от языка работы, а ресурсы, которые определяют индивидуальную локализацию, могут подключаться динамически. Это позволяет гибко добавлять новые локализации приложения позже.

Классы этого пакета предназначены для форматирования дат, чисел и сообщений, анализа, поиска и сортировки строк, а также итеративного следования символов, слов, предложений и строк. Этот пакет содержит три основные группы классов и интерфейсов, которые в соответствии решают следующие группы задач:

  • сортировка строк
  • форматирование и разбор на лексемы
  • итерация по тексту

Работу сортировки обеспечивает класс Collator ("сравнитель"). С помощью экземпляра класса Collator можно осуществлять сортировку строк с динамическим определением локализации. Например, если осуществлять сортировку текстов на украинском языке обычными средствами, возникает проблема со специфическими украинскими буквами, такими как ґ, є, і и ї, поскольку их коды расположены отдельно от кодов других символов украинского алфавита. Для корректной сортировки массивов строк с украинским текстом необходимо создать экземпляр класса Collator с помощью функции getInstance(), параметром которой является необходимая локализация.

Следующий пример демонстрирует сортировку массива строк сначала без использования класса Collator, а затем с созданием объекта типа Collator, учитывающий украинскую локализацию. Для определения порядка сортировки создаем безымянный внутренний класс.

package ua.inf.iwanoff.text;

import java.text.Collator;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Locale;

public class SortDemo {

    public static void main(String[] args) {
        String[] words = { "воля", "воїн", "возити" };
        // Осуществляем сортировку по умолчанию:
        Arrays.sort(words);
        System.out.println(Arrays.asList(words)); // [возити, воля, воїн]
        // Осуществляем сортировку с учетом локализации:
        Arrays.sort(words, new Comparator<String>() {
            Collator collator = Collator.getInstance(new Locale("uk"));
            
            @Override
            public int compare(String s1, String s2) {
                return collator.compare(s1, s2);
            }
        });
        System.out.println(Arrays.asList(words)); // [возити, воїн, воля]
    }

}

1.3 Форматирование

Форматирование данных с учетом локализации обеспечивает абстрактный класс java.text.Format. Потомки класса FormatNumberFormat, DateFormat и MessageFormat. Они позволяют осуществлять форматирование чисел, дат и сообщений соответственно. В классе Formatter объявлен метод format(), который преобразует переданные в него параметры в строку заданного формата и сохраняет в объекте типа Formatter.

Следующий пример демонстрирует вывода значения типа double с учетом указанной локализации:

package ua.inf.iwanoff.text;

import java.text.NumberFormat;
import java.util.Locale;
import java.util.Scanner;

public class NumberPrinter {

    public static void main(String[] args) {
        NumberFormat form = NumberFormat.getInstance(new Locale("uk"));
        Scanner scan = new Scanner(System.in);
        double x = scan.nextDouble();
        System.out.println(form.format(x));
    }

}

Для форматирования целых можно получить объект с помощью метода getIntegerInstance(). Аналогично можно получить getCurrencyInstance() для вывода денежных сумм, getPercentInstance() для вывода процентов и т.д..

Чтобы конвертировать информацию в региональные стандарты, следует создать объект класса NumberFormat с конструктором, принимающим в качестве параметра объект класса Locale, или с конструктором без параметров (с локализацией, установленной по умолчанию):

NumberFormat nf = NumberFormat.getInstance(new Locale("RU"));
NumberFormat nf = NumberFormat.getInstance();    

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

import java.text.NumberFormat;
import java.text.ParseException;
import java.util.Locale;

public class DemoNumberFormat {

    public static void main(String[] args) {
        NumberFormat nfGe = NumberFormat.getInstance(Locale.GERMAN);
        NumberFormat nfUs = NumberFormat.getInstance(Locale.US);
        NumberFormat nfFr = NumberFormat.getInstance(Locale.FRANCE);
        NumberFormat nfUa = NumberFormat.getInstance(new Locale("ua", "UA"));
        double iGe = 0, iUs = 0, iFr = 0, iUa = 0;
        String str = "1.245,999";
        try {
            //преобразование строки в германский стандарт:
            System.out.println(iGe = nfGe.parse(str).doubleValue());
            //преобразование строки в американский стандарт:
            System.out.println(iUs = nfUs.parse(str).doubleValue());
            //преобразование строки во французский стандарт:
            System.out.println(iFr = nfFr.parse(str).doubleValue());
            //преобразование строки в украинский стандарт: 
            System.out.println(iUa = nfUa.parse(str).doubleValue());  
        }
        catch (ParseException e) {
            e.printStackTrace();
        }
        System.out.println();
        String sUs = nfUs.format(iGe);//преобразование числа из германского в американский стандарт    
        String sFr = nfFr.format(iGe);//преобразование числа из германского во французский стандарт
        String sUa = nfUa.format(iGe);//преобразование числа из германского в украинский стандарт
        System.out.println(sUs + "\n" + sFr + "\n" + sUa);
    }

}

Здесь для преобразования строки в число и обратно используются методы NumberFormat parse(String source) и String format(double number) соответственно.

Задачу поддержки национальных особенностей в отображении даты и времени в различных странах и регионах мира помогает решить класс java.text.DateFormat. При создании объекта данного класса может быть задана конкретная локализация, либо использована локализация для данной среды по умолчанию:

DateFormat df = DateFormat.getDateInstance( DateFormat.MEDIUM, new Locale("Ua"));
DateFormat df = DateFormat.getDateInstance();    

Абстрактный класс DateFormat его подкласс SimpleDateFormat пакета java.text содержат методы, позволяющие осуществлять различные способы форматирования представления даты и времени. Класс DateFormat предлагает следующие стили представления даты и времени:

  • стиль SHORT представляет дату и время в коротком числовом виде: 27.04.01 17:32;
  • стиль MEDIUM задает год четырьмя цифрами и показывает секунды: 27.04.2001 17:32:45;
  • стиль LONG представляет месяц словом и добавляет часовой пояс: 27 апрель 2001 г. 17:32:45 GMT+03.-00;
  • стиль FULL совпадает со стилем LONG;
  • стиль DEFAULT совпадает со стилем MEDIUM.

Например, следующий код создает форматированную строку даты с форматом для текущего региона по умолчанию:

DateFormat dateFormatter = DateFormat.getDateInstance(DateFormat.DEFAULT);
Date today = new Date();
String formattedDate = dateFormatter.format(today);
System.out.println(formattedDate);    

Кроме метода DateFormat.getDateInstance() могут быть использованы методы DateFormat.getTimeInstance() и DateFormat.getDateTimeInstance() для форматирования времени, а также для форматирования и даты, и времени.

Дочерний класс SimpleDateFormat абстрактного класса DateFormat может быть использован для определения собственных форматов пользователя. При создании объекта класса SimpleDateFormat можно задать в конструкторе шаблон, определяющий какой-либо другой формат, например:

SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy hh mm");
System.out.println(sdf.format(new Date()));    

В шаблоне буква d означает цифру дня месяца, M – цифру месяца, у – цифру года, h – цифру часа, m – цифру минут.

Например, следующий код выведет дату в формате "04/29/2013":

Date today = new Date();
SimpleDateFormat formatter = new SimpleDateFormat("MM/dd/yyyy");
String formattedDate = formatter.format(today);
System.out.println(formattedDate);    

Шаблон "EEEE d MMMM yyyy" задаст формат вывода даты как "четверг 16 мая 2011".

Для настройки символов для любого компонента даты или времени используется класс DateFormatSymbols:

DateFormatSymbols symbols = new DateFormatSymbols();
String[] oddMonthAbbreviations = new String[] {"Ja","Fe","Mh","Ap","My","Jn","Jy","Au","Se","Oc","No","De" };
symbols.setShortMonths(oddMonthAbbreviations);
formatter = new SimpleDateFormat("MMM dd, yyyy", symbols);
formattedDate = formatter.format(today);
System.out.println(formattedDate);    

Конструктор SimpleDateFormat принимает строку шаблона и объект DateFormatSymbols.

Получить представление текущей даты во всех возможных региональных стандартах можно следующим образом:

Date d = new Date();
Locale[] locales = DateFormat.getAvailableLocales();
for (Locale loc : locales) {
    DateFormat df = DateFormat.getDateInstance(DateFormat.FULL, loc);
    System.out.println(loc.toString() + "-> " + df.format(d));
}

Пример вывода даты в формате, заданном пользователем:

import java.util.Calendar;
import java.util.Date;
import java.util.Locale;
import java.text.*;

public class Task {

    public static void main(String args[]) {
        try {
            String[] months = { "января", "февраля", "марта", "апреля", "мая", "июня",
                     "июля", "августа", "сентября", "октября", "ноября", "декабря" };
            DateFormatSymbols dfs = new DateFormatSymbols(new Locale("ru"));
            dfs.setMonths(months);
            SimpleDateFormat sdf = new SimpleDateFormat("d MMMM yyyy 'г.'", dfs);
            System.out.println("Сегодня: " +  sdf.format(new Date()));
            Calendar c = Calendar.getInstance();
            c.set(2011, 5, 1);
            System.out.println("Начало лета: " + sdf.format(c.getTime()));
        }
        catch (Exception e) {
            System.out.println(e);
        }
    }

}

Класс java.util.Formatter отвечает за форматирование на уровне представления данных с точки зрения ширины поля вывода, выравнивание, наличии знака + перед числами и т.п.

Статический метод format класса String позволяет получить строку, представляющий данные в соответствии с форматом, например:

    double d = 3.5;
    int i = 12;
    String result = String.format("%f %d%n", d, i);
    System.out.print(result);

Аналогичный метод объявлен у классов PrintStream и PrintWriter. Кроме того, у этих классов объявлен метод printf() с параметрами, идентичными параметрам метода format(), который осуществляет форматированный вывод в поток. Следующим образом можно получить непосредственное форматирование вывода данных с помощью функции System.out.printf():

    double d = 3.5;
    int i = 12;
    System.out.printf("%f %d%n", d, i);    

При форматировании используются следующие спецификаторы формата:

Спецификатор формата Выполняемое форматирование
%a

Шестнадцатеричное значение с плавающей точкой

%b

Логическое (булево) значение аргумента

%c

Символьное представление аргумента

%d

Десятичное целое значение аргумента

%h

Хэш-код аргумента

%e

Экспоненциальное представление аргумента

%f

Десятичное значение с плавающей точкой

%g

Выбирает более короткое представление из двух: %е или %f

%o

Восьмеричное целое значение аргумента

%n

Вставка символа новой строки

%t

Время и дата

%x

Шестнадцатеричное целое значение аргумента

%%

Вставка знака %

 

Также возможны спецификаторы с заглавными буквами: %A (эквивалентно %a). Форматирование с их помощью обеспечивает перевод символов в верхний регистр.

import java.util.Formatter;

public class SimpleFormatString {

    public static void main(String[] args) {
        Formatter f = new Formatter(); 
        f.format("%s %c %nОсновы разработки приложений Java %S ", "Модуль",'2',"se");  
        System.out.print(f); 
    }

}    

Для данных с плавающей точкой (спецификаторы %f, , %g), а также для строк (спецификатор %s) может быть применен спецификатор точности. Он задает количество выводимых символов. Например, спецификатор %10.3f выводит число с шириной поля 10 символов и с тремя десятичными знаками (принятая по умолчанию точность равна шести десятичным знакам). Примененный к строкам спецификатор точности задает максимальную длину поля вывода. Например, %.15s выводит строку длиной 15 символов, спецификатор %3.7s – строку длиной не менее трех и не более семи символов. Если строка длиннее, конечные символы отбрасываются. Возможно задание флагов, позволяющих осуществить дополнительные возможности форматирования:

import java.util.Formatter;

public class SimpleFormatString {

    public static void main(String[] args) {
        Formatter f = new Formatter(); 
        f.format("|%10.2f|", 123.123);  // выравнивание вправо 
        System.out.println(f);
        f = new Formatter();            // выравнивание влево
        f.format("|%-10.2f|", 123.123); // применение флага '-' 
        System.out.println(f);
        f = new Formatter();
        f.format("%,.2f", 123456789.34);// применение флага ',' 
        System.out.println(f);
        f = new Formatter();
        f.format("%.4f", 1111.1111111); // задание точности представления для чисел 
        System.out.println(f);
        f = new Formatter();
        f.format("%.6s", "Работа с текстовыми данными."); // задание точности представления для строк 
        System.out.println(f);
    }

}

Существуют также спецификаторы для форматирования даты и времени, которые могут употребляться только для типов long, Long, Calendar, Date, например:

import java.util.*;

public class Time {

  public static void main(String args[]) {
    Formatter f = new Formatter();
    Calendar calendar = Calendar.getInstance(); 
    f.format("%tr", calendar);// вывод в 12-часовом временном формате 
    System.out.println(f); 
    f = new Formatter();
    f.format("%tc", calendar);// полноформатный вывод времени и даты 
    System.out.println(f); 
    f = new Formatter();
    f.format("%tl:%tM", calendar, calendar);// вывод текущего часа и минуты 
    System.out.println(f); 
    f = new Formatter();
    f.format("%tB %tb %tm", calendar, calendar, calendar);// различные варианты вывода месяца 
    System.out.println(f);
  }

}

1.4 Итерация по символам строки

Интерфейс java.text.CharacterIterator предоставляет средства для двунаправленного прохождения строки с помощью итератора. Класс StringCharacterIterator реализует интерфейс java.text.CharacterIterator. Методы getBeginIndex() и getEndIndex() позволяют вернуть индексы первого и последнего символов строки. Индекс текущего символа можно получить с помощью метода getIndex(). Вызов метода setIndex(int index) перемещает итератор в новое положение. С помощью методов previous() и next() можно перемещать итератор на предыдущую и следующую позиции, при достижении границ диапазона эти методы вернут DONE. Методы first() и last() установят итератор на первую и последнюю позиции соответственно, вернув символ, находящийся на этой позиции. В следующем примере используется итератор CharacterIterator класса StringCharacterIterator для прохождения строки от начального индекса к концу строки.

import java.text.CharacterIterator;
import java.text.StringCharacterIterator;

public class StringCharacterExample {
    private static final String text = "Jackdaws love my big sphinx of quartz";

    public static void main(String[] args) {
        CharacterIterator it = new StringCharacterIterator(text);
        for (char ch = it.first(); ch != CharacterIterator.DONE; ch = it.next()) {
            System.out.print(ch);
        }
    }

}

Поскольку класс Character учитывает локализацию его целесообразно использовать вместо char в приложениях, рассчитанных на интернационализацию и локализацию.

1.5 Регулярные выражения

Регулярные выражения представляют собой способ описания множества строк на основе общих характеристик. Регулярные выражения могут быть использованы для поиска, редактирования или работы с текстом и данными. Синтаксис регулярных выражений единый в различных языках программирования.

В Java работа с регулярными выражениями реализована в пакете java.util.regex, в частности, классами Pattern и Matcher. Также определен специфический класс-исключение – PatternSyntaxException.

Объект Pattern – это скомпилированное представление регулярных выражений. У этого класса нет открытых конструкторов. Чтобы создать объект типа Pattern, необходимо сначала вызвать один из статических методов compile(), каждый из которых создает объект Pattern и возвращает ссылку на него. Эти методы принимают регулярное выражение в качестве первого аргумента. Например:

Pattern pattern = Pattern.compile(ex); // ex - строка, определяющая регулярное выражение

После создания объекта Pattern его используют при инициализации объекта Matcher:

Matcher matcher = pattern.matcher(s); // s - строка, подлежащая проверке

Далее можно использовать функции класса Matcher для сопоставления строк (matches()), поиска подстроки (find()) и т.д.

В простейшем случае регулярное выражение – это строка, содержащая последовательность символов. Это означает, что при сопоставлении строк проверяется их эквивалентность, а при поиске осуществляется нахождение полного текста регулярного выражения. Результатом приведенной ниже программы является вывод в консольное окно значения true:

package ua.inf.iwanoff.text;

import java.util.regex.*;

public class FirstRegex {

    public static void main(String[] args) {
        Pattern pattern = Pattern.compile("text");
        Matcher matcher = pattern.matcher("text");
        System.out.println(matcher.matches());
    }

}

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

Вместо сопоставления можно осуществлять поиск первого вхождения шаблона в строку. Например:

    Pattern pattern = Pattern.compile("text");
    Matcher matcher = pattern.matcher("textual");
    System.out.println(matcher.find());    

Теперь мы получим true. Также результатом будет true, если мы будем проверять такие строки, как "the text", "context" и т.п.

Для того, чтобы последовательно найти все вхождения подстроки, можно воспользоваться таким циклом:

    while (matcher.find()) {
        System.out.printf("Найден текст" + " \"%s\", начиная с позиции"
                  + "%d и заканчивая позицией %d.%n", matcher.group(),
                  matcher.start(), matcher.end());
    }

В приведенном примере функция group() возвращает последовательность символов, которые удовлетворяют условиям поиска, функции start() и end() возвращают позиции начала и конца найденной последовательности в выходной строке.

К регулярным выражениям можно добавлять управляющие последовательности, которые начинаются с обратной косой черты (\ - обратный слеш, backslash). Например, можно определять символы по их коду:

Представление Объяснение Кодирование
\0n n – восьмеричное число от 0 до 377 8-битное
\xdd d – шестнадцатеричная цифра
\udddd 16-битное (Юникод)

Можно также использовать управляющие символы:

Представление Символ
\t Табуляция
\v Вертикальная табуляция
\r Возврат каретки
\n Перевод строки
\f Конец страницы
\a Звонок
\e Escape-символ
\b Забой (backspace)

Примечание: Должен находиться внутри квадратных скобок (иначе интерпретируется как граница слова).

\cA\cZ Ctrl+A ... Ctrl+Z

Эквивалентно \x01 ... \x1A (\cA = \001, \cZ = \032)

Для создания сложных регулярных выражений используют так называемые метасимволы – специальные символы, которые можно использовать для образования символьных классов, квантификаторов, управляющих последовательностей т.д. Это следующие символы:

[ ] \ ^ $ . | ? * + ( ) { }

Значение символов зависит от их положения в выражении. Для того, чтобы получить отображение метасимвола как обычного символа текста, ему должен предшествовать обратный слеш (\). Чтобы получить непосредственно символ \, нужно использовать два символа обратной косой черты подряд (\\).

Набор символов в квадратных скобках [ ] – это так называемый символьный класс. Он позволяет указать, что на этом месте в строке может стоять один из перечисленных символов. Например, [abc] задает возможность появления в тексте одного из трех указанных символов [1234567890] определяет соответствие одной из цифр. Возможно указание диапазонов символов: например, [A-Za-z] соответствует всем буквам латинского алфавита. Если нужно указать символы, не входящие в указанный набор, то используют символ ^ внутри квадратных скобок. Например, [^0-9] означает любой символ, кроме цифр.

Пример использования выражения [ ]: "[bcr]at" соответствуют строки "bat", "cat" и "rat"; выражению "[^bcr]at" эти строки отвечать не будут, а строка "hat" – будет.

Для создания единого символьного класса, который объединяет два или более различных символьных классов, используется объединение. Для создания объединения один класс вкладывается внутрь другого: [0-3[7-9]], что соответствует цифрам 0, 1, 2, 3, 7, 8, 9.

Возможно создание пересечения – единственного символьного класса, который определяет определяет строки, подходящие обоим вложенным классам, например, выражение [0-9&&[234]] соответствует цифрам 2, 3 и 4.

Вычитание используется для отрицания одного или нескольких символов класса, например выражение [0-9&&[^345]] создает класс, который соответствует цифрам от 0 до 9, исключая числа 3, 4 и 5.

Для обозначения некоторых символьных классов также используют управляющие последовательности:

Представление Эквивалент Значение
\d [0-9] Цифра
\D [^\d] Любой символ, кроме цифры
\w [A-Za-zа-Яа-Я0-9_] Символы, образующие "слово" (буквы, цифры и символ подчеркивания)
\W [^\w] Символы, не образующие "слова"
\s [ \t\v\r\n\f] Пробельный символ
\S [^\s] Непробельний символ

В фигурных скобках можно определять количество повторения символов, например, {2} (именно два символа), {2, 4} (от двух до четырех), {1,} (один или более). Точка – именно один (любой) символ, * – ноль или более + – один или более, ? – ноль или один.

Например, выражение "A*" соответствует любому количеству последовательно расположенных символов A. Такому шаблону будет соответствовать и пустая строка. Выражение "A+" соответствует строке с как минимум одним символом A. Выражение "A {1,4}" соответствует строкам "A", "AA", "AAA", "AAAA". Выражения "AB?" будут соответствовать строкам "A" и "AB". Выражению "." соответствует любая строка, состоящая из одного символа. Выражение ".+" будет соответствовать любому тексту (один или более любых символов). Выражению "A.+" соответствует любая строка, которая начинается на букву "А".

Существуют специальные символы, которые соответствуют не какому-либо проверяемому символу в строке, а некоторому месту в этой строке. Символом ^ определяется начало строки символом $ – конец строки. Например, выражение "^this" соответствует строке, которая начинается на "this".

Последовательность \b указывает на границу слова, то есть на место между словом и пробелом. Выражение "\bis" соответствует второму is в строке "this is". Выражение "\b\w\w\w\w\b" соответствует слову из четырех букв. Последовательность "\B" соответствует всем местам, кроме границы слова. Выражение "\Bis" соответствует первому is в "this is".

Используется также символ "|" – изображение логического "или", который используется, когда нужно задать несколько вариантов, которым может соответствовать строка:

  Pattern pattern = Pattern.compile("Ivanov|Petrov");
  Matcher matcher = pattern.matcher("These are Ivanov and Petrov and another Ivanov");
  while (matcher.find()) {
      System.out.printf( "\"%s\"", matcher.group());
  }

В приведенном примере будут найдены соответствия каждому из вариантов ("Ivanov" "Petrov" "Ivanov").

Если символ "|" используется не отдельно, а в сочетании с другими элементами, то выражение с этим символом нужно брать в круглые скобки.

Некоторые функции класса String используют регулярные выражения. Например, функции replaceFirst() и replaceAll() возвращают строки, для которых поиск и замена реализованы регулярными выражениями.

Например, нужно удалить пробелы в начале и в конце строки:

  Pattern p = Pattern.compile("^\\s+"); // все пробелы в начале строки
  String s = p.matcher("  These are Ivanov and Petrov and another Ivanov  ").replaceFirst("");
  p = Pattern.compile("\\s+$");         // все пробелы в конце строки
  s = p.matcher(s).replaceAll("");
  System.out.println(s);

1.6 Разделение строки на лексемы

Лексема (token) – это последовательность символов, имеющих определенное совокупное значение. Между отдельными лексемами располагают разделители – пробел, табуляция, новая строка и т.д.. Задача разделения строки на лексемы возникает очень часто – при создании трансляторов, в задачах компьютерного перевода, проверки орфографии и т.д.

Для разделения на лексемы используют несколько способов:

  • использование класса java.util.StringTokenizer
  • использование метода split() класса String
  • использование методов класса java.text.BreakIterator.

Традиционный путь – создание объекта класса java.util.StringTokenizer. Объект этого класса создается с помощью конструктора с параметром типа String, который определяет строку, подлежащую разделению на лексемы:

StringTokenizer st = new StringTokenizer(someString);

После создания объекта можно получить общее количество лексем с помощью метода countTokens(). Класс реализует внутренний "текущий указатель", который указывает на следующее слово. Функция nextToken() возвращает следующую лексему из строки. С помощью функции hasMoreTokens() можно проверить, есть ли еще лексемы.

Метод split() класса String позволяет получить массив строк.

String[] words = someString.split("\\s");

Класс java.text.BreakIterator реализует методы нахождения местоположения границ в тексте.

BreakIterator boundary = BreakIterator.getWordInstance();
boundary.setText(someString);

Работа этого класса будет рассмотрена ниже в примерах программ.

2 Примеры программ

2.1 Просмотр доступных локализаций

Метод getAvailableLocales() позволяет просмотреть доступные локализации. Метод toString() возвращает сокращенное представление локализации. С помощью функции getDisplayName() можно получить более детальную информацию:

package ua.inf.iwanoff.text;

import java.util.Locale;
import java.text.NumberFormat;

public class AvailableLocales {

    public static void main(String[] args) {
        Locale list[] = NumberFormat.getAvailableLocales();
        for (Locale locale : list) {
            System.out.printf("%-25s %s%n", locale.toString(), locale.getDisplayName());
        }
    }

}

2.2 Первая подстрока, которая удовлетворяет шаблону

Следующая программа находит первую подстроку, которая удовлетворяет шаблону.

package ua.inf.iwanoff.text;

import java.util.Scanner;
import java.util.regex.Pattern;
import java.util.regex.Matcher;

public class RegexTest {

    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        System.out.printf("%nВведите регулярное выражение: ");
        String ex = scan.nextLine();
        Pattern pattern = Pattern.compile(ex);
        System.out.printf("Введите строку для поиска: ");
        Matcher matcher = pattern.matcher(scan.nextLine());
        if (matcher.find()) {
            System.out.printf("Найден текст" + " \"%s\", начиная с позиции"
                       + "%d и заканчивая позицией %d.%n", matcher.group(),
                       matcher.start(), matcher.end());
        }
    }

}

2.3 Проверка формата телефонного номера

Предположим, необходимо написать программу, которая проверяет введенную строку на соответствие формату номера стационарного телефона. Считать, что номера должны быть записаны в виде групп цифр, разделенных дефисами. Телефонный номер может быть семизначным и начинаться на 57, или шестизначным и начинаться на 572. Обязательно должны быть введены только последние шесть или семь цифр. Учесть, что номер может содержать код города в скобках (три цифры), со знаком "+" в начале, или без знака "+".

package ua.inf.iwanoff.text;

import java.util.Scanner;
import java.util.regex.Pattern;
import java.util.regex.Matcher;

public class RegexTest {

    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        System.out.printf("%nВведите номер телефона: ");
        String phone = scan.nextLine();
        Pattern p = Pattern.compile("\\+?(380-)?((57-)?\\d{3}|(572-)?\\d{2})-\\d{2}-\\d{2}$");
        Matcher m = p.matcher(phone);
        if (m.matches()) {
            System.out.println("\"" + phone + "\" - правильный формат номера");
        }
        else {
            System.out.println("\"" + phone + "\" - неправильный формат номера");
        }
    }
 
}

2.4 Вывод слов предложения

Предположим, необходимо осуществить ввод предложения с клавиатуры и вывода его слов в отдельных строчках. Можно предложить три варианта:

2.4.1 Использование класса StringTokenizer

Первый (традиционный) вариант построен на использовании класса StringTokenizer:

package ua.inf.iwanoff.text;

import java.util.Scanner;
import java.util.StringTokenizer;

public class UsingStringTokenizer {

    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        String sentence = scan.nextLine();
        StringTokenizer st = new StringTokenizer(sentence);
        while (st.hasMoreTokens()) {
            System.out.println(st.nextToken());
        }
    }

}

2.4.2 Использование класса String

Второй вариант использует метод split() класса String:

package ua.inf.iwanoff.text;

import java.util.Scanner;

public class UsingSplit {

    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        String sentence = scan.nextLine();
        String[] words = sentence.split("\\s");
        for (String word : words) {
            System.out.println(word);
        }
    }

}

При вызове метода split() указывается регулярное выражение, которое определяет разделитель между словами.

2.4.3 Использование класса BreakIterator

Последний вариант построен на использовании класса BreakIterator, который ищет разделители:

package ua.inf.iwanoff.text;

import java.text.BreakIterator;
import java.util.Scanner;

public class UsingBreakIterator {

    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        String sentence = scan.nextLine();
        BreakIterator boundary = BreakIterator.getWordInstance();
        boundary.setText(sentence);
        int start = boundary.first();
        for (int end = boundary.next(); end != BreakIterator.DONE; 
                                start = end, end = boundary.next()) {
            System.out.println(sentence.substring(start, end));
        }
    }

}

В этом варианте в отдельных строчках выводятся не только слова, но и разделители.

3 Задания на самостоятельную работу

3.1 Вывод значения с учетом всех возможных локализаций

Ввести с клавиатуры действительное значение, вывести его в консольное окно с учетом всех доступных локализаций.

3.2 Вывод слов предложения в обратном порядке

Ввести с клавиатуры предложение с помощью функции nextLine() объекта типа Scanner. Вывести в консольное окно слова предложения в обратном порядке. Использовать функцию split().

3.3 Прохождение строки в обратном порядке*

Осуществить с помощью итератора прохождение строки от заданного индекса к началу строки.

3.4 Форматированный вывод числа

Вывести число 100 в шестнадцатеричном и восьмеричном представлении. Вывести числа в диапазоне от 100000 до 10000000 с шагом 100000 в формате, выбирающем более короткое представление. Вывести 5 вещественных чисел, выровняв их по левому краю и задав ширину поля вывода 12, а точность – 4 знака после запятой.

3.5 Нахождение всех подстрок, удовлетворяющих определенным требованиям*

Ввести строку, найти и вывести на экран, вывести все подстроки, которые соответствуют шаблону.

3.6 Маска файла*

Вывести имена файлов, удовлетворяющих маске файла. Файл должен начинаться на определенное сочетание букв, первая буква должна быть задана как вариант из двух букв, длина имени не определена, расширение файла задать как одно из трех возможных вариантов. Имя папки файла задать точно.

3.7 Замена шаблона

Заменить в тексте все шаблоны типа %user%Nick%/user% на <a href="http://www.my.by/search.htm?param=Nick">Nick</a>.

3.8 Проверка формы адреса е-mail*

Написать программу, которая проверяет введенную строку на соответствие формату адреса электронной почты. Формат адреса должен соответствовать следующим правилам. Первым должно идти имя учетной записи. Оно начинается с буквы латинского алфавита, после которой могут следовать другие символы латинского алфавита, цифры, знак подчеркивания, затем символ "@", после него имя сервера. Имя сервера должно состоять из нескольких частей (минимум двух), разделенных точками. Последняя часть имени сервера – это имя домена первого уровня. Оно может состоять только из букв латинского алфавита.

3.9 Форматированный вывод

Создать класс Person с полями name (имя), surname (фамилия), country (страна), language (язык), date (дату выступления). Создать класс Conference (Конференция) с полями persons (список лиц), topic (тема), city (город). Создать метод, выводящий информацию о каждом участнике конференции в соответствии с локализацией, заданной с его страной. Использовать класс SimpleDateFormat для задания пользовательского формата.

4 Контрольные вопросы

  1. Какая разница между интернационализацией и локализацией?
  2. Какое общее назначение пакета java.text?
  3. В чем преимущества сортировки с помощью класса Collator?
  4. Как осуществляется форматирование данных с учетом локализации?
  5. В чем заключается форматирование данных?
  6. Какие дополнительные возможности предоставляет функция printf()?
  7. Как исползуется CharacterIterator?
  8. Что такое регулярное выражение?
  9. Как в Java осуществляется работа с регулярными выражениями?
  10. Какие существуют способы разделения текста на лексемы?
  11. В чем преимущесва использования метода split() по сравнению со средствами StringTokenizer?
  12. Как использовать класс BreakIterator для разбиения строки на лексемы?

 

up