Тема 2.2. Основные инструментальные средства языка Java

advertisement
-1Прикладное программирование в ТС
Лекция 3-04
Лекция 3-04
Тема 2.2. Основные инструментальные средства языка Java
2.2.1. Обработка ошибок в Java
2.2.1.1. Классы исключений
2.2.1.2. Организация обработки исключений
2.2.1.3. Передача обработки исключения вызвавшему методу
2.2.1.4. Передача обработки исключения по иерархии классов исключений
2.2.1.5. Создание собственных исключений
2.2.2. Интерфейсы в Java
2.2.2.1. Концепция интерфейсов
2.2.2.2. Объявление интерфейсов
2.2.2.3. Наследование интерфейсов
2.2.2.4. Реализация интерфейсов в классах
2.2.2.5. Переменные интерфейсного типа и их преобразования
2.2.3. Создание и использование пакетов в Java
2.2.4. Работа с потоками в Java
2.2.4.1. Процессы и потоки
2.2.4.2. Реализация потоков в Java
2.2.4.3. Приоритеты и группы потоков
2.2.4.4. Синхронизация потоков
2.2.5. Вложенные классы
2.2.6. Регулярные выражения в Java
2.2.6.1. Основные сведения о регулярных выражениях
2.2.6.1.1. Синтаксис регулярного выражения
2.2.6.1.2. Операция альтернации
2.2.6.1.3. Одиночный метасимвол
2.2.6.1.4. Квантификаторы
2.2.6.1.5. Классы символов
2.2.6.1.6. Специальные символы
2.2.6.1.7. Анкеры
2.2.6.1.8. Группировка элементов и обратные ссылки
2.2.6.2. Класс Pattern
2.2.6.2.1. Создание объекта класса Pattern
2.2.6.2.2. Методы класса Pattern
2.2.6.3. Класс Matcher
2.2.6.3.1. Создание объекта класса Matcher
2.2.6.3.2. Операции с регионами
2.2.6.3.3. Методы поиска соответствий
2.2.6.3.4. Методы замены
2.2.6.4. Класс PatternSyntaxException
2.2.6.5. Методы класса String для работы с регулярными выражениями
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
-2Прикладное программирование в ТС
Лекция 3-04
Тема 2.2. Основные инструментальные средства языка Java
2.2.1. Обработка ошибок в Java
2.2.1.1. Классы исключений
Во время своей работы программа может сталкиваться с различными ситуациями,
которые делают невозможным дальнейшее выполнение программы. Эти ситуации обычно
порождаются одной из следующих основных причин:
 ошибочное программирование (например, задание недопустимого индекса
массива или недопустимого значения переменной);
 ввод пользователем неверных данных (например, задание значения делителя
равным нулю);
 ошибки компилятора и интерпретатора Java.
Поэтому программист (особенно в том случае, когда он делает программный
продукт для внешнего использования) должен проанализировать все возможные причины
возникновения ошибок в своей программе и организовать обработку возможных ошибок.
Ошибка в программе, как правило, происходит при выполнении некоторых условий, и
программисту достаточно проверить эти условия и написать фрагмент программы,
обрабатывающей данную ошибку, например:
int arrayIndex;
// Индекс массива
…
if (arrayIndex < 0)
{
// Действия, если индекс массива – отрицательное число
}
else
{
// Действия, если индекс массива больше или равен 0
}
Многие ошибки являются типовыми, или стандартными (например, деление на
нуль) и поэтому и их обработка является в большинстве случаев типовой – сигнализация о
характере и месте возникновения ошибки и окончания работы программы. Таким
образом, обработку типовых ошибок может выполнять не программист, а средства языка
программирования и/или среда выполнения программы. Кроме того, программисту
следует оставить возможность самому организовать нужную ему обработку типовых
ошибок, а также обрабатывать нестандартные ошибки.
В объектно-ориентированных языках программирования, в том числе и Java, при
возникновении ошибки создается объект определенного класса, соответствующего
возникшей ситуации, содержащий сведения о том, что, где и когда произошло. Этот
объект передается на обработку программе, в которой возникла ошибка.
Если программа на языке Java не обрабатывает ошибку, то объект передается
обработчику по умолчанию среды выполнения Java. Обработчик выводит в стандартный
вывод System.err (обычно на дисплей)
сообщение об ошибке и прекращает
выполнение программы.
Объекты-ошибки в Java создаются классами, образующими свою собственную
иерархию.
Суперклассом для всех классов ошибок в Java является класс Throwable,
являющийся подклассом класса Object. Только экземпляры объектов этого класса и
всех других классов, входящих в иерархию этого класса (т.е. все прямые и косвенные
потомки класса Throwable) могут обрабатывать ошибки в среде выполнения Java.
В классе Throwable определены следующие основные методы:
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
-3Прикладное программирование в ТС
Лекция 3-04
 public String getMessage() – возвращает строку с информацией об
ошибке;
 public
String
getLocalizedMessage() – возвращает строку с
информацией об ошибке в локализованном виде (например, на русском языке);
 public String toString() – выводит имя класса ошибки, а затем строку
с информацией об ошибке;
 public void printStackTrace() – отображает на стандартном выводе
System.err диагностический вывод обработчика ошибок по умолчанию: имя класса
ошибки и стек вызовов методов (первым выводится метод, при выполнении которого
произошла ошибка, затем метод, вызвавший данный метод и т.д.) (для каждого метода
выводится его имя и номер строки, в которой произошла ошибка или строка вызова
другого метода);
 public StackTraceElement[] getStackTrace() – возвращает массив
объектов класса StackTraceElement.
Все элементы массива объектов класса StackTraceElement (кроме последнего)
содержат элементы стека вызовов методов. Последний элемент содержит точку создания
объекта-исключения в программе. С помощью методов класса StackTraceElement
можно получить следующие характеристики элемента стека:
 public String getClassName() – возвращает имя класса, содержащего
точку выполнения для данного элемента стека;
 public String getFileName() – возвращает имя файла, содержащего
точку выполнения для данного элемента стека;
 public String getMethodName() – возвращает имя метода, содержащего
точку выполнения для данного элемента стека;
 public int getLineNumber() – возвращает номер строки для точки
выполнения данного элемента стека;
 public boolean isNativeMethod() – возвращает true, если метод,
содержащий точку выполнения для данного элемента стека, является внешней функцией
C/C++ и false – в противном случае;
 public String toString() – возвращает строковое представление
данного элемента стека.
Использование метода getStackTrace() класса Throwable и методов класса
StackTraceElement позволяет формировать пользовательские сообщения об ошибках.
Класс Throwable имеет два подкласса: Error (класс ошибок) и Exception
(класс исключений):
Класс Error, образующий, в свою очередь, собственную иерархию классов,
обрабатывает исключительные ситуации, связанные с ошибками, которые возникают при
аппаратных сбоях или сбоях в работе операционной системы, а также компонент
виртуальной машины Java (например, класс OutOfMemoryError, являющийся
подклассом класса VirtualMachineError, который, в свою очередь, является
подклассом класса Error, обрабатывает ситуации, связанные с неверным
распределением памяти для виртуальной машины Java).
Программист не должен сам обрабатывать эти ошибки и если они постоянно
повторяются, поставить в известность разработчика JDK (фирму Sun).
Класс Exception образуют те исключения, для которых программист на языке
Java должен организовать свою собственную обработку. Всего в классе Exception в
содержится более 200 исключений.
Среди этих исключений следует отметить класс RuntimeException, который
служит родительским классом для тех исключений, которые могут возникать только во
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
-4Прикладное программирование в ТС
Лекция 3-04
время выполнения программы (а не во время ее компиляции). Класс
RuntimeException имеет следующую особенность: обработку
исключений этого
класса и производных от него классов в программе описывать необязательно (поскольку
исключения этого класса могут возникнуть во время выполнения программы, а могут и не
возникнуть). Если обработка какого-либо исключения времени выполнения в программе
отсутствует, оно, как и другие исключения обрабатывается обработчиком по умолчанию
среды выполнения Java.
Другим важным классом исключений Java является класс IOException. Этот
класс и порожденные им классы охватывают все исключительные ситуации, связанные с
вводом и выводом данных в программе.
В табл. 2.1 перечислены те исключения, с которыми программист вполне вероятно
столкнется при написании своей программы.
Следует отметить, что, в отличие от большинства других иерархий классов, класс
Throwable и его подклассы находятся в разных пакетах. Так, классы Throwable,
Error, Exception, класс RuntimeException и большинство его подклассов
находятся в пакете java.lang, а классы, обрабатывающие исключения ввода-вывода
находятся в пакете java.io. Поэтому импортирование всех классов какого-либо пакета
автоматически делает доступными также классы обработки ошибок и исключений,
которые могут возникнуть при выполнении методов в классах данного пакета.
2.2.1.2. Организация обработки исключений
Как указывалось выше, для всех исключений, кроме, возможно, исключений
времени выполнения, в программе на языке Java необходимо организовывать обработку
исключений.
После того как Java создаст объект-исключение, этот объект посылается
прикладной программе; такая операция называется броском (throw) исключения.
Прикладная программа должна перехватить или поймать (catch) исключение.
Программист должен (за исключением ошибок времени выполнения), организовать
собственную обработку исключения, используя блоки try, catch и finally.
Для обработки исключения фрагмент программы, который может вызвать
исключение, помещается в программный блок try. Если один из операторов блока
бросает исключение, Java игнорирует остальные операторы в блоке try и переходит на
непосредственно следующий за блоком try блок catch, в котором программа
обрабатывает исключение. Если же все проходит нормально, весь код внутри блока try
выполняется, а блок catch пропускается.
Программный блок catch перехватывает исключение, возбужденное системой
Java, поэтому после ключевого слова catch задается вызов объекта-исключения
заданного класса. При необходимости к методам объекта-исключения можно обратиться
через этот объект.
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
-5Прикладное программирование в ТС
Лекция 3-04
Таблица 2.1. Примеры исключений Java
Исключение
ArithmeticException
ArrayStoreException
Причина возникновения
Математические ошибки, например,
деление на ноль (исключение времени
выполнения).
Программа пытается записать в массив
неправильный тип данных(исключение
времени выполнения).
ArrayIndexOutOfBoundsException
Программа пыталась обратиться к
несуществующему индексу массива
(исключение времени выполнения).
NegativeArraySizeException
Попытка создать массив с
отрицательным размером (исключение
времени выполнения).
Программа пыталась обратиться к
несуществующей позиции символа в
строке (исключение времени
выполнения).
Общие исключения ввода/вывода,
например, невозможность чтения из
файла
Обращение к несуществующему файлу
(исключение ввода/вывода).
Достигнут конец файла (исключение
ввода/вывода).
Ссылка на несуществующий объект –
возникает при попытке использовать
вместо объекта значение null
(например, при доступе к полю или
изменении поля объекта null)
(исключение времени выполнения).
StringIndexOutOfBoundsException
IOException
FileNotFoundException
EOFException
NullPointerException
NumberFormatException
InvalidParameterException
AccessControlException
Неправильное преобразование между
строками и числами (исключение
времени выполнения).
Передача неверного параметра методу
(исключение времени выполнения).
Приложение или апплет пытается
выполнить действие, запрещенное
режимом защиты (исключение времени
выполнения).
Следует отметить, что в одном блоке try может быть сгенерировано несколько
исключений. В этом случае каждое из этих исключений обрабатывается своим блоком
catch, который следуют друг за другом после блока try. Кроме того, иногда нужно
выполнить некоторый блок кода вне зависимости от того, было исключение или нет. Для
этого после последнего оператора catch можно добавить программный блок finally.
Общий вид блоков обработки исключений приведен ниже:
try
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
-6Прикладное программирование в ТС
Лекция 3-04
{
// Операторы, которые могут вызвать исключения
}
catch (имя-класса-исключения-1 идентификатор-объекта-1)
{
// Операторы обработки исключения-1
}
catch (имя-класса-исключения-2 идентификатор-объекта-2)
{
// Операторы обработки исключения-2
}
…
finally
{
// Операторы, выполняемые после нормального завершения
// блока try или по окончанию работы блоков catch
}
Между блоками try, catch и finally не должно быть никаких других
предложений.
В блоке или блоках catch производится обработка исключения, например, вывод
диагностического сообщения (при этом могут быть использованы приведенные выше
методы классов Throwable и StackTraceElement), завершение программы (с
помощью метода exit() класса System) и другие необходимые действия, например:
String str1, str2 ="abc";
…
try
{
if (str1.equals(str2))
System.out.println("Strings str1 and str2 are equal");
}
catch(NullPointerException npe)
{
System.out.println("String str1 has no value");
System.exit(2);
}
Если в программе переменной str1 не присвоено значение, то при выполнении
операции
сравнения
строк
на
равенство
будет
брошено
исключение
NullPointerException. При его обработке в блоке catch будет выдано
диагностическое сообщение, и выполнение программы будет прекращено.
В блоке catch можно вообще не выполнять никаких действий. В этом случае блок
не будет содержать операторов, например:
int x = 0, x1 = 6, x2 = 0;
…
try
{
x = x1 / x2;
}
catch (ArithmeticException ae) {}
x++;
Поскольку при делении на 0 было брошено исключение, присвоение переменной x
результата деления не произошло, т.е. перед выполнением операции инкремента
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
-7Прикладное программирование в ТС
Лекция 3-04
переменная x сохранила значение 0, присвоенное ей при инициализации. Поэтому, в
результате выполнения операции инкремента значение переменной x будет равно 1.
2.2.1.3. Передача обработки исключения вызвавшему методу
В приведенных примерах программные блоки try и catch размещены в том
месте программы, который может генерировать исключение. Однако можно перенести
обработку исключения из метода, где оно бросается в метод, вызвавший данный метод. В
этом случае в объявлении метода добавляется ключевое слово throws, за которым
задается список исключений, которые данный метод бросает вызвавшему его методу,
например:
void myMethod()
throws ArithmeticException, NumberFormatException
Многие методы классов библиотеки Java не обрабатывают исключений сами, а
передают их вызвавшему их методу. Так, например, полное объявление метода
преобразования строки в целое число parseInt() в классе Integer имеет следующий
вид:
public static int parseInt(String s)
throws NumberFormatException
Наличие в объявлении метода выбрасываемого исключения или списка
выбрасываемых исключений означает, в вызывающем методе необходимо поместить
вызов данного метода в блок try и задать и задать блоки catch для обработки каждого
исключения, перечисленного в списке исключений вызываемого метода.
Пример обработки исключения в вызывающем методе:
int index1;
String arg = "1.0r";
…
try
{
index1= Integer.parseInt(arg);
}
catch (NumberFormatException e)
{
System.out.println("Нечисловое значение " + arg +
" переменной arg");
System.exit(7);
}
Можно также, в свою очередь, бросить исключения в метод, вызвавший данный
очередь и т.д. В приложении первым вызываемым методом является метод main(),
поэтому, если в нем не организована обработка исключений, брошенных вызываемыми
им методами, эта обработка выполняется обработчиком исключений среды выполнения
Java.
Блок finally обычно используется в тех случаях, когда необходимо
проанализировать, как был выполнен оператор, который мог бросить исключение.
2.2.1.4. Передача обработки исключения по иерархии классов исключений
Объекты-исключения передаются вверх от класса классу вверх по иерархии
классов обработки ошибок и исключений до пор, пока какой-то из методов очередного
класса не обработает их. Если исключение доходит до среды времени выполнения Java, то
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
-8Прикладное программирование в ТС
Лекция 3-04
она обрабатывает исключения предусмотренным по умолчанию образом, обычно
генерируя сообщение об ошибке.
Поэтому в качестве параметра в блоке catch можно указать объект любого
класса, который лежит в уровне иерархии выше, чем объект класса, бросившего
исключение. Например, для класса NumberFormatException это будут (в порядке
уровней иерархии) классы IllegalArgumentException, RuntimeException,
Exception и Throwable, а для класса FileNotFoundException – классы
IOException, Exception и Throwable. Такая возможность позволяет более
рационально организовать обработку исключений (например, обрабатывать все
исключения ввода-вывода в одном блоке). Если для какого-либо объекта-исключения нет
блока catch для этого исключения, либо для одного из исключений, лежащих выше по
иерархии, то обработку исключения выполняет обработчик среды выполнения Java.
Однако следует иметь в виду, что блоки catch просматриваются в том порядке, в
котором они размещаются после блока try. Так, при следующем расположении блоков
catch:
try
{
// Операторы, проверяемые на выбрасывание исключений
}
catch (IOException)
{
// Обработка исключения ввода-вывода
}
catch (FileNotFoundException)
{
// Обработка исключения: файл не найден
}
блок catch для обработки исключения FileNotFoundException не будет
выполняться никогда, поскольку первый блок catch будет перехватывать все
исключения ввода-вывода, в том числе и исключение, выбрасываемое, когда файл не
найден. Поэтому первыми необходимо задавать блоки catch для исключений,
расположений ниже в иерархии исключений.
2.2.1.5. Создание собственных исключений
Хотя в Java имеются классы исключений для многих стандартных ошибок, в
прикладной программе могут встретиться ситуации, которые надо обрабатывать как
исключения, например,
сложения двух чисел в определенном диапазоне. Если
пользователь вводит число, выходящее за установленные пределы, программа может
возбуждать собственное (пользовательское) исключение.
Для создания и возбуждения собственных исключений сначала необходимо
описать класс для этого исключения, причем этот класс обычно порождается от класса
Exception или производных от него классов.
Так, описание класса исключений NumberRangeException, связанных с тем,
что число лежит вне заданного числового диапазона (исключение числового диапазона)
будет иметь следующий вид:
public class NumberRangeException extends RuntimeException
{
public NumberRangeException()
{
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
-9Прикладное программирование в ТС
Лекция 3-04
super("Numbers out of range");
}
}
Как можно видеть из примера, для описания нового исключения необходимо
создать класс для данного исключения и в нем определить конструктор класса. Этот
конструктор получает параметр, представляющий собой сообщение по умолчанию,
которое класс возвращает, если вызывается методы, наследуемые от класса Throwable.
Внутри конструктора вызывается конструктор суперкласса, т.е. в конце концов,
конструктор класса Throwable.
В отличие от исключений, определенных в классах библиотеки Java, которые
бросаются автоматически при возникновении ошибки в программе, пользовательские
исключения необходимо бросать «вручную». Для этого в блоке try используется
оператор throw, который имеет следующий формат:
throw объект-исключение;
Так же, как и для библиотечных исключений, брошенное исключение
обрабатывается в своем блоке catch.
Для описанного выше исключения NumberRangeException его выбрасывание
и обработка могут быть реализованы с помощью следующих операторов:
try
{
//Числа не укладываются в заданный диапазон
if ((int1 < 10) || (int1 > 20) ||
int2 < 10) || (int2 > 20))
{
throw new NumberRangeException();
}
answer = int1 + int2;
}
catch (NumberRangeException e)
{
System.out.println(e.toString());
}
В этом примере при вводе двух числа int1 и int2 в диапазоне от 10 до 20, будет
произведено их сложение и результат присвоен переменной answer. В противном случае
программа возбуждает исключение NumberRangeException и выводит сообщение об
ошибке.
Создавать или не создавать собственные исключения зависит от конкретной
реализации программы. Так, если приведенная выше ситуация встречается в программе
один или два раза, для нее вряд ли имеет смысл создавать новый класс исключений.
Однако, если данная ситуация является типовой, т.е. встречается в программе достаточно
часто и, кроме того, она будет встречаться и в других программах, которые
предполагается написать при реализации программного продукта, предпочтительным
является создания нового исключения.
2.2.2. Интерфейсы в Java
2.2.2.1. Концепция интерфейсов
Если класс потомок может иметь только один родительский класс, то такое
наследование называется одиночным наследованием. Однако иногда необходимо, чтобы
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 10 Прикладное программирование в ТС
Лекция 3-04
данный класс имел несколько суперклассов (такое наследование называется
множественным наследованием). Рассмотрим это на конкретном примере.
Пусть у нас имеется класс ElectronicDevice, подклассом которого является
класс Computer и класс MassStorage, подклассом которого является класс CDROM, как
это представлено на рис. 2.1.
ccllaassss E
ElleeccttrroonniiccD
Deevviiccee
ccllaassss M
MaassssSSttoorraaggee
ccllaassss C
Coom
mppuutteerr
ccllaassss C
CD
DR
RO
OM
M
Рис. 2.1. Независимые иерархии классов ElectronicDevice и MassStorage
Свойства класса Computer описывают технические характеристики компьютера
(процессор, оперативную память и т.д.), а свойства класса CDROM описывают его объем и
размещенные на нем файлы – звуковые, видеофайлы или программные файлы.
Как видно, эти классы не имеют никаких общих свойств и методов. Однако, если
компьютеры и CD-ROM продаются в магазине, у них как у товара, появляются общие
свойства, например, цена, количество данного товара на складе и количество проданных
товаров, а также методы, описывающие процесс поступления, хранения и продажи
товаров.
Эти одинаковые для компьютеров и CD-ROM свойства и методы можно описывать
для каждого класса отдельно, однако это увеличивает объем программного кода и
затрудняет модификацию программ (например, все изменения в одном из методов,
необходимо изменить в каждом классе, представляющем товар). Более изящным
решением является множественное наследование, как это показано на рис. 2.2.
ccllaassss E
ElleeccttrroonniiccD
Deevviiccee
ccllaassss C
Coom
mppuutteerr
ccllaassss G
Gooooddss
ccllaassss M
MaassssSSttoorraaggee
ccllaassss C
CD
DR
RO
OM
M
Рис. 2.2. Множественное наследование классов
В этом примере и класс Computer и класс CDROM являются также подклассами
класса Goods, и, соответственно, свойства и методы для товаров, описанные в классе
Goods, доступны и в классах Computer и CDROM.
Множественное наследование, поддерживается в ряде объектно-ориентированных
языков, в частности, в языке C++. Однако Java, как это видно из определения класса,
поддерживает только одиночное наследование. Причиной такого решения создателей Java
явилась, в первую очередь, сложность программной реализации, а также некоторые
неоднозначности, связанные с множественным наследием (например, какой метод
вызывать в том случае, когда в родительских классах есть методы с одинаковым именем и
одинаковыми параметрами, но реализующие разные алгоритмы).
Однако частично концепцию множественного наследования в Java можно
реализовать с помощью интерфейсов.
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 11 Прикладное программирование в ТС
Лекция 3-04
Интерфейсы – это абстрактные классы, которые являются полностью
нереализуемыми. Это означает, что в интерфейсе допустимы только объявления методов
(без описания тела методов). Кроме того, данные интерфейса содержат только
переменные с модификаторами static final, т.е. являются статическими
константами.
В отличие от абстрактных классов, подклассы который могут иметь только один
родительский класс, для классов могут быть реализованы несколько интерфейсов, что
является альтернативой множественному наследованию. Главное различие между
наследованием интерфейсов и множественным наследованием заключается в том, что
интерфейсы позволяют наследовать только описания методов, а не их реализацию.
Поэтому, если класс реализует множественные интерфейсы, этот класс должен
обеспечить реализацию всех методов, определенных в интерфейсе. Хотя это более
ограниченное свойство, чем множественное наследование, оно во многих случаях
является очень полезным.
Таким образом, между интерфейсами и абстрактными классами существует два
важных отличия:
 интерфейсы предоставляют некоторую разновидность множественного
наследования, поскольку класс может реализовать несколько интерфейсов. С другой
стороны, класс расширяет всего один класс, а не два или более, даже если все они состоят
только из абстрактных методов.
 абстрактный класс может содержать частичную реализацию, защищенные
компоненты, статические методы и т. д., тогда как интерфейс ограничивается открытыми
методами, для которых не задается реализация, и константами.
Эти отличия обычно определяют выбор средства, которое должно применяться для
конкретного случая. Если необходимо воспользоваться множественным наследованием,
применяются интерфейсы. Однако при работе с абстрактным классом вместо
перенаправления методов можно частично или полностью задать их реализацию, чтобы
облегчить наследование.
Реализация в Java приведенной на рис.2.2 схемы множественного наследования
представлена на рис. 2.3.
ccllaassss E
ElleeccttrroonniiccD
Deevviiccee
ccllaassss C
Coom
mppuutteerr
iinntteerrffaaccee G
Gooooddss
ccllaassss M
MaassssSSttoorraaggee
ccllaassss C
CD
DR
RO
OM
M
Рис. 2.3. Реализация множественного наследования в Java с помощью интерфейса
2.2.2.2. Объявление интерфейсов
Интерфейсы имеют следующий формат объявления:
модификаторы interface идентификатор-интерфейса
{
тело-интерфейса
}
Идентификатор-интерфейса определяет имя интерфейса (соглашения об
именовании интерфейсов те же, что и для классов), а тело-интерфейса описывает
абстрактные методы и переменные, составляющие интерфейс. Поскольку в интерфейсе
определяются только абстрактные методы, поэтому сам интерфейс тоже является
абстрактным и должен иметь модификатор abstract.
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 12 Прикладное программирование в ТС
Лекция 3-04
Однако, в соответствии со спецификацией Java, задание модификатора abstract
для интерфейса является излишним. Из модификаторов доступа для интерфейсов можно
использовать либо модификатор доступа по умолчанию (тогда интерфейс доступен только
для классов и интерфейсов того пакета, в котором он объявлен), либо модификатор
public (тогда интерфейс доступен всем другим классам и интерфейсам). Модификаторы
доступа protected и private можно использовать только для рассматриваемых далее
внутренних интерфейсов.
Независимо от того, какие модификаторы используются при объявлении
переменных, все переменные в интерфейсах могут быть только public, final и
static (даже если эти модификаторы не указаны в объявлениях переменных). Все
переменные в интерфейсе должны быть обязательно инициализированы с помощью
констант, выражений или вызова статических методов (как и для переменных с
модификатором static в классах).
Основная задача интерфейсов — объявлять абстрактные методы, которые будут
описываться в других классах, поэтому синтаксис объявления метода в интерфейсе очень
похож на объявление метода класса, но в отличие от него, у методов интерфейсов
отсутствуют тела, т.е. метод интерфейса состоит только из объявления и имеет
следующий формат:
возвращаемое-значение имя-метода (параметры) throws исключения;
например:
void windowOpened(WindowEvent e);
Как и для обычных объявлений методов, ключевое слово throws указывается
только в том случае, если данный метод будет бросать какие-либо исключения.
Так как предполагается, что все методы интерфейса являются абстрактными и
доступными всем классам, нет необходимости указывать для них модификаторы
abstract и public.
Интерфейсы, так же как и классы, компилируются в отдельные файлы с именами,
совпадающими с именами интерфейсов и расширением .class.
Пример объявления интерфейса:
interface TestNumber
{
NULL = 0;
ONE = 1;
boolean testNull(int number);
boolean testOne(int number);
}
2.2.2.3. Наследование интерфейсов
Интерфейсы так же, как и классы, могут расширяться с помощью ключевого слова
extends, однако, в отличие от классов, допускается расширение интерфейсом сразу
нескольких других интерфейсов. Так, например, для интерфейсов блоков прослушивания
событий мыши, используемых в графических приложениях Java, часть интерфейсов
имеют следующие объявления:
public interface EventListener
{
// Тело интерфейса
}
public interface MouseListener extends EventListener
{
// Тело интерфейса
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 13 Прикладное программирование в ТС
Лекция 3-04
}
public interface MouseMotionListener extends EventListener
{
// Тело интерфейса
}
public interface MouseInputListener extends MouseListener,
MouseMotionListener
{
// Тело интерфейса
}
Как видно из приведенных объявлений, интерфейсы, в отличие от классов, могут
создавать сложные иерархические структуры не только с несколькими потомками, но и с
несколькими родителями, что наглядно видно на рис. 2.4.
E
EvveennttL
Liisstteenneerr
M
MoouusseeL
Liisstteenneerr
M
MoouusseeM
MoottiioonnL
Liisstteennee
rr
M
MoouusseeIInnppuuttL
Liisstteenneerr
Рис. 2.4. Пример иерархии интерфейсов в Java
Переменные и методы, объявленные в родительском интерфейсе, так же как и в
классах, наследуются всеми интерфейсами-потомками данного интерфейса.
Если какой-либо интерфейс объявляет переменную с именем, которая уже
объявлена в одном из его интерфейсов-предков, то при использовании этого интерфейса
значение переменной в интерфейсах-предках будет недоступным. Такая ситуация
называется сокрытием (hiding) переменной.
Так, в объявлении интерфейсов I и J:
interface I
{
i = 1; j = 0;
}
interface J extends I
{
i = 1; j = 1;
}
переменная I.j имеет значение 0, а переменная J.j – значение 1, т.е. переопределение
переменной j в интерфейсе J скрывает значение j в родительском интерфейсе I.
Следует также отметить, что в отличие от классов, интерфейсы не имеют единого
корневого интерфейса, наподобие класса Object. Поэтому интерфейсы могут
образовывать множество связанных или не связанных между собой древовидных
структур.
В связи с такой структурой наследования в интерфейсах могут возникнуть
проблемы неоднозначного и множественного наследования (ambiguous and multiple
inheritance).
Так, при следующем объявлении интерфейсов:
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 14 Прикладное программирование в ТС
Лекция 3-04
interface I
{
i = 1; j = 0;
}
interface J extends I
{
j = 1;
}
interface K extends I, J
{
k = 2;
}
значение переменной j в интерфейсе K является неоднозначным, поскольку из
интерфейса I наследуется значение j, равное 0, а из интерфейса J – значение j, равное
1. Такое наследование вызовет ошибку при компиляции. Наследование значения i в
интерфейсе является множественным, поскольку оно наследуется как из интерфейса I,
так из интерфейса J (который, в свою очередь, наследует ее из интерфейса I).
Так же, как и для классов, методы в интерфейсах-потомках наследуются от
интерфейсов-родителей и могут переопределять методы в интерфейсах-родителях.
Однако, если методы имеют различные возвращаемые типы, как в следующем примере:
interface I0
{
int a1(int x, int y);
}
interface I1 extends I0
{
float a1(float x, float y);
}
то такое переопределение вызовет ошибку во время компиляции. Ошибка компиляции
возникает также, если в переопределяемых методах после ключевого слова throws
определены разные исключения.
2.2.2.4. Реализация интерфейсов в классах
Объявленные в интерфейсе методы нельзя использовать до тех пор, пока
некоторый класс или классы не реализуют данный интерфейс и не переопределят (каждый
в своем теле класса) эти методы.
Для того, чтобы указать, что какой-либо класс наследует, а точнее, реализует
интерфейс или интерфейсы класс объявляется следующим образом:
class идентификатор-класса implements список-интерфейсов
{
тело-класса
}
Идентификатор определяет имя нового класса, интерфейс – имя реализуемого
интерфейса (может быть задано несколько имен интерфейсов, разделенных запятыми), а
тело-класса – тело нового класса.
При реализации какого-либо интерфейса необходимо переопределить все методы,
объявленные в нем. Это приходится делать даже в том случае, если некоторые методы в
данном классе не используется. Если этого не сделать, класс будет считаться
компилятором Java абстрактным классом.
Чтобы избежать этой ситуации, неиспользуемые методы обычно описывают в
классе с пустым телом метода, например:
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 15 Прикладное программирование в ТС
Лекция 3-04
public void windowOpened(WindowEvent e) { }
Пример использования интерфейса:
Пусть объявлен интерфейс IntParmIO:
interface IntParmIO
{
void parmInput(String name, int value);
void parmOutput(String name, int value);
}
Этот интерфейс может быть реализован, например, в классе MyClass:
Class MyClass implements IntParmIO
{
// …
void parmOutput(String name, int value)
{
System.out.println(name + value);
}
void parmInput(String name, int value) {}
}
В классе MyClass определена конкретная реализация метода parmOutput, а
метод parmInput не используется, поэтому для него задано пустое тело метода.
2.2.2.5. Переменные интерфейсного типа и их преобразования
Можно определять переменные интерфейсного типа – ссылочные переменные,
использующие в качестве типа переменной идентификатор интерфейса. В такой
переменной можно сохранять экземпляр того класса, который реализует заданный
интерфейс. При вызове метода для такой переменной будет вызываться та версия
объявленного в интерфейсе метода, которая определена в данном классе.
Пример использования переменной интерфейсного типа:
Class InterfaceVar
{
// …
IntParmIO varOut = new MyClass();
varOut.parmOutput("x=", 15);
}
Переменная varOut объявлена как переменная интерфейсного типа IntParmIO,
в которой хранится экземпляр класса MyClass, реализующего интерфейс IntParmIO
(интерфейс IntParmIO и класс MyClass определены выше). При вызове метода
parmOutput() для этой переменной будет использована реализация этого метода,
определенная в классе MyClass.
Так же, как и для классов, для переменных интерфейсного типа можно выполнять
преобразования одного типа в другой.
Расширяющими преобразованиями для переменных интерфейсного типа
являются следующие преобразования:
 из любой переменной класса в любую переменную заданного интерфейсного
типа, при условии, что класс реализует заданный интерфейс;
 из типа null к любому интерфейсному типу;
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 16 Прикладное программирование в ТС
Лекция 3-04
 из любого интерфейсного типа к заданному интерфейсному типу при условии,
что интерфейсный тип является потомком заданного интерфейсного типа;
 из любого интерфейсного тип к типу Object.
Расширяющие преобразования выполняются автоматически и не вызывают ошибок
во время выполнения программы.
Сужающими преобразованиями для переменных интерфейсного типа являются
следующие преобразования:
 из любой переменной класса в любую переменную заданного интерфейсного
типа, при условии, что класс не имеет модификатор final и не реализует заданный
интерфейс;
 из типа Object к любому интерфейсному типу;
 из любого интерфейсного типа к заданной переменной класса, который не имеет
модификатора final;
 из любого интерфейсного типа к заданной переменной класса, который имеет
модификатор final и реализует данный интерфейс;
 из любого интерфейсного типа к заданному интерфейсному типу при условии,
что интерфейсный тип не является потомком заданного интерфейсного типа и оба
интерфейсных типа не содержат метода с одним и тем же именем, но разными
возвращаемыми значениями.
Выполнение сужающих преобразований требует использования оператора
приведения типа
(идентификатор-типа)переменная
а также проверки законности преобразования во время выполнения. Если преобразование
является незаконным, бросается исключение ClassCastException.
Интерфейсы, так же, как классы исключений, размещаются в тех пакетах Java,
классы
которых
реализуют
данные
интерфейсы
(например,
интерфейсы
MouseListener, MouseMotionListener и MouseInputListener, связанные с
обработкой событий графического интерфейса AWT в Java, размещены в пакете
java.awt.event).
Примеры практического использования интерфейсов будут рассмотрены ниже (при
рассмотрении потоков и обработке событий в компонент AWT).
2.2.3. Создание и использование пакетов в Java
Как указывалось выше, классы и интерфейсы стандартной библиотеки Java
содержатся в пакетах.
Поскольку файл с программой на языке Java может содержать несколько классов, в
спецификации Java для такого файла введен термин единица компиляции (compilation
unit).
В общем случае членами пакетов являются единицы компиляции и файлы с
расширением .class, полученные при компиляции этих единиц компиляции, а также
вложенные подпакеты (subpackages), которые, в свою очередь, могут также содержать
вложенные подпакеты. Таким образом, пакеты и подпакеты образуют древовидную
структуру.
Эта структура пакетов отображается на структуру файловой системы. Все файлы,
образующие пакет, хранятся в одном каталоге файловой системы. Подпакеты собраны в
подкаталоги этого каталога. Так пакет java.lang содержится в каталоге java\lang, а
пакет java.lang.reflect – в каталоге java\lang\reflect.
Чтобы создать пакет, надо в первой строке единицы компиляции записать
объявление пакета:
package идентификатор-пакета;
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 17 Прикладное программирование в ТС
Лекция 3-04
например:
package mypack;
Тем самым создается пакет с именем mypack, и все классы данной единицы
компиляции включаются в пакет mypack. Если необходимо создать пакет mypack из
нескольких единиц компиляции, надо в начале каждой единицы компиляции задавать
объявление пакета mypack.
Если необходимо создать подпакет данного пакета, необходимо задать уточненное
имя:
идентификатор-пакета.идентификатор-подпакета
например:
package mypack.subpack;
Рекомендуется записывать имена пакетов строчными буквами, тогда они не будут
совпадать с именами классов, которые, по соглашению, начинаются с прописной буквы.
Кроме того, фирма Sun советует использовать в качестве имени пакета или подпакета
доменное имя своего сайта, записанное в обратном порядке, например:
com.sun.developer
Если в начале единицы компиляции нет объявления пакета, компилятор создает
для файлов .class данной единицы безымянный пакет (unnamed package), которому
соответствует текущий каталог файловой системы, т.е. все файлы .class программной
единицы помещаются в текущий каталог. Это позволяет выполнить программу, задавая в
качестве интерпретатору java в качестве параметра просто имя класса (без указания пути
к нему).
Если создается пакет, то все файлы .class данного пакета должны находиться в
каталоге, имя которого совпадает с именем данного пакета.
В той программной единице, которая использует классы или классы созданного
пакета, необходимо задавать, как и для классов библиотеки Java, полное имя класса, либо
задать оператор import для включения классов пакета, например:
import mypack.*;
Однако для того, чтобы классы пакета были доступны в использующей их
программной единице, необходимо выполнение одного из следующих условий:
 программа запускается в каталоге, находящимся в иерархии на единицу выше,
чем каталог с файлами .class пакета;
 путь к каталогу пакета добавляется в переменную окружения CLASSPATH с
помощью команды
set CLASSPATH путь-1; путь-2;…
Текущее значение переменной CLASSPATH можно просмотреть, задав команду
set CLASSPATH без параметров.
2.2.4. Работа с потоками в Java
2.2.4.1. Процессы и потоки
Все современные операционные системы (например, Windows или Unix)
функционируют в многозадачном режиме. Это означает, что каждая из решаемых на
компьютере задач выполняется в квазипараллельном или (в случае наличия в компьютере
нескольких процессоров) в параллельном режиме. Кроме того, каждая выполняемая
задача может быть разбита на отдельные процессы, которые также будут выполняться
квазипараллельно или параллельно.
Квазипараллельность означает, что на одном процессоре имитируется работа
нескольких процессоров. Реальные реализации этой имитации довольно сложны, но в
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 18 Прикладное программирование в ТС
Лекция 3-04
упрощенном виде механизм выполнения нескольких процессов на одном компьютере
можно представить следующим образом.
Внутри компьютера имеются часы-таймер. Для примера предположим, что таймер
срабатывает каждую миллисекунду (на практике, период значительно меньше). При этом
происходит прерывание выполняющегося в данный момент процесса. В многозадачных
операционных системах для управления процессами используется специальный модуль –
диспетчер задач (Task Manager) (в разных операционных системах этот модуль называется
по-разному). В период интервала прерывания диспетчер задач просматривает таблицу
процессов. В этой таблице хранятся указатели на все выполняющиеся в данный момент
процессы. Затем определяется наличие процессов, ожидающих выполнения; если таковые
отсутствуют, то управление передается процессу, выполнявшемуся ранее.
Предположим, что в компьютере функционирует только один процесс, хотя такая
ситуация практически не встречается, поскольку диспетчер задач сам является процессом.
В этом случае после прерывания процессор компьютера продолжит выполнение процесса,
как показано на рис. 2.5.
Процесс 1
Процесс 1
Процесс 1
Процесс 1
Время
- интервал прерывания по таймеру
Рис. 2.5. Выполнение одного процесса
Когда в компьютере появляется другой процесс, то он может находиться в одном
из двух состояний: в состоянии готовности или в состоянии ожидания. В первом случае
процесс готов выполняться, как только освободится процессор компьютера, занятый
выполнением других процессов. Во втором случае процесс не готов к выполнению и
ожидает выполнения некоторого условия, например, окончания выполнения другого
процесса или завершения периода «спячки» (sleeping) в течение некоторого промежутка
времени. В случае, если второй процесс готов к выполнению, диспетчер задач поочередно
выполняет на процессоре то первый, то второй процесс, как это показано на рис. 2.6.
Процесс 1
Процесс 2
Процесс 1
Процесс 2
Время
- интервал прерывания по таймеру
Рис. 2.6. Выполнение двух процессов
Однако на практике некоторые процессы являются более важными, чем другие.
Для того, чтобы учесть это, для процессов введено понятие приоритета. Чем более
важным является поток, тем выше у него приоритет. В частности, диспетчер задач,
поскольку он управляет выполнением всех остальных процессов, имеет наивысший
приоритет. Диспетчер задач просматривает очередь готовых к выполнению процессов и
допускает к выполнению на процессоре компьютера в первую очередь более
приоритетные процессы (менее приоритетные процессы запускаются только в том случае,
если все более приоритетные процессы находятся в состоянии ожидания). Предположим,
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 19 Прикладное программирование в ТС
Лекция 3-04
что процесс 1 имеет более высокий приоритет, чем процесс 2. В этом случае выполнение
этих двух процессов будет выглядеть, как показано на рис. 2.7.
В показанной на рисунке ситуации возможны два алгоритма выполнения
процессов:
1. Процесс 2 будет выполняться до тех пор, пока он не завершится или перейдет в
состояние ожидания.
2. Выполнение процесса 2 может быть принудительно прекращено при
определенных условиях.
Процесс 1
Процесс 1
Процесс 2
Процесс 2
Время
- интервал прерывания по таймеру
Процесс 1 перешел в
состояние ожидания
Рис. 2.7. Выполнение процессов с учетом приоритета
Принудительное прекращение процесса называется прерыванием процесса
(process interrupt). Условием прерывания процесса может быть, например, превышение
времени выполнения (кванта времени), выделенного для процесса данного приоритета,
или наличие в очереди процессов более высокоприоритетного процесса, которому
разрешено прекращать выполнение низкоприоритетных процессов. В этом случае
выполнение процессов будет выглядеть, как показано на рис. 2.8.
Процесс 1
Процесс 1
- интервал прерывания по
таймеру
Процесс 1 перешел в
состояние ожидания
Процесс 2
Процесс 1
Время
Процесс
1 прервал
выполнение процесса 2
Рис. 2.8. Выполнение процессов с прерываниями
Часто процессы разбиваются по каким-либо признакам на группы. Например, в
одну группу можно выделить все процессы, относящиеся к определенной задаче. В этом
случае диспетчер задач может управлять всей группой так же, как он управляет
отдельным потоком.
Операционная система осуществляет защиту каждого процесса от других
процессов. Программа, выполняемая в одном из процессов, не может просматривать или
изменять данные, обрабатываемые другой программой, если только обе они не
используют для взаимодействия между собой механизм коллективного доступа,
называемого IPC (InterProcess Communication – межпроцессная связь). Такая высокая
степень защиты оказывается весьма полезной в том случае, когда некоторая программа
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 20 Прикладное программирование в ТС
Лекция 3-04
работает некорректно или «зависает», потому что даже в этом случае она не нарушает
работы всего компьютера.
Однако такая организация работы компьютера имеет свои недостатки, главным из
которых является необходимость больших затрат системных ресурсов на запуск и
поддержание отдельных процессов.
Поэтому разработчики операционных систем
предложили концепцию потоков или нитей (threads), которые стали называть
упрощенными процессами. Подобно обычному процессу, поток является независимой
последовательностью выполняемых команд процессора. Однако, в отличие от процессов,
потоки команд не защищены друг от друга средствами операционной системы. Главное
преимущество потоков заключается в том, что их запуск выполняется очень быстро.
2.2.4.2. Реализация потоков в Java
Язык Java является одним из немногих языков программирования, которые
содержат средства поддержки потоков. В языке Java потоки обычно используются для
того, чтобы апплеты могли выполнять какие-то действия в то время, как Web-браузер
продолжает свою работу, однако потоки можно применить в любой программе при
необходимости параллельного выполнения нескольких задач.
Так, например, при создании коллективом программистов большого и сложного
программного продукта, как правило, отдельные модули программы разрабатываются
параллельно отдельными программистами или группами программистов. В этом случае
процесс разработки каждого модуля программы можно представить как отдельный поток.
Реализация использования потоков в программах на языке Java может выполняться
двумя способами:
 расширением класса Thread;
 реализацией интерфейса Runnable.
При первом способе класс становится поточным, если он создан как расширение
класса Thread, который определен в пакете java.lang, например:
public class GreatRace extends Thread
При этом становятся доступными все методы для работы с потоками.
Обычно, когда необходимо, чтобы данный класс является расширением некоторого
другого класса и в нем необходимо реализовать потоки, предыдущий подход нельзя
использовать, поскольку, как уже указывалось, в языке Java нет множественного
наследования.
Для решения этой проблемы для данного класса нужно реализовать интерфейс
Runnable, например:
public class GreatRace extends Applet implements Runnable
Интерфейс Runnable имеет только один метод
public void run().
На самом деле класс Thread также реализует этот интерфейс, однако стандартная
реализация run() в классе Thread не выполняет никаких операций. Необходимо либо
расширить класс Thread, чтобы включить в него новый метод run(), либо создать
объект Runnnable и передать его конструктору потока. Когда создается класс,
реализующий интерфейс Runnable, этот класс должен переопределить метод run().
Именно этот метод выполняет фактическую работу, возложенную на конкретный поток.
Создать поток можно с помощью одного из следующих конструкторов:
public Thread()
public Thread(String name)
public Thread(Runnable target)
public Thread(Runnable target, String name)
public Thread(ThreadGroup group, String name)
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 21 Прикладное программирование в ТС
Лекция 3-04
public Thread(ThreadGroup group, Runnable target)
public Thread(ThreadGroup group, Runnable target,
String name)
В первом конструкторе создается поток, который использует самого себя в
качестве такого интерфейса Runnable.
В остальных конструкторах используемые параметры имеют следующий смысл:
 name – имя, которое присваивается новому потоку;
 target – определение целевого объекта, который будет использоваться новым
объектом Thread при запуске потоков. Если опустить этот параметр или присвоить ему
значение null, новый объект Thread будет запускать потоки посредством вызова
метода run() текущего объекта Thread. Если при создании нового объекта Thread
указывается целевой объект, то для запуска новых процессов он будет вызывать метод
run() указанного целевого объекта;
 group – предназначен для помещения нового объекта Thread в дерево
объектов данного класса. Если опустить данный параметр или присвоить ему значение
null, новый объект класса Thread станет членом текущей группы потоков
ThreadGroup.
Метод
public String toString()
возвращает строковое представление потока, включая имя потока, приоритет и имя
группы, а методы
public final String getName()
и
public final void setName(String name)
позволяют получить имя потока или установить имя потока.
Запуск потока выполняет метод
public void start() throws IllegalThreadStateException
(исключение бросается, если делается попытка запуска уже запущенного потока).
Следует отметить, что когда запускается программа на языке Java, один поток
начинает выполняться немедленно. Этот поток с именем по умолчанию "main" обычно
называют главным потоком. Все остальные потоки, порождаемые с помощью
конструктора Thread, будут дочерними потоками главного потока. Главный поток
является последним потоком, в котором заканчивается выполнение, т.е. при завершении
этого потока заканчивается выполнение программы.
Для завершения работы потока, а также для их приостановки и последующего
возобновления в JDK 1.0 использовались соответственно методы stop(), suspend() и
resume().
Хотя эти методы и сохранены в следующих версиях JDK, они объявлены
отмененными (deprecated), поскольку использование этих методов может привести к
блокировке или «зависанию» потоков.
Для остановки потока рекомендуется
останавливаемому потоку присвоить значение null, например:
Thread myThread;
…
myThread.start(); // Запуск потока
…
myThread = null; // Остановка или завершение потока
Предположим, что на каком-то этапе работы над программным проектом
необходимы два модуля, над которыми работают две группы программистов. Этап может
начаться, только если обе группы закончили работу над своими модулями, т.е. одной из
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 22 Прикладное программирование в ТС
Лекция 3-04
групп программистов придется дожидаться окончания работы над модулем другой
группы. Для такого согласования действий используется метод
public final void join() throws InterruptedException,
например:
double finalResult;
// Место, куда вычислительный
// поток запишет результат
Thread group1Thread = new Thread ();
// Поток, моделирующий разработку модуля первой группой
group1Thread.start();
...
// Производятся другие действия
...
// Ожидание окончания работы над модулем первой группы
group1Thread.join();
Методу join можно также передать значение тайм-аута, используя его варианты
public final syncronized void join(long millis)
throws InterruptedException
public final syncronized void join(long millis, int nanos)
throws InterruptedException.
Эти методы ожидают в течение millis миллисекунд либо millis миллисекунд
плюс nanos наносекунд.
Если требуется, чтобы перед продолжением работы поток ждал определенное
время, можно использовать метод
public static void sleep(long millis)
throws InterruptedException
или
public static void sleep(long millis, int nanos)
throws InterruptedException,
где параметры millis и nanos имеют тот же смысл, что и для метода join(). Метод
sleep() очень часто используется в циклах, управляющих анимацией.
Если в программе есть поток, который захватывает процессор, производя большое
количество вычислений, может появиться необходимость заставлять его время от времени
«отпускать» процессор, давая возможность выполняться другим потокам. Это достигается
с помощью метода
public static void yield().
Метод
public void destroy()
уничтожает поток без всякой очистки относящихся к нему данных, а метод
public final boolean isAlive()
позволяет определить, запущен ли поток и еще «жив».
Потоки в Java могут прерывать друг друга. Механизм прерываний реализуется с
помощью следующих методов:
 public void interrupt() – прерывает данный поток;
 public static boolean interrupted() – проверяет, был ли прерван
данный поток (этот метод очищает признак прерывания для потока, т.е. при повторном
вызове метода для этого же прерванного потока он вернет значение false);
 public boolean isInterrupted() – аналогичен предыдущему методу, но
не очищает признака прерывания для потока.
В классе Thread есть ряд статических методов для изучения текущего потока и
других потоков из той же группы. Метод
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 23 Прикладное программирование в ТС
Лекция 3-04
public static Thread currentThread()
возвращает объект, соответствующий выполняемому в момент вызова потоку, метод
public static void dumpStack()
выводит трассировку стека для текущего потока.
Метод
public final void checkAccess()
проверяет, имеет ли право текущий поток модифицировать данный поток.
Обычно программа на Java работает до завершения всех входящих в нее потоков.
Иногда, однако, встречаются потоки, работающие в фоновом режиме, выполняя
вспомогательные действия, которые никогда не заканчиваются. Можно пометить такой
поток как поток-демон (daemon thread), что говорит JVM о том, что этот поток не надо
принимать в расчет при определении, все ли потоки данной программы завершились.
Другими словами, приложение на Java выполняется до тех пор, пока не завершится
последний поток, не являющийся демоном. Потоки, не помеченные как демоны, называются пользовательскими потоками (user threads).
Чтобы поток считался демоном, надо воспользоваться методом
public final void setDaemon(boolean on)
throws IllegalThreadStateException.
Если параметр on равен true, поток получает статус демона, если false – статус
пользовательского потока. Статус потока может быть изменен в процессе его выполнения.
Метод
public final boolean isDaemon()
возвращает true, если поток является демоном, и false, если это пользовательский
поток.
Метод
public static int enumerate(Thread[] threadArray)
класса Thread заполняет массив объектами Thread, представляющими потоки в группе,
к которой относится текущий поток. Поскольку перед таким вызовом необходимо создать
массив threadArray, надо знать, сколько элементов будет получено. Метод
public static int activeCount()
класса Thread сообщает, сколько активных потоков в группе, к которой относится
данный поток.
2.2.4.3. Приоритеты и группы потоков
Распределения процессорного времени между потоками в Java выполняется по
следующим правилам: когда поток блокируется, то есть приостановлен, переходит в
состояние ожидания или должен дождаться какого-то события, Java выбирает другой
поток из тех, которые готовы к выполнению. Выбирается поток, имеющий наибольший
приоритет. Если таких потоков несколько, выбирается любой из них. Приоритет потока
можно установить методом
public final void setPriority(int newPriority)
throws IllegalArgumentException.
Приоритет потока должен быть числом в диапазоне от Thread.MIN_PRIORITY
до Thread.MAX_PRIORITY. Любое значение вне этих пределов вызывает исключение
IllegalArgumentException. По умолчанию потоку приписывается приоритет
Thread.NORM_PRIORITY. Значение приоритета потока можно выяснить с помощью
метода
public final int getPriority().
Класс ThreadGroup реализует стратегию обеспечения безопасности, которая
позволяет влиять друг на друга только потокам из одной группы. Например, поток может
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 24 Прикладное программирование в ТС
Лекция 3-04
изменить приоритет другого потока из той же группы или перевести его в состояние
ожидания. Если бы не было разбиения потоков на группы, один поток мог бы вызвать
хаос в среде Java, переведя в состояние ожидания все остальные потоки, или, что еще
хуже, завершив их. Группы потоков организованы в иерархическую структуру, где у
каждой группы есть родительская группа. Потоки могут воздействовать на потоки из
своей группы и из дочерних групп.
Группу потоков можно создать, просто задав ее имя, с помощью конструктора
public ThreadGroup(String groupName).
Можно также создать группу потоков, дочернюю по отношению к существующей,
используя конструктор
public ThreadGroup (ThreadGroup existingGroup,
String groupName) throws NullPointerException.
Метод
public String toString()
возвращает строковое представление данной группы, а методы
public final String getName()
public final void setName(String name)
позволяют получить имя группы или установить имя группы.
Другие методы класса ThreadGroup являются аналогами соответствующих
методов класса Thread, но применяются ко всей группе:
 public int activeCount() – возвращает число активных потоков в
данной группе;
 public int activeGroupCount() – возвращает число активных групп в
данной группе;
 public final void checkAccess() – проверяет, может ли выполняемый
в настоящее время поток модифицировать данную группу;
 public boolean isDestroyed()и public final void destroy()
throws IllegalThreadStateException, SecurityException – проверяет
«жива» ли данная группа или уничтожает все потоки данной группы и ее подгрупп (все
потоки в группе должны быть предварительно остановлены);
 public final void interrupt() – прерывает все потоки в данной
группе;
 public final boolean isDaemon() и public final void
setDaemon(boolean daemon) – соответственно проверяет и устанавливает признак
потока-демона для всей группы.
Можно ограничить приоритет потоков из группы с помощью вызова метода
public final synchronized void
setMaxPriority (int priority),
а определить максимальный приоритет потока в группе можно с помощью метода
public final int getMaxPriority().
Родительская группа потоков доступна посредством вызова метода
public final ThreadGroup getParent(),
а с помощью метода
public final boolean parentOf(ThreadGroup g)
можно проверить является ли группа g родительской для данной группы.
Различные варианты метода enumerate класса ThreadGroup позволяют
выяснить, какие потоки и группы потоков входят в состав данной группы:
public int enumerate(Thread[] threadList)
public int enumerate(Thread[] threadList, boolean recurse)
public int enumerate(ThreadGroup[] groupList)
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 25 Прикладное программирование в ТС
Лекция 3-04
public int enumerate(ThreadGroup[] groupList,
boolean recurse)
Параметр recurse указывает, что надо рекурсивно перебрать все дочерние
группы, охватив потомков всех «поколений».
Оценить количество активных потоков и групп можно при помощи методов
public int activeCount()
и
public int activeGroupCount().
2.2.4.4. Синхронизация потоков
Как уже указывалось, основное отличие потоков от процессов состоит в том, что
потоки не защищены друг от друга средствами операционной системы. Поэтому любой из
потоков может получить доступ и даже внести изменения в данные, которые другой поток
считает своими. Решение этой проблемы состоит в синхронизации потоков.
Синхронизация потоков состоит в гарантии в каждый момент времени
предоставления доступа к данным только к одному потоку. Для этого используется
следующий оператор:
synchronized (выражение)
оператор
Взятое в скобки выражение должно указывать на блокируемые данные – обычно
оно является ссылкой на объект. Чаще всего при блокировке объекта необходимо
выполнить сразу несколько операторов, так что оператор, как правило, представляет
собой блок.
Если оказывается, что критический участок распространяется на весь метод, а
разделяемым ресурсом является весь объект в целом, то можно просто указать
модификатор synchronized в объявлении метода, например:
synchronized void myMethod()
{
…
}
Более гибкий и эффективный способ координации выполнения потоков
обеспечивают методы wait() и notify() класса Object.
Метод wait() переводит поток в состояние ожидания выполнения определенного
условия и вызывается с помощью одной из следующих форм:
 public final void wait() throws InterruptedException,
IllegalMonitorStateException – приостановление выполнения текущего потока
до получения извещения;
 public
final
void
wait(long
timeout)
throws
InterruptedException,
IllegalMonitorStateException,
IllegalArgumentException – приостановление выполнения текущего потока до
получения извещения или до истечения заданного интервала времени timeout (в
миллисекундах);
 public final void wait(long timeout, int nanos) throws
InterruptedException,
IllegalMonitorStateException,
IllegalArgumentException – приостановление выполнения текущего потока до
получения извещения или до истечения заданного интервала времени timeout (в
миллисекундах) и, дополнительно, в наносекундах (значение в диапазоне 0-999999).
Метод
public final void notify()
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 26 Прикладное программирование в ТС
Лекция 3-04
переводит в активное состояние один из потоков, установленных в состояние ожидания с
помощью метода wait(). Критерий выбора потока является произвольным и зависит от
конкретной операционной системы и реализации виртуальной машины Java. Если
необходимо перевести все ожидающие потоки в активное состояние, можно
воспользоваться методом public final void notifyAll().
Методы
могут вызываться только потоком, который является владельцем
монитора данного объекта. Если до вызова методов wait() или notify() не
выполнить
захват
монитора
данного
объекта,
генерируется
исключение
IllegalMonitorStateException. Поток становится владельцем данного объекта
одним из трех способов:
 вызовом метода, объявленного как synchronized, который осуществляет
доступ к требуемому экземпляру объекта класса Object;
 выполнением тела оператора synchronized, предназначенного для
синхронизации доступ к требуемому экземпляру объекта класса Object;
 выполнением, для объектов типа Class, синхронизированного статического
метода этого класса.
Таким образом, как это видно из приведенного выше обзора, в Java имеются
развитые средства работы с потоками и группами потоков. Это позволяет разрабатывать
на языке Java довольно сложные, в том числе и системные, программы.
2.2.5. Вложенные классы
Существует возможность определения одного класса внутри другого. Такие классы
известны как вложенные (nested) классы, например:
class EnclosingClass {
. . .
class ANestedClass {
. . .
}
}
Область видимости вложенного класса ограничивается областью видимости
включающего класса. Таким образом, если класс B определен в классе A, то B известен
внутри A, но не вне его. Вложенный класс имеет доступ к членам класса (включая члены с
модификатором private), в который он вложен. Однако включающий класс не имеет
доступа к членам вложенного класса.
Существует два типа вложенных классов: статические и нестатические.
Статический вложенный класс (static nested class) – это класс, который имеет модификатор
static. Согласно своей характеристике он должен обращаться к членам своего
включающего класса через объект. То есть он не может обратиться к членам
включающего класса напрямую. Из-за этого ограничения статические вложенные классы
используются редко.
Наиболее важный тип вложенного класса – внутренний (inner) класс. Внутренний
класс – это нестатический вложенный класс, имеющий доступ ко всем переменным и
методам своего внешнего класса и возможность обращаться к ним напрямую, таким же
способом, как это делают другие нестатические члены внешнего класса. Итак, внутренний
класс находится полностью в пределах видимости своего включающего класса.
Пример статического и нестатического вложенных классов:
class EnclosingClass {
. . .
static class AStaticNestedClass {
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 27 Прикладное программирование в ТС
Лекция 3-04
. . .
}
class InnerClass {
. . .
}
}
Хотя внутренний класс имеет доступ ко всем членам своего включающего класса,
члены внутреннего класса известны только в пределах внутреннего класса и не могут
использоваться внешним классом.
Вложенные классы, как и все локальные переменные, известны только в блоке, в
котором они определены. Они могут быть безымянными (anonymous classes). В
безымянном классе не может быть конструктора, поскольку имя конструктора должно
совпадать с именем класса. Вместо конструктора в безымянном классе используется блок
инициализации экземпляра для суперкласса безымянного класса, например:
void m ()
{
new Object()
{
// Создается объект безымянного класса,
// указывается конструктор
// его суперкласса
private int e = pr;
void g()
{
System.out.println ("From g()");
}
}
g(); // Выполнение метода созданного объекта
}
Как и любые другие классы, вложенные классы могут быть объявлены с
модификаторами
abstract или final. Для них также можно использовать
модификаторы доступа private, public и protected.
Существует возможность определять внутренние классы в пределах любого блока.
Например, можно определить вложенный класс в блоке, установленном в методе или даже
в теле цикла for, например:
class Outer {
int outer_x = 100;
void test() {
for(int i=0; i<10; i++) {
class Inner {
void display() {
System.out.println("display: outer_x = " +
outer_x);
}
}
Inner inner = new Inner();
inner.display();
}
}
}
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 28 Прикладное программирование в ТС
Лекция 3-04
Примеры безымянных классов будут приведены при рассмотрении обработки
событий в языке Java.
Для доступа к членам вложенного класса и членам их суперклассы используются
следующие уточнения при формировании для имен переменных и методов:
 язык Java позволяет использовать одни и те же имена в разных областях
видимости, поэтому необходимо уточнять константу this именем класса, например,
Nested.this, В.this;
 нельзя создать экземпляр вложенного класса, не создав предварительно
экземпляр внешнего класса, поэтому операция new уточняется именем экземпляра
внешнего класса, например, nest.new, theA.new, theB.new, причем при определении
экземпляра указывается полное имя вложенного класса, но в операции new записывается
просто конструктор класса;
 для обращения из подкласса к методу суперкласса константа super уточняется
именем соответствующего суперкласса, например, super.outerMethod().
Таким образом, в Java можно использовать две различные иерархии классов. Одну
иерархию образует наследование классов, другую – вложенность классов.
Чтобы определить, какую иерархию классов следует использовать в программе,
необходимо выяснить, в каком отношении находятся классы A и B: в отношении «класс B
является экземпляром класса A» ("is-a") или в отношении «класс B – часть класса A»
("has-a").
Отношение "is-a" — это отношение "обобщение-детализация", отношение большей
или меньшей абстракции, и ему соответствует наследование классов. Например,
рассмотренный выше класс AutoCar является детализацией класса Car, поэтому в
данном случае используется наследование.
Отношение "has-a" — это отношение "целое-часть", ему соответствует вложение.
Например, если мы хотим моделировать функционирование автомобиля, то класс Engine
(двигатель) является частью автомобиля и в этом случае этот класс будет являться
вложенным для класса Car.
2.2.6. Регулярные выражения в Java
2.2.6.1. Основные сведения о регулярных выражениях
Функции работы с подстроками позволяют выполнить операции поиска подстрок в
строке и замены подстрок в строке. Однако при работе с данными часто приходится
выполнять операции поиска и замены по довольно сложным алгоритмам, например, найти
первое вхождение цифры в строке. Хотя такие операции можно выполнить, используя
функции работы со строками, условные операторы и операторы цикла, в языке Java, как и
в других языках программирования, существует более удобный способ – использование
регулярных выражений.
Регулярные выражения используются для решения следующих задач:
 проверки данных на наличие некоторой последовательности данных, заданных с
помощью определенного образца, называемого шаблоном (pattern);
 замены или удаления данных;
 извлечения некоторой последовательности из данных.
Синтаксис регулярных выражений в Java в основном такой же, как и в других
языках программирования.
2.2.6.1.1. Синтаксис регулярного выражения
Регулярное выражение в языке Java является объектом класса String, т.е.
является строкой – последовательностью символов.
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 29 Прикладное программирование в ТС
Лекция 3-04
Символы в строке могут быть следующих типов:
 алфавитно-цифровые символы, включая буквы кириллицы;
 символ '\\' – обратная косая черта (обратный слеш);
 символ '\0num' – восьмеричное число, где num – одна, две или три
восьмеричные цифры;
 символ '\xhh' – код символа ASCII, где hh – две шестнадцатеричные цифры;
 символ '\uhhhh' – код символа Unicode, где hhhh – четыре шестнадцатеричные
цифры;
 символ табуляции ('\t' или '\u0009');
 символ новой строки ('\n' или '\u000A');
 символ возврата каретки ('\r' или '\u000D');
 символ перехода к новой странице ('\f' или '\u000C');
 символ звукового сигнала ('\a' или '\u0007');
 символ Escape (Esc) ('\u001B');
 символ '\cx' – соответствует управляющему символу x (например, \cM
соответствует символу Ctrl+M или символу возврата каретки).
Некоторые символы в регулярных выражениях, называемые метасимволами,
имеют особое значения. Метасимволами являются следующие символы:
^ $ ( ) \ | [ { ? . + *
Именно использование метасимволов обеспечивает всю мощь и гибкость
регулярных выражений.
Если в регулярном выражении необходимо рассматривать метасимвол как
обычный символ, перед ним надо поставить символ '\\', например, "\\(".
2.2.6.1.2. Операция альтернации
В регулярных выражениях можно объединять несколько шаблонов, так чтобы
найденная строка соответствовала хотя бы одному из них. Для решения подобной
проблемы служит операция альтернации, которая в регулярных выражениях задается
символом "|", например шаблон "Internet|Интернет" означает поиск в исходной
строке либо строки "Internet", либо строки "Интернет".
2.2.6.1.3. Одиночный метасимвол
Метасимвол точка "." внутри регулярного выражения точка соответствует любому
одиночному символу, кроме символа перевода строки.
Пример использования одиночного метасимвола в регулярных выражениях
"с.р"
В этом шаблоне точка означает любой символ. Этому шаблону соответствуют
слова сор, сыр, сарай, и кассир. Точка заменяет только один символ. Поэтому слова
срок, стирка и сатира не соответствуют указанному шаблону, поскольку в первом
слове между символами с и р нет никакого символа, а во втором и третьем словах между
этими символами расположены более одного символа.
2.2.6.1.4. Квантификаторы
Квантификаторы – это метасимволы, используемые для указания
количественных отношений между символами в шаблоне и в искомой строке.
Квантификатор может быть поставлен после одиночного символа или после группы
символов.
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 30 Прикладное программирование в ТС
Лекция 3-04
Простейшим квантификатором является метасимвол "+". Он означает, что идущий
перед ним символ соответствует нескольким идущим подряд таким символам в строке
поиска. Количество символов может быть любым (максимально большим в рамках
соответствия шаблону), но должен присутствовать хотя бы один символ.
Пример использования метасимвола "+" в регулярных выражениях
"ком+а"
Этому шаблону будут соответствовать слова команда и коммандос, а слово
коалиция соответствовать не будет.
Действие метасимвола "*" похоже на действие метасимвола "+". Метасимвол "*"
указывает, что идущий перед ним символ встречается нуль или более раз.
Пример использования метасимвола "*" в регулярных выражениях
"точе*к"
Этому шаблону соответствуют слова точек и точка, а слово точечный
соответствовать не будет.
Метасимвол "?" указывает, что предшествующий ему символ должен встречаться
либо один раз, либо не встречаться вообще.
Пример использования метасимвола "?" в регулярных выражениях
"ком?а"
Этому шаблону будут соответствовать слова команда и коалиция, а слово
коммандос соответствовать не будет.
Если необходимо указать точно количество повторений символа, можно
воспользоваться конструкцией
{n,m}
Здесь n – минимально допустимое количество повторений предшествующего
символа, m – максимально допустимое количество повторений. Один из параметров n или
m можно опустить.
Примеры использования шаблона с указанием точного числа повторений
символов в регулярных выражениях
1. "10{3,5}1" – 0 встречается как минимум 3 раза, но не более 5 раз. Числа
10001, 100001 и 1000001 удовлетворяют этому условию, а числа 1001 и 10000001 –
нет.
2. "10{3,}1" – 0 встречается 3 или более раз. Двоичные числа 10001,
100001, 1000001, 10000001 и т.д. удовлетворяют этому условию, а числа 101 и 1001
– нет.
3. "10{0,3}1" – 0 встречается не более 3 раз, но может вообще не встретиться.
Числа 11, 101, 1001 и 10001 удовлетворяют этому условию, а числа 100001, 1000001
и т.д. – нет.
4. "10{3}1" – 0 встречается ровно 3 раза. Двоичное число 10001
удовлетворяет этому условию, а числа 101, 1001, 100001 и т.д. – нет.
Фактически квантификаторы "+", "*" и "?" являются частными случаями
конструкции {n,m}: соответственно, {1,}, {0,} и {0,1}.
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 31 Прикладное программирование в ТС
Лекция 3-04
В регулярных выражениях часто используют сочетание метасимволов ".*". Ему
соответствуют любые символы. По правилам обработки регулярных выражений
находится самая длинная строка, все еще удовлетворяющая шаблону поиска.
Если необходимо ограничить поиск, следует после квантификатора (в том числе и
символа "?") указать символ "?".
Пример использования метасимволов ".*" в регулярных выражениях
Если задать для исходной строки
"Первый может стать как последний и последний может стать
как первый."
шаблон
"первый.*последний"
(без учета регистра букв), то результатом поиска будет строка
"Первый может стать как последний и последний"
Если заменить шаблон на
"первый.*?последний"
то строка – результат поиска будет иметь следующий вид
"Первый может стать как последний"
2.2.6.1.5. Классы символов
Для поиска в регулярных выражениях можно задавать также классы символов,
заключенные в квадратные скобки. Во время поиска все символы в классе
рассматриваются как один символ. Внутри класса можно задавать диапазон символов
(когда такой диапазон имеет смысл), помещая дефис между границами диапазона. Внутри
символьных классов большинство метасимволов теряют свои значения и становятся
обыкновенными символами.
Примеры задания классов символов в регулярных выражениях
1. "[абвг]" или "[а-г]" – любой из символов "а", "б", "в" или "г".
Строка "огонь" удовлетворяет шаблону, поскольку в ней есть символ "г", а строка
"окно" – не удовлетворяет, поскольку в ней нет ни одного из символов, указанных в
шаблоне.
2. "Глава [0-9]+" – символы "Глава", за которыми (через пробел) следует
одна или несколько цифр. Строки "Глава 5" и "Глава 18" удовлетворяет шаблону,
поскольку в них после строки "Глава" и пробела следуют соответственно одна и две
цифры, а строка "Глава десять" – не удовлетворяет, поскольку в ней после слова
"Глава" и пробела нет ни одной цифры.
3. "[А-Я][а-я]+" – заглавная буква, за которой следует одна или несколько
строчных букв. Строка "Иванов" удовлетворяет шаблону, поскольку она начинается с
заглавной буквы, за которой следуют строчные буквы, а строка "ивановский" – не
удовлетворяет, поскольку она начинается со строчной буквы.
4. "[.?!]" – один из символов окончания предложения (обратите внимание, что
символы "." и "?" здесь используются как обычные символы, а не как метасимволы).
Строки "Как дела?", "Замечательно!" и "Хорошо." удовлетворяет шаблону,
поскольку они содержат символы окончания предложения, а строка "Плохо" – не
удовлетворяет, поскольку она не содержит ни одного символа окончания предложения.
Если первым символом класса является знак вставки "^", то значение выражения
инвертируется. Другими словами, такому классу соответствует любой символ, не
входящий в класс.
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 32 Прикладное программирование в ТС
Лекция 3-04
Примеры задания инвертированного класса в регулярных выражениях
"[^А-Я ]"
Этому шаблону удовлетворяет строка, содержащая хотя бы один символ, не
являющийся заглавной буквой или пробелом. Строки "Щит и меч", "МВД и МИД"
удовлетворяет шаблону, поскольку они содержат строчные буквы, а строка "ЩИТ И
МЕЧ" – не удовлетворяет, поскольку она содержит только заглавные буквы и пробелы.
Так как в классах символы "]", "^" и "-" имеют специальное значение, для их
использования в классе существуют определенные правила:
 литерал "^" не должен быть первым символом класса;
 перед литералом "]" должен стоять символ обратной косой черты;
 для помещения в класс символа "-" достаточно либо поставить его на первую
позицию, либо поместить перед ним символ обратной косой черты.
Примеры задания шаблонов с учетом правил для символов "]", "^" и "-" в
регулярных выражениях
1. "[^&*%^]" – строка содержит хотя бы один символ, не являющийся одним из
символов "&", "*", "%" или "^".
2. "[[\]]" – строка содержит символ "[" или символ "]".
3. "[;:\-]]" – строка содержит символ ";" , символ ":" или символ "-".
2.2.6.1.6. Специальные символы
Наиболее распространенные классы символов можно задать с помощью
следующих специальных символов:
 \d – соответствует любому цифровому символу (эквивалентно [0-9]);
 \D – соответствует любому нецифровому символу (эквивалентно [^0-9]);
 \w – соответствует любой латинской букве или цифре (эквивалентно [A-Zaz0-9]);
 \W – соответствует любому небуквенному (латинскому) и нецифровому символу
(эквивалентно [^A-Za-z0-9]);
 \s – соответствует любому пробельному символу (эквивалентно
[\f\n\r\t\v]);
 \S – соответствует любому непробельному символу (эквивалентно
[^\f\n\r\t\v]).
Следует отметить, что специальные символы \w и \W нельзя использовать для
букв кириллицы, а также букв западноевропейских алфавитов, отличных от латинских
букв. В этом случае необходимо напрямую задавать диапазон символов, как это делается
для классов символов.
Пример использования классов символов в регулярных выражениях
Шаблон для номера мобильного телефона имеет следующий вид:
"\\d{3}-\\d{3}-\\d\\d-\\d\\d"
Этому шаблону соответствует телефонный номер 067-745-12-18 и не
соответствует номер 055-867-1567 (нет тире перед предпоследней цифрой номера).
2.2.6.1.7. Анкеры
С помощью анкеров можно указать, в каком месте строки должно быть найдено
соответствие с шаблоном.
В Java определены следующие анкеры:
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 33 Прикладное программирование в ТС
Лекция 3-04
 ^ – соответствует позиции в начале строки;
 $ – соответствует позиции в конце строки;
 \b – соответствует границе слова, т.е. границе между словом и пробельным
символом;
 \B – соответствует не границе слова.
К сожалению, анкеры \b и \B действуют только для строк, состоящих из
латинских букв.
Пример использования анкеров в регулярных выражениях
Шаблон
"^Глава \\d{1,2}\..*"
Ищет в исходной строке следующие соответствия: строка "Глава" в начале
строки, затем пробел, затем одна или две цифры, затем точка, затем любое содержимое до
конца строки.
2.2.6.1.8. Группировка элементов и обратные ссылки
Операция группировки элементов, т.е. заключение группы элементов в круглые
скобки, позволяет рассматривать данную группу элементов как один элемент.
Предположим, что необходимо найти в строке одно из следующих слов:
белый, красный, зеленый, желтый или черный
Эту операцию можно выполнить, задав в качестве шаблона выражение
"белый|красный|зеленый|желтый|черный",
однако, с учетом того, что все приведенные слова имеют одинаковое окончание "ый",
можно данный шаблон записать короче, используя группировку символов:
"(бел|красн|зелен|желт|черн)ый"
Допускается вложение скобок, а, следовательно, и вложение групп, поэтому
предыдущий пример можно записать и таким образом:
"(бел|желт|(крас|зеле|чер)н)ый".
Если в регулярных выражениях используются скобки, части искомой строки,
соответствующие фрагментам в скобках, запоминаются в специальных переменных $1
(первый фрагмент в скобках), $2 (второй фрагмент в скобках), $3 (третий фрагмент в
скобках) и т.д. Такая операция называется захватом (capture) переменной.
Для вложенных скобок переменные $1-$9 формируются в порядке появления
левой (открывающей) скобки выражения. При использовании группировки свойство
lastParen (сокращение $+) объекта RegExp возвращает последнюю найденную группу
символов.
Следует отметить, что переменные модифицируются при каждом успешном
поиске, независимо от использования в регулярном выражении скобок. Кроме того,
значения этих переменных устанавливаются тогда и только тогда, когда строка полностью
соответствует шаблону. В противном случае значения этих переменных равны пустой
строке.
Пример использования операции группировки в регулярных выражениях
Скобки в шаблоне мобильного телефона
"((\\d{3})-(\\d{3}-\\d\\d-\\d\\d))"
позволяют выделить весь телефонный номер ($1) и его отдельные части – код оператора
($2) и собственно номер телефона ($3).
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 34 Прикладное программирование в ТС
Лекция 3-04
Переменные $1-$9 можно записывать и в выражении шаблона в форме \i, где i –
номер переменной. Переменную называет обратной ссылкой.
Пример использования обратной ссылки в регулярных выражениях
Изменим шаблон для номера мобильного телефона следующим образом:
"((\\d{3})-(\\d{3}-(\\d\\d)-\\4))"
Такая запись означает, что последняя пара цифр должна совпадать с предпоследней
парой цифр (значением переменой $4). В этом случае телефон 094-996-18-18 будет
удовлетворять шаблону, а телефон 094-996-18-19 – не будет.
Переменные $1-$9 не всегда необходимо использовать как результат поиска
соответствия шаблону. В случае, когда необходимо сгруппировать какие-либо символы
шаблона, но не выполнять для них операции по определению соответствующих
переменных (не выполнять для них операцию захвата), используется следующая форма
группирования:
(?:символы)
Пример использования операции группирования без захвата в регулярных
выражениях
Шаблон
"сет(?:ь|и|ью|ей|ям|ями|ях)"
будет выполнять в строке поиск слова "сеть" во всех падежах единственного и
множественного числа, но при этом переменная $1 не создается.
Другим видом группировки является группировка с «заглядыванием вперед».
Группирование в этом случае записывается в следующем виде:
шаблон(?=символы)
В этом случае при поиске соответствия шаблону учитываются символы, заданные в
скобках и идущие после шаблона, но в результат поиска эти символы не входят.
Следующий поиск начинается с позиции, с которой начинаются символы в скобках, т.е.
результат «заглядывания вперед» не учитывается.
Вторая форма группировки с «заглядыванием вперед» записывается в следующем
виде:
шаблон(?!символы)
В отличие от первой формы символы в скобках не должны содержаться в
соответствии шаблону. В результат поиска эти символы не входят и результат
«заглядывания вперед» также не учитывается.
Примеры использования операции группирования «с заглядыванием вперед»
в регулярных выражениях
1. Шаблон
"компьютер(?=\\s|[.,;:?!)()])"
(без учета регистра букв) для исходной строки
"Персональный компьютер. Компьютер - это устройство для
обработки данных. Компьютерное оборудование выпускается многими
фирмами. Суперкомпьютер - это мощный компьютер, содержащий
десятки и сотни процессоров."
будет соответствовать шаблону только в том случае, если справа от слова "компьютер"
находится граница слова, т.е. после этого слова должен следовать либо пробельный
символ, либо один из знаков препинания. В найденные соответствия пробельные символы
и знаки препинания не входят.
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 35 Прикладное программирование в ТС
Лекция 3-04
В результате поиска в строке будут найдены следующие соответствия:
"Персональный компьютер. Компьютер - это устройство для
обработки данных. Компьютерное оборудование выпускается многими
фирмами. Суперкомпьютер - это мощный компьютер, содержащий
десятки и сотни процессоров."
Символы "Компьютер" в слове "Компьютерное" не соответствуют шаблону,
поскольку после символа "р" следует буква, а не пробельный символ или знак
препинания.
2. Если в предыдущем примере изменить шаблон следующим образом:
"компьютер(?!\s|[.,;:?!)()])"
то соответствовать
шаблону будут только символы "Компьютер" в слове
"Компьютерное", поскольку символ "н", который следует после символа "р" не
совпадает ни с одним из символов, указанных в группировке.
Группировка «заглядывание назад» записывается в следующем виде:
(?<=символы) шаблон
В этом случае при поиске соответствия шаблону учитываются символы, заданные в
скобках и идущие перед шаблоном, но в результат поиска эти символы не входят.
Следующий поиск начинается с первой позиции после шаблона.
Вторая форма группировки с «заглядыванием назад» записывается в следующем
виде:
(?<!символы) шаблон
В отличие от первой формы символы в скобках не должны содержаться в
соответствии шаблону. В результат поиска эти символы не входят и результат
«заглядывания назад» также не учитывается.
Примеры использования операции группирования «с заглядыванием назад» в
регулярных выражениях
1. Для исходной строки, заданной в предыдущем примере зададим следующий
шаблон:
"(?iu)(?<=\\s|[.,;:?!)()])компьютер(?=\\s|[.,;:?!)()])"
В результате поиска в строке будут найдены следующие соответствия:
"Персональный компьютер. Компьютер - это устройство для
обработки данных. Компьютерное оборудование выпускается многими
фирмами. Суперкомпьютер - это мощный компьютер, содержащий
десятки и сотни процессоров."
Слово
"Суперкомпьютер" не соответствуют шаблону, поскольку перед
символом "к" следует буква, а не пробельный символ или знак препинания.
2. Если изменить шаблон на
"(?iu)(?<!\\s|[.,;:?!)()])компьютер(?=\\s|[.,;:?!)()])"
то только слово "Суперкомпьютер" будет соответствовать шаблону, поскольку символ
перед символом "к" – не пробельный символ и не знак препинания.
Для работы с регулярными выражениями в Java используются классы Pattern,
Matcher и PatternSyntaxException пакета java.util.regex.
2.2.6.2. Класс Pattern
2.2.6.2.1. Создание объекта класса Pattern
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 36 Прикладное программирование в ТС
Лекция 3-04
Объект класса Pattern является откомпилированным представлением шаблона
регулярного выражения и создается не с помощью ключевого слова new, а с помощью
статических методов compile()класса Pattern.
Метод
public static Pattern compile(String шаблон)
возвращает объект класса Pattern для заданного в параметре шаблона.
Метод
public static Pattern compile(String шаблон, int флажки)
возвращает объект класса Pattern для заданного в параметре шаблона с заданными
флажками.
Флажки представлены в Java как статические поля типа int класса Pattern:
 public static final int CASE_INSENSITIVE (2) – включает поиск
соответствия без учета верхнего или нижнего регистра, т.е. строки "abc", "Abc" и "ABC"
будут считаться соответствующими регулярному выражению "abc" (при отключенном
флажке шаблону будет соответствовать только первая строка);
 public static final int UNICODE_CASE (64) – если этот флажок
включен вместе с флажком CASE_INSENSITIVE, то верхний и нижний регистры букв в
коде Unicode не учитываются при поиске соответствия, т.е. строки "строка", "Строка" и
"СТРОКА" будут считаться соответствующими регулярному выражению "строка" (при
отключенном флажке UNICODE_CASE и включенном флажке CASE_INSENSITIVE
шаблону будет соответствовать только строки, содержащие латинские буквы);
 public static final int UNIX_LINES (1) – при включении этого
флажка только символ "\n" учитывается как символ окончания строки, в которой
выполняется поиск соответствия;
 public static final int MULTILINE (8) – если внутри строки, в
которой выполняется поиск соответствия, есть символы "\n", то считается что строка
состоит из нескольких строк (если флажок выключен, то считается, что поиск
соответствия производится в одной строке, независимо от наличия символов "\n");
 public static final int LITERAL (16) – все символы шаблона,
включая метасимволы, рассматриваются как обычные символы (если флажок выключен,
метасимволы в строке обрабатываются при компилировании);
 public static final int DOTALL (32) – если в шаблоне есть
метасимвол ".", то ему будет соответствовать любой символ, включая символ "\n" (если
флажок выключен, метасимвол "." будет соответствовать любому символу, исключая
символ "\n");
 public static final int COMMENTS (4) – в строке шаблона, допустимы
пробелы и комментарии, начинающиеся с символа "#" до конца строки (при компиляции
шаблона пробелы и комментарии будут проигнорированы);
 public static final int CANON_EQ (128) – при поиске соответствия
будет учитываться соответствие между кодом символа и сами символом, т.е. при
включенном флажке латинская буква "a" будет соответствовать коду Unicode этой буквы
"\u00E5" в шаблоне.
Если необходимо задать одновременно несколько флажков, то они должны быть
разделены знаком операции ИЛИ – "|".
Примеры задания шаблона
1. Pattern pattern1 = Pattern.compile("abc");
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 37 Прикладное программирование в ТС
Лекция 3-04
Задание шаблона – строки "abc".
2. Pattern pattern2 = Pattern.compile("string",
Pattern.CASE_INSENSITIVE);
Задание шаблона – строки "string" с поиском соответствия без учета регистра.
3. Pattern pattern3 = Pattern.compile("строка",
Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
Задание шаблона – строки "строка" с поиском соответствия без учета регистра, в
том числе и для букв кириллицы.
Флажки можно включать непосредственно в шаблоне, используя следующую
синтаксическую форму:
(?строка-символов)
где символы в строке-символов могут иметь одно из следующих значений
i – для флажка CASE_INSENSITIVE;
d – для флажка UNIX_LINES;
m – для флажка MULTILINE;
s – для флажка DOTALL;
u – для флажка UNICODE_CASE;
x – для флажка COMMENTS.
Пример задания флажков в регулярном выражении
"(?ium)компьютер"
Для шаблона "компьютер" включены флажки
UNICODE_CASE и MULTILINE.
CASE_INSENSITIVE,
2.2.6.2.2. Методы класса Pattern
Метод
public static boolean matches(String шаблон,
CharSequence строка-поиска)
проверяет соответствие шаблона строке-поиска и возвращает значение true, если строка
поиска соответствует шаблону и false – в противном случае.
Пример использования метода match
1. boolean isFound1 = Pattern.matches("a.c", "abc");
Переменная isFound1 получит значение true, т.к. строка "abc"соответствует
шаблону "a.c" (между "a" и "c" один произвольный символ).
2. boolean isFound2 = Pattern.matches("a.c", "abbc");
Переменная isFound2 получит значение false, т.к. строка "abc" не
соответствует шаблону "a.c" (между "a" и "c" два символа).
Помимо перечисленных методов в классе Pattern определены следующие
вспомогательные методы:
 public String pattern() – возвращает строку шаблона для объекта
класса Pattern;
 public int flags() – возвращает числовое значения флажка для объекта
класса Pattern; (если задано несколько флажков, возвращает сумму их числовых
значений);
 public static String quote(String строка) – возвращает строковый
шаблон для заданной строки (см. поле LITERAL);
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 38 Прикладное программирование в ТС
Лекция 3-04
 public String[] split(CharSequence строка-поиска) – создает из
строки-поиска массив, разделенный на элементы по шаблону, заданному в объекте класса
Pattern;
 public String[] split(CharSequence строка-поиска, int предел) –
создает из строки-поиска массив, разделенный на элементы по шаблону, заданному в
объекте класса Pattern, и с заданным в параметре предел количеством элементов (если
значение параметра больше или равно количеству элементов, либо меньше 0, выводятся
все элементы, если меньше количества элементов – все оставшиеся соответствия
выводятся в последнем элементе массива);
 public String toString() – возвращает строковое представление
откомпилированного шаблона.
Примеры использования вспомогательных методов
1.
// Задание шаблона
Pattern pattern4 = Pattern.compile("строк?",
Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
// Получение значения шаблона
String patternValue = pattern4.pattern();
// Получение строкового значения шаблона
String stringPattern = pattern4.toString();
// Получение значения флажков
int flags = pattern4.flags();
// Вывод результатов
System.out.println("patternValue='" + patternValue +
"' stringPattern='" + stringPattern +
"' flags=" + flags);
Вывод этого фрагмента будет иметь следующий вид:
patternValue='строк?' stringPattern='строк?' flags=66
Значение переменной flags будет равно сумме значений для поля
CASE_INSENSITIVE (2) и поля UNICODE_CASE (64).
2. boolean isFound3 = Pattern.matches("строк?", "строк");
boolean isFound4 = Pattern.matches("строк?", "строк?");
Переменная isFound3 получит значение true (метасимвол "?" в шаблоне
означает, что символ "к" должен встречаться в строке один раз или не встречаться
вообще). Переменная isFound4 получит значение false (символ "?" в строке не
соответствует шаблону).
// Получение строкового значения шаблона
String literalPattern = Pattern.quote("строк?");
boolean isFound5 =
Pattern.matches(literalPattern, "строк");
boolean isFound6 =
Pattern.matches(literalPattern, "строк?");
Переменная isFound5 получит значение false , а переменная isFound6
получит значение true (метасимвол "?" в шаблоне трактуется как обычный символ).
3.
// Задания шаблона для разделителя строк
// (пробел или запятая,пробел)
Pattern pattern5 = Pattern.compile(" |, ");
// Исходная строка
String inString = "Использование квантификаторов, " +
"классов и групп в шаблоне";
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 39 Прикладное программирование в ТС
Лекция 3-04
// Разделение исходной строки на компоненты
String outString[] = pattern5.split(inString);
// Вывод компонент строки
for (int i = 0; i < outString.length; i++) {
System.out.println("outString[" + i + "]='" +
outString[i] + "'");
}
Вывод этого фрагмента программы будет иметь следующий вид:
outString[0]='Использование'
outString[1]='квантификаторов'
outString[2]='классов'
outString[3]='и'
outString[4]='групп'
outString[5]='в'
outString[6]='шаблоне'
4. При замене предложения вызова метода split() на следующее предложение:
String outString[] = pattern5.split(inString, 4);
вывод фрагмента примет следующий вид:
outString[0]='Использование'
outString[1]='квантификаторов'
outString[2]='классов'
outString[3]='и групп в шаблоне'
2.2.6.3. Класс Matcher
2.2.6.3.1. Создание объекта класса Matcher
Класс Matcher обеспечивает выполнение поиска или замены соответствия
заданному объектом класса Pattern шаблону.
Объект класса Matcher создается с помощью метода
public Matcher matcher(CharSequence строка-поиска)
класса Pattern для строки-поиска.
Строковое представление объекта класса Matcher можно получить с помощью
метода
public String toString().
2.2.6.3.2. Операции с регионами
Поиск соответствия выполняется в подстроке исходной строки, называемой
регионом (region). По умолчанию регионом является вся вводимая последовательность
символов.
Установка границ региона выполняется с помощью метода
public Matcher region(int начальный-индекс, int конечный-индекс)
Этот метод возвращает объект класса Matcher для подстроки, начинающейся с
начального-индекса и заканчивающуюся индексом, на единицу меньшим, чем конечныйиндекс.
Получить текущие значения начального и конечного индексов региона для
объекта класса Matcher можно с помощью методов
public int regionStart()
и
public int regionEnd().
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 40 Прикладное программирование в ТС
Лекция 3-04
Характер границ региона можно установить с помощью методов
useAnchoringBounds() и useTransparentBounds().
Метод
public Matcher useAnchoringBounds(boolean флажок)
Если параметр флажок установлен в true, то для задания шаблона на границах
региона можно использовать анкеры "^" и "$". В противном случае (если флажок
установлен в false) соответствие анкерам "^" и "$" на границах региона не проверяется.
По умолчанию значение флажка равно true.
Метод
public Matcher useTransparentBounds(boolean флажок)
Если параметр флажок установлен в true, границы региона являются
прозрачными для шаблонов, содержащих граничные условия, а также «заглядывание
вперед» и «заглядывание назад», в том числе и за границы региона. В противном случае
(если флажок установлен в false) границы региона будут непрозрачными, т.е. операции
с символами за границами региона запрещены. По умолчанию значение флажка равно
false.
Методы
public boolean hasAnchoringBounds()
и
public boolean hasTransparentBounds()
позволяют проверить являются ли границы анкерными или прозрачными. При
выполнении условия методы возвращают true, в противном случае – false.
2.2.6.3.3. Методы поиска соответствий
Метод
public boolean matches()
выполняет для объекта класса Matcher поиск на соответствие всего региона, начиная с
начала региона. Возвращает true, если соответствие найдено и false – в противном
случае.
Метод
public boolean lookingAt()
так же, как метод matches(), выполняет для объекта класса Matcher поиск, начиная с
начала региона на наличие шаблона в регионе, но необязательно соответствия всего
региона шаблону. Возвращает true, если соответствие найдено и false – в противном
случае.
Пример использования методов matches() и lookingAt()
// Исходная строка
String inStr = "строка 1; Строка 2; СТРОКА 3";
// Задание шаблона для исходной строки
Pattern pattern6 = Pattern.compile("строка",
Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
// Создание объекта класса Matcher для всей строки
Matcher matcher1 = pattern6.matcher(inStr);
// Проверка с помощью метода matches()
// соответствия всей строки шаблону
boolean isFound7 = matcher1.matches();
// Проверка с помощью метода lookingAt()
// наличия шаблона в строке
boolean isFound8 = matcher1.lookingAt();
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 41 Прикладное программирование в ТС
Лекция 3-04
// Вывод границ региона для matcher1
// и результатов проверки
System.out.println("Регион для matcher1: индекс начала=" +
matcher1.regionStart() + " индекс окончания=" +
matcher1.regionEnd() + "\nisFound для matches():
" +
isFound7 + "\nisFound для lookingAt(): " + isFound8);
// Выделение региона в строке inStr
Matcher regionMatcher = matcher1.region(10, 16);
// Проверка с помощью метода matches()
// соответствия региона шаблону
boolean isFound9 = regionMatcher.matches();
// Вывод границ региона для regionMatcher
// и результата проверки
System.out.println("\nРегион для regionMatcher: " +
"индекс начала=" + regionMatcher.regionStart() +
" индекс окончания=" + regionMatcher.regionEnd() +
"\nisFound для matches(): " + isFound9);
Вывод этого фрагмента программы будет иметь следующий вид:
Регион для matcher1: индекс начала=0 индекс окончания=28
isFound для matches():
false
isFound для lookingAt(): true
Регион для regionMatcher: индекс начала=10
индекс окончания=16
isFound для matches(): true
Метод
public boolean find()
выполняет для объекта класса Matcher поиск, начиная с начала региона или, если
предыдущий вызов метода был успешным, и объект класса Matcher не был сброшен, с
первого символа после найденного предыдущего соответствия. Возвращает true, если
соответствие найдено и false – в противном случае.
Метод
public boolean find(int начальный-индекс)
выполняется так же, как и предыдущей метод, но поиск начинается не с начала региона, а
заданного начального-индекса. Если необходимо найти все соответствия шаблону в
строке, начиная с начального-индекса, то этот метод можно использовать только для
поиска первого соответствия. Все остальные соответствия определяются с помощью
метода find() без параметров.
Характеристики найденного соответствия, полученного с помощью методов
matches(),lookingAt() и find() можно определить с помощью следующих
методов:
 public String group() – возвращает предыдущее найденное соответствие;
 public String group(int номер-группы) – возвращает предыдущее
найденное соответствие для заданного номера-группы (группы нумеруются слева направо,
начиная с 1; если задан 0, метод выполняется аналогично предыдущему методу);
 public int start() – возвращает начальный индекс в строке поиска
предыдущего найденного соответствия;
 public int start(int номер-группы) – возвращает начальный индекс в
строке поиска предыдущего найденного соответствия для заданного номера-группы
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 42 Прикладное программирование в ТС
Лекция 3-04
(группы нумеруются слева направо, начиная с 1; если задан 0, метод выполняется
аналогично предыдущему методу);
 public int end() – возвращает конечный индекс в строке поиска
предыдущего найденного соответствия;
 public int end(int номер-группы) – возвращает конечный индекс в строке
поиска предыдущего найденного соответствия для заданного номера-группы (группы
нумеруются слева направо, начиная с 1; если задан 0, метод выполняется аналогично
предыдущему методу);
 public int groupCount() – возвращает количество групп в объекте класса
Matcher.
Метод
public Pattern pattern()
возвращает шаблон, соответствующий объекту класса Matcher.
Метод
public Matcher usePattern(Pattern новый-шаблон)
заменяет шаблон для объекта класса Match на новый-шаблон.
Метод
public boolean hitEnd()
возвращает true, если поиск соответствия затронул окончание строки поиска и false –
в противном случае.
Метод
public boolean requireEnd()
возвращает true и, если соответствие было найдено, то дальнейший поиск может
привести к потере соответствия. Если метод возвращает false и соответствие было
найдено, то дальнейший поиск не приведет привести к потере соответствия. Если
соответствия не было найдено, метод не имеет значения.
Примеры использования метода find(), методов определения характеристик
соответствия и методов задания опций для границ регионов
1.
// Исходная строка
String inStr = "строка 1; Строка 2; СТРОКА 3";
// Задание шаблона для исходной строки
Pattern pattern6 = Pattern.compile("строка",
Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE
// Создание объекта класса Matcher для всей строки
Matcher matcher1 = pattern6.matcher(inStr);
// Соответствия шаблону в строке не найдено
boolean isFound10 = false;
// Начальный индекс соответствия шаблону в строке
int matchIndex = 1;
// Цикл поиска соответствий в строке
while (matcher1.find()) {
// Определение предыдущего соответствия
String matchValue = matcher1.group();
// Определение начального индекса
// предыдущего соответствия
int matchBeginIndex = matcher1.start();
// Определение конечного индекса
// предыдущего соответствия
int matchEndIndex = matcher1.end();
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 43 Прикладное программирование в ТС
Лекция 3-04
// Вывод предыдущего соответствия
// и его характеристик
System.out.println("Соответствие " + matchIndex +
": '" + matchValue + "' индекс начала=" +
matchBeginIndex + " индекс окончания=" +
matchEndIndex);
// Найдено соответствие в строке
isFound10 = true;
// Увеличение индекса соответствия
// шаблону в строке на 1
matchIndex++;
}
// Если ни одного соответствия шаблону
// в строке не найдено
if(!isFound10){
// Вывод сообщения
System.out.println("Не найдено ни одного " +
"соответствия шаблону");
}
В этом фрагменте программы выполняется поиск и вывод всех соответствий
шаблону в строке. Вывод фрагмента будет иметь следующий вид:
Соответствие 1: 'строка' индекс начала=0
индекс окончания=6
Соответствие 2: 'Строка' индекс начала=10
индекс окончания=16
Соответствие 3: 'СТРОКА' индекс начала=20
индекс окончания=26
2. Если в предыдущем пункте необходимо выполнить поиск всех соответствий,
начиная с индекса 5 в строке inStr, то необходимо изменить цикл следующим образом:
// Первый вызов метода find()
// (начало поиска с индекса 5)
matcher1.find(5);
// Цикл поиска соответствий в строке
do {
// Определение предыдущего соответствия
String matchValue = matcher1.group();
// Определение начального индекса
// предыдущего соответствия
int matchBeginIndex = matcher1.start();
// Определение конечного индекса
// предыдущего соответствия
int matchEndIndex = matcher1.end();
// Вывод предыдущего соответствия
// и его характеристик
System.out.println("Соответствие " + matchIndex +
": '" + matchValue + "' индекс начала=" +
matchBeginIndex + " индекс окончания=" +
matchEndIndex);
// Найдено соответствие в строке
isFound10 = true;
// Увеличение индекса соответствия
// шаблону в строке на 1
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 44 Прикладное программирование в ТС
Лекция 3-04
matchIndex++;
}
// Проверка условия окончания цикла
while (matcher1.find());
Вывод измененного фрагмента будет иметь следующий вид:
Соответствие 1: 'Строка' индекс начала=10
индекс окончания=16
Соответствие 2: 'СТРОКА' индекс начала=20
индекс окончания=26
3.
// Исходная строка - ФИО и номера телефонов
String teleponeNumbers = "Иванов И.И.: 069-173-42-05;" +
"Петров П.П.: 035-979-15-46; Сидоров С.С.: 044-227-17-32";
// Задание шаблона для исходной строки
Pattern pattern7 =
Pattern.compile("([А-Я][а-я]+ [А-Я]\\.[А-Я]\\.): " +
"(\\d{3})-(\\d{3}-\\d\\d-\\d\\d)");
// Создание объекта класса Matcher
// для исходной строки
Matcher matcher2 = pattern7.matcher(teleponeNumbers);
// Цикл поиска соответствий в строке
for (int i = 1; matcher2.find(); i++) {
// Определение предыдущего соответствия для ФИО
String name = matcher2.group(1);
// Определение начального индекса
// предыдущего соответствия для ФИО
int nameBeginIndex1 = matcher2.start(1);
// Определение конечного индекса
// предыдущего соответствия для ФИО
int nameEndIndex = matcher2.end(1);
// Определение кода вызова
String callIndex = matcher2.group(2);
// Определение номера телефона
String telephone = matcher2.group(3);
// Вывод результатов поиска
System.out.println("ФИО: '" + name +
"' индекс начала=" + nameBeginIndex1 +
" индекс окончания=" + nameEndIndex);
System.out.println(" Код вызова: " +
callIndex + "
Телефон: " + telephone);
}
В этом фрагменте программы определяются и выводятся компоненты исходной
строки – списка телефонных номеров: фамилия и инициалы абонента (ФИО), код вызова и
номер телефона. Компоненты представлены в шаблоне как группы, заключенные в
круглые скобки. Вывод фрагмента имеет следующий вид:
ФИО: 'Иванов И.И.' индекс начала=0 индекс окончания=11
Код вызова: 069
Телефон: 173-42-05
ФИО: 'Петров П.П.' индекс начала=27 индекс окончания=38
Код вызова: 035
Телефон: 979-15-46
ФИО: 'Сидоров С.С.' индекс начала=55 индекс окончания=67
Код вызова: 044
Телефон: 227-17-32
4.
// Исходная строка - ФИО и номера телефонов абонента
String teleponeNumbers = "Иванов И.И.: 069-173-42-05;" +
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 45 Прикладное программирование в ТС
Лекция 3-04
"Петров П.П.: 035-979-15-46; Сидоров С.С.: 044-227-17-32" +
"Петренко Н.Ф.: 044-598-52-13; Король А.Г.: 077-342-81-00";
// Задание шаблона для исходной строки
Pattern abonentPattern =
Pattern.compile("[А-Я][а-я]+ [А-Я]\\.[А-Я]\\.: " +
"(\\d{3})-(\\d{3}-\\d\\d-\\d\\d)");
// Создание объекта класса Matcher
// для исходной строки
Matcher abonentMatcher =
bonentPattern.matcher(teleponeNumbers);
// Установка начального индекса
// для поиска соответствия шаблону в строке
int startIndex = 0;
// Цикл поиска соответствий в строке
for (int i = 1; abonentMatcher.find(startIndex); i++) {
// Определение предыдущего
// соответствия для абонента
String abonentString = abonentMatcher.group();
// Определение начального индекса
// предыдущего соответствия для абонента
int abonentBeginIndex = abonentMatcher.start();
// Определение конечного индекса
// предыдущего соответствия для абонента
int abonentEndIndex = abonentMatcher.end();
// Задание региона для абонента
Matcher abonentRegion =
abonentMatcher.region(abonentBeginIndex,
abonentEndIndex);
// Установление анкерных границ
// региона для абонента
abonentRegion =
abonentRegion.useAnchoringBounds(true);
// Задание шаблона для поиска ФИО абонента
// в начале региона
Pattern namePattern =
Pattern.compile("^Петр");
// Установка шаблона поиска фамилии в регионе
abonentRegion.usePattern(namePattern);
// Если шаблон найден в регионе абонента
if(abonentRegion.lookingAt()) {
// Вывод результатов поиска
System.out.println("Найден -> " + abonentString);
}
// Восстановление шаблона абонента
abonentMatcher.usePattern(abonentPattern);
// Установка нового значения начального индекса
// для поиска соответствия шаблону в строке
startIndex = abonentEndIndex;
}
В этом фрагменте программы выполняется поиск абонента, фамилия которого
начинается на символы "Петр". Сначала по шаблону abonentPattern получается
запись абонента, потом из этой записи образуется регион с анкерными границами, в
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 46 Прикладное программирование в ТС
Лекция 3-04
котором ищутся символы "Петр" в начале региона. Если регион удовлетворяет критерию
поиска, его содержимое выводится на экран.
Вывод фрагмента имеет следующий вид:
Найден -> Петров П.П.: 035-979-15-46
Найден -> Петренко Н.Ф.: 044-598-52-13
// Исходная строка - сведения о студентах:
// фамилия и инициалы, пол, дата рождения
String studentChars = "Иванов И.И.;м;13.07.1089" +
"\nПетрова П.П.;ж;25.01.1990\nСидоров С.С.;м;01.09.1089" +
"\nКнязь Н.Ф.;ж;11.04.1990\nКороль А.Г.;м;04.12.1089";
// Задание шаблона для исходной строки
Pattern namePattern =
Pattern.compile("^[А-Я][а-я]+ [А-Я]\\.[А-Я]\\.",
Pattern.MULTILINE);
// Создание объекта класса Matcher
// для исходной строки
Matcher studentMatcher =
namePattern.matcher(studentChars);
// Установка начального индекса
// для поиска соответствия шаблону в строке
int startIndex = 0;
// Цикл поиска соответствий в строке
for (int i = 1; studentMatcher.find(startIndex); i++) {
// Определение предыдущего соответствия
// для студента
String studentString = studentMatcher.group();
// Определение начального индекса
// предыдущего соответствия для студента
int studentBeginIndex = studentMatcher.start();
// Определение конечного индекса
// предыдущего соответствия для студента
int studentEndIndex = studentMatcher.end();
// Задание региона для студента
Matcher studentRegion =
studentMatcher.region(studentBeginIndex,
studentEndIndex);
// Установление прозрачных границ
// региона для студента
studentRegion =
studentRegion.useTransparentBounds(true);
// Задание шаблона для поиска ФИО студента
// с "заглядыванием вперед" для
// определения пола студента
Pattern genderPattern =
Pattern.compile("[А-Я][а-я]+ [А-Я]\\.[А-Я]\\.(?=;м)");
// Установка шаблона поиска фамилии в регионе
studentRegion.usePattern(genderPattern);
// Если шаблон найден в регионе студента
if(studentRegion.lookingAt()) {
// Вывод результатов поиска
System.out.println("Найден студент -> " +
4.
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 47 Прикладное программирование в ТС
Лекция 3-04
studentString);
}
// Восстановление шаблона студента
studentMatcher.usePattern(namePattern);
// Установка нового значения начального индекса
// для поиска соответствия шаблону в строке
startIndex = studentEndIndex;
}
В этом фрагменте программы выполняется поиск и вывод на экран студентов –
мужчин. Сначала по шаблону studentPattern получается запись студента, потом из
этой записи образуется регион с прозрачными границами, в котором с «заглядыванием
вперед» ищутся символы ";м" за пределами региона. Если регион удовлетворяет
критерию поиска, его содержимое выводится на экран.
Вывод фрагмента имеет следующий вид:
Найден студент -> Иванов И.И.
Найден студент -> Сидоров С.С.
Найден студент -> Король А.Г.
2.2.6.3.4. Методы замены
Методы класса Matcher позволяют не только выполнить поиск в строке по
заданному шаблону, но и заменить найденные соответствия заданными
последовательностями символов – строками замены.
Так, методы
public String replaceFirst(String строка-замены)
и
public String replaceAll(String строка-замены)
позволяют заменить только первое соответствие или все соответствия в строке поиска
строкой-замены. Оба метода возвращают измененную строку.
Примеры использования методов replaceFirst() и replaceAll()
1.
// Исходная строка
String inStr = "строка 1; Строка 2; СТРОКА 3";
// Задание шаблона для исходной строки
Pattern firstStringPattern = Pattern.compile("строка",
Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
// Создание объекта класса Matcher для всей строки
Matcher firstStringMatcher =
firstStringPattern.matcher(inStr);
// Замена первого соответствия
// заданной строкой замены для новой строки
String newStr =
firstStringMatcher.replaceFirst("Первая строка");
// Вывод новой строки
System.out.println("Новая строка: '" + newStr + "'");
В этом фрагменте программы выполняется поиск соответствия шаблону
"строка" (без учета регистра) в строке inStr. Первое найденное соответствие (и
только оно) заменяется на строку "Первая строка". Вывод фрагмента имеет
следующий вид:
Новая строка: 'Первая строка 1; Строка 2; СТРОКА 3'
2.
// Исходная строка - ФИО и номера телефонов абонента
String teleponeNumbers = "Иванов И.И.: 069-173-42-05;" +
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 48 Прикладное программирование в ТС
Лекция 3-04
"Петров П.П.: 035-979-15-46; Сидоров С.С.: 044-227-17-32";
// Задание шаблона с группами для исходной строки
Pattern abonentPattern =
Pattern.compile("(\\d{3})-(\\d{3})-(\\d\\d)-(\\d\\d)");
// Создание объекта класса Matcher для исходной строки
Matcher abonentMatcher =
abonentPattern.matcher(teleponeNumbers);
// Установка начального значения
// новой строки
String newTeleponeNumbers = null;
// Цикл поиска соответствий в строке
for (int i = 1; abonentMatcher.find(); i++) {
// Внесение изменения в новую строку
// для текущего найденного соответствия
newTeleponeNumbers =
abonentMatcher.replaceAll("($1)-$2-$3$4");
}
// Вывод новой строки
System.out.println("Новая строка: '" +
newTeleponeNumbers + "'");
В этом фрагменте программы выполняется поиск в строке teleponeNumbers
соответствия шаблону номера телефона, в котором определены четыре группы: первые
три цифры номера ($1), следующие три цифры номера ($2), следующие две цифры
номера ($3) и последние две цифры номера ($3). Для всех найденных соответствий номер
телефона изменяется следующим образом: первые три цифры номера ($1) заключаются в
скобки, а последние четыре цифры номера ($3 и $4) записываются слитно (без символа
"-" между ними). Вывод фрагмента имеет следующий вид:
Новая строка: 'Иванов И.И.: (069)-173-4205;
Петров П.П.: (035)-979-1546;Сидоров С.С.: (044)-227-1732'
Метод
public Matcher appendReplacement(StringBuffer новая-строка,
String строка-замены)
формирует новую-строку по следующему алгоритму:
 пересылает символы строки поиска в новую-строку, начиная с конечной позиции
(append position) до символа на единицу меньшего, чем символ, определяемого методом
start() объекта Match;
 затем к новой строке добавляется строка-замены;
 после этого конечная позиция в новой строке становится равной позицией,
определяемой методом end() объекта Match.
В начале просмотра и замены значение конечной позиции равно 0.
Метод
public StringBuffer appendTail(StringBuffer новая-строка)
пересылает символы строки поиска в новую-строку, начиная с конечной позиции и до
конца
строки
поиска.
Этот
метод
используется
вместе
с
методом
appendReplacement() для завершения процесса поиска и замены в строке.
Методы appendReplacement() и appendTail() выполняют те же
действия, что и методы replaceFirst() и replaceAll(), однако они позволяют
управлять как количеством замен, так самими заменами в строке.
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 49 Прикладное программирование в ТС
Лекция 3-04
Пример использования методов appendReplacement() и appendTail()
// Исходная строка
String inStr = "строка 1; СТрока 2; СТРОКА 3;" +
"сТрока 4; сТРОКА 5; Строка 6; СТрокА 7";
// Задание шаблона для исходной строки
Pattern stringPattern = Pattern.compile("строка",
Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CASE);
// Создание объекта класса Matcher для всей строки
Matcher stringMatcher =
stringPattern.matcher(inStr);
// Создание новой строки
StringBuffer newStr = new StringBuffer();
// Цикл поиска соответствий и замены в строке
for (int i = 1; stringMatcher.find(); i++) {
// Получение текущего соответствия
// в исходной строке
String currentMatch = stringMatcher.group();
// Если соответствие имеет допустимую форму
if(currentMatch.equals("строка") ||
currentMatch.equals("Строка") ||
currentMatch.equals("СТРОКА"))
// Оставить форму без изменения
stringMatcher.appendReplacement(newStr,
currentMatch);
else {
// Если первый символ соответствия // строчная буква
if(currentMatch.charAt(0) == 'с')
// Замена соответствия строкой "строка"
stringMatcher.appendReplacement(newStr,
"строка");
// Если первый символ соответствия // заглавная буква
else if(currentMatch.charAt(0) == 'С')
// Замена соответствия строкой "Строка"
stringMatcher.appendReplacement(newStr,
"Cтрока");
}
}
// Добавление остатка исходной строки к новой строке
stringMatcher.appendTail(newStr);
// Вывод новой строки
System.out.println("Новая строка: '" + newStr + "'");
В этом фрагменте программы выполняется поиск соответствия шаблону
"строка" (без учета регистра) в строке inStr. Если соответствие имеет одну из
допустимых форм, замена производится на допустимую форму. В противном случае, если
соответствие начинается на строчную букву, вся строка заменяется на форму, состоящую
из одних строчных букв, иначе выполняется замена на строку, в которой первая буква –
заглавная, а остальные – строчные. Вывод фрагмента имеет следующий вид:
Новая строка: 'строка 1; Cтрока 2; СТРОКА 3;
строка 4; строка 5;Строка 6; Cтрока 7'
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 50 Прикладное программирование в ТС
Лекция 3-04
В классе Matcher определены также следующие вспомогательные методы:
 public static String quoteReplacement(String строка-замены) –
преобразует строку замены в строковую форму, в которой метасимволы, такие как "$",
рассматриваются как обычные символы (действует аналогично методу quote() класса
Pattern) и возвращает измененную строку;
 public Matcher reset() – сбрасывает все хранимые в объекте класса
Matcher данные, устанавливает конечную позицию в 0, регионом становится вся строка
поиска (однако на состояние анкерной границы и границы прозрачности сброс не влияет)
и возвращает измененный объект класса Matcher;
 public Matcher reset(CharSequence строка-поиска) – выполняет те
же действия, что и предыдущий метод, но задает для объекта новую последовательность
символов поиска.
2.2.6.4. Класс PatternSyntaxException
Класс PatternSyntaxException бросает исключение, если регулярное
выражение (шаблон) содержит синтаксическую ошибку.
В классе определены следующие методы:
 public String getPattern() – возвращает шаблон, содержащий ошибку;
 public String getDescription() – возвращает описание ошибки;
 public int getIndex() – возвращает позицию символа ошибки в шаблоне;
 public String getMessage() – возвращает сообщение об ошибке,
содержащее все перечисленные выше компоненты: описание ошибки и ее индекс, шаблон,
содержащий ошибку и визуальную индикацию индекса ошибки внутри шаблона.
Пример использования класса PatternSyntaxException для вывода параметров
ошибки в регулярном выражении
try {
// Задание шаблона для исходной строки
Pattern errorPattern = Pattern.compile("ab|*d");
} catch(PatternSyntaxException e) {
System.out.println("Неверный шаблон: '" +
e.getPattern() + "'");
System.out.println("Описание ошибки: " +
e.getDescription());
System.out.println("Позиция ошибки: " +
e.getIndex());
System.out.println("Сообщение об ошибке:\n" +
e.getMessage());
}
В этом фрагменте ошибка в задании шаблона errorPattern (перед
метасимволом "*" должен обязательно быть какой-либо обычный символ) приводит к
переходу в блок catch и выводу характеристик ошибки:
Неверный шаблон: 'ab|*d'
Описание ошибки: Dangling meta character '*'
Позиция ошибки: 3
Сообщение об ошибке:
Dangling meta character '*' near index 3
ab|*d
^
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
- 51 Прикладное программирование в ТС
Лекция 3-04
2.2.6.5. Методы класса String для работы с регулярными выражениями
Для работы с регулярными выражениями в классе String определены следующие
методы:
 public boolean matches(String шаблон) – если объект класса String
соответствует шаблону, возвращает значение true, в противном случае возвращает
false (действует аналогично методу matches() класса Pattern);
 public String[] split(String шаблон) – создает для объекта класса
String массив строк, разделенный на элементы по заданному шаблону (действует
аналогично соответствующему методу split() класса Pattern);
 public String[] split(String шаблон, int предел) – создает для
объекта класса String массив строк, разделенный на элементы по заданному шаблону, и
с заданным в параметре предел количеством элементов (если значение параметра больше
или равно количеству элементов, либо меньше 0, выводятся все элементы, если меньше
количества элементов – все оставшиеся соответствия выводятся в последнем элементе
массива) (действует аналогично соответствующему методу split() класса Pattern);
 public String replaceFirst(String шаблон, String строка-замены)
– заменяет в объекте String первое соответствие шаблону на строку-замены и
возвращает измененную строку (действует аналогично методу replaceFirst ()
класса Match);
 public String replaceAll(String шаблон, String строка-замены) –
заменяет в объекте String все соответствия шаблону на строку-замены и возвращает
измененную строку (действует аналогично методу replaceAll() класса Match).
Пример использования метода replaceFirst() класса String для замены в
строках с использованием регулярных выражений
// Исходная строка
String inStr = "строка 1; Строка 2; СТРОКА 3";
// Замена первого соответствия в исходной строке
String newStr =
inStr.replaceFirst("строка|Строка|СТРОКА",
"Первая строка");
// Вывод новой строки
System.out.println("Новая строка: '" + newStr + "'");
Вывод этого фрагмента программы имеет следующий вид:
Новая строка: 'Первая строка 1; Строка 2; СТРОКА 3'
Файл: 681457702 Создан: 09.07.2007 Модифицирован: 01.05.2016
Автор: Шонин В.А.
Download