КИС – это определенная совокупность методов и решений для создания информационного пространства управления и обеспечения деятельности компании. КИС = технологические средства + ПО +данные жизнедеятельности предприятия. Цель: максимально оптимизировать бизнес – процессы, структуру и методы управления.
Факторы, влияющие на развитие КИС:
1Развитие методик управления предприятием.
2Развитие общих возможностей и производительности компьютерных систем (развитие сетевых технологий и систем передачи данных, интеграция ПК с различным оборудованием).
3Развитие подходов к технологической и программной реализации элементов информационных систем. Среди них наибольшее влияние имели:
а) объектно-ориентированное программирование
б) сетевые технологии: клиент-сервер и многоуровневые
в) развитие сети Интернет для работы с удаленными клиентами, Интернет-технологии (использование Интернет в корпоративных сетях)
Методы управления:
1ресурсами (бухгалтерия, финансы, кадры, логистика)
2процессами (документооборот, производство, качество, проекты)
3корпоративными знаниями – коммуникациями
Основные составляющие КИС:
1Компьютерная инфраструктура (сетевая, телекоммуникационная, программная, информационная и организационная) или корпоративная сеть.
2Функциональные подсистемы. Зависят от специфики задач, базируются на компьютерной инфраструктуре и определяют прикладную функциональность.
КИС может быть реализована на существующем интегрированном программно-аппаратном комплексе или создана «с нуля» посредством единого решения.
КИС – это определенная совокупность методов и решений для создания информационного пространства управления и обеспечения деятельности компании. КИС = технологические средства + ПО +данные жизнедеятельности предприятия. Цель: максимально оптимизировать бизнес – процессы, структуру и методы управления.
Классификация КИС:
1по функциональным возможностям
2по масштабу предприятия
3по стоимости внедрения проекта (лицензия + услуги внедрения)
4по срокам внедрения
5по сфере применения
6по используемым информационным технологиям
1. По функциональным возможностям:
1Бухгалтерский учет . Легко формируется, но разработка весьма трудоемка, так как повышенные требования к надежности, простоте и удобству в эксплуатации.
2Управление финансовыми потоками. Расчеты с поставщиками и покупателями. Весьма критично к ошибкам, поэтому должно быть точно просчитано и жестко контролируемо. Цель – увеличить оборотные средства предприятия.
3Управление складом, ассортиментом, закупками (логистические цепочки).Автоматизация процесса движения товара. Цель – получить максимальную прибыль при постоянной нехватке средств, то есть отследить те 20% товара, которые приносят 80% прибыли.
4Управление производственным процессом. Сложная задача. Автоматизация дает возможность грамотно планировать и учитывать затраты, проводить техническую подготовку производства, оперативно управлять процессом выпуска продукции в соответствии с производственной программой и технологией.
5Управление маркетингом. Подразумевает сбор и анализ данных о фирмах-конкурентах, их продукции, ценах, а также моделирование параметров внешнего окружения для определения оптимального уровня цен, прогнозирования прибыли и планирования рекламных компаний.
6Документооборот. Автоматизация позволяет отражать реальную производственную деятельность и возможность воздействовать на нее.
7Оперативное управление предприятием. Это общая база данных предприятия, используемая в бизнес-процессах. По сути, это и есть КИС.
8Предоставление информации о фирме. Предполагается наличие Web-сервера предприятия для решения задач: -создания имиджа предприятия; -предоставление информации о фирме, предлагаемых товарах и услугах существующим и потенциальным клиентам; -электронная коммерция
КИС– это определенная совокупность методов и решений для создания информационного пространства управления и обеспечения деятельности компании. КИС = технологические средства + ПО +данные жизнедеятельности предприятия. Цель: максимально оптимизировать бизнес – процессы, структуру и методы управления.
По стоимости и срокам внедрения. Для крупных корпораций: стоимость от $150 – 300 тыс.; сроки внедрения год и больше («Галактика»). Для среднего бизнеса: стоимость от $840 – 880 тыс.; сроки внедрения 2-3 мес. («Парус», «Аксанта»). Для малого бизнеса: несколько тыс.$ (1С:Предприятие). Для бюджетных: «Парус», «Галактика».
По сфере применения
1) системы обработки транзакций (СУБД) разделяются на пакетные и оперативные.
Оперативные – для управления (OLTP – Online Transaction Processity). Транзакции: заказы, платежи, запросы. Требования: высокая производительность обработки транзакции; гарантированная доставка информации при удаленном доступе к БД.
2) системы принятия решение - DSS (Decision Support System). Используются сложные запросы, с помощью которых производится отбор и анализ данных в различных разрезах: временном, географическом и др.
3) информационно-справочные системы основаны на гипертекстовых документах и мультимедиа.
4) офисные информационные системы - автоматизация делопроизводства и управления документооборотом.
По способу организации:
1) на основе архитектуры файл-сервера (FS)
2) модель удаленного доступа к данным (Remote Data Access – RDA)
3) модель сервера базы данных (Data Base Server – DBS)
4) модель сервера приложений - многоуровневая архитектура ( Application Server)
5) модель Интернет/интранет-технологии
2-4) – все это модели «клиент-сервер». В этой технологии не выполняется основной принцип распределенных систем – отсутствие центральной установки, так как в ее основе два принципа:
То есть такие системы распределены только в отношении пользователей, поэтому давно стали отдельным классом многопользовательских систем.
Сервер приложений может выполнять несколько прикладных функций, каждая – как отдельная служба. Серверов AS может быть несколько (каждый для отдельных услуг).
Архитектура «клиент-сервер», основанная на Web-технологии.
Рабочая станция – клиент
Web-документ – гипермедийный документ, объединяющий ссылками различные страницы. Web-страница может быть связана с программами и содержать ссылки на другие объекты – гиперссылки на:
1другую часть Web-документа
2другой Web-документ или документ другого формата (Word, Excel), размещенный на любом ПК сети
3мультимедийный объект (рисунок, звук, видео)
4программу, которая при переходе на нее по ссылке будет передана клиенту для интерпретации или запуска на выполнение навигатором
5любой другой сервис – электронную почту, копирование файлов с другого ПК, сети, поиск информации и т.д.
Передачу обеспечивает Web-сервер (доставляет информацию из различных источников и в однородном виде предоставляет пользователю).
Достоинства систем Интранет:
1на сервере вырабатывается информация (а не данные) в форме, удобной уже для представления пользователю
2для обмена используется протокол открытого типа
3у клиента только программа-навигатор, поэтому легче выполнять централизованное управление
4унифицирован интерфейс (не зависит от ПО пользователя: ОС, СУБД)
Требования к КИС: 1Системный подход к комплексу задач управления 2Наличие СУБД и клиент-серверных технологий обработки 3Безопасность 4Контроль и разграничение прав доступа 5Модульный принцип построения из оперативно-функциональных блоков 6Поддержка Интернет и Интранет 7Поддержка принятых стандартов управления 8Русифицированный оргонамический интерфейс
Концепции MRP I, MRP II и CRP.
Концепция планирования потребности в материалах (MRP I) предполагает метод решения следующего комплекса управленческих задач: 1)формирование календарного плана-графика снабжения сырьем, материалами и комплектующими; 2)управление складским хозяйством; 3)учет оборотных средств (запасов материалов).
Концепция планирования потребности в производственных мощностях (CRP) нацелена на улучшение использования производственных мощностей рабочих центров (оборудования, поточных линий, бригад рабочих и т.п.). Система выполняет планирование и балансировку загрузки рабочих центров с учетом ресурсных ограничений и планов выпуска готовой продукции. Информационные системы классов CRP/MRP обеспечивают реализацию функций управления в направлении «сверху вниз», без учета обратной связи, а также решение функциональных задач планирования потребностей в материалах и производственных мощностях. Такие функции управления, как бизнес-планирование, планирование продаж, планирование производства, разработка главного календарного плана производства, оказались не охваченными ИС классов MRP/CRR.
Планирование ресурсов производства (MRP II) является усовершенствованным методом планирования всех видов ресурсов предприятия, продолжением и расширением замкнутого цикла MRP. Важнейшая установка стандарта MRP II – обеспечение руководящего персонала необходимой информацией для принятия управленческих решений. Система MRP II обеспечивает поддержку 16 функций управления корпорацией, сгруппированных следующим образом: =бизнес-планирование; =планирование продаж и операций; =планирование производства; =формирование главного календарного плана производства; =планирование потребности в материалах; =планирование потребности в мощностях; =система поддержки исполнения планов для производственных мощностей и материалов.
Система MRP II представляет собой подробную и точную модель производства, основными объектами которой являются:
-укрупненный план потребности в производственных мощностях;
-план потребности в материалах;
-план потребности в производственных мощностях;
-финансовый план.
К недостаткам MRP II-систем относятся:
-отсутствие интеграции с процессами управления финансами и персоналом;
-ориентация на существующие заказы (специального комплекса задач по прогнозированию спроса нет);
-слабая интеграция с системами проектирования и конструирования (конструкторско-технологической подготовкой производства).
В 1990-х гг. MRP II интегрируют с модулем финансового планирования (Financial Resource Planning — FRP) и системой бизнес планирования. В результате сформировалась система класса предприятия (корпорации) (Enterprise Resource Planning — ERP), которая позволяет эффективно планировать коммерческую деятельность корпорации, включая планирование потребностей материальных, трудовых и финансовых ресурсов, ресурсов оборудования, а также осуществлять подготовку инвестиционных проектов. В основе методологии ERP лежит принцип единого хранилища данных репозитария, содержащего всю деловую информацию, накопленную организацией в процессе ведения бизнеса, включая финансовую информацию, данные, связанные с производством, управлением персоналом, или любые другие сведения.
Требования к КИС:
1Системный подход к комплексу задач управления
2Наличие СУБД и клиент-серверных технологий обработки
3Безопасность
4Контроль и разграничение прав доступа
5Модульный принцип построения из оперативно-функциональных блоков
6Поддержка Интернет и Интранет
7Поддержка принятых стандартов управления
8Русифицированный оргонамический интерфейс
Интеграция концепций управления.
Особенностью систем MRP II и ERP является основополагающий принцип системности и функциональной целостности системы управления. Подобные системы могут применяться для управления предприятиями различного масштаба. Эти системы позволяют:
-оптимизировать бизнес-процессы для снижения издержек на производство и реализацию продукции, работ и услуг;
-использовать оптимальные методы планирования и управления запасами материальных ценностей;
-обеспечить управление себестоимостью продукции, сократить незавершенное производство;
-сократить цикл изготовления продукции (заказов);
-вести детализированный учет работы каждой производственной единицы;
-оперативно вносить изменения в производственные планы;
-улучшить обслуживание клиентов и заказчиков и др.
Однако с накоплением опыта моделирования производственных и непроизводственных бизнес-процессов понятие ERP постоянно уточняется, охватывая все больше функций — таких, как работа с клиентами (CRM), управления цепочками поставок (SCM), системы помощи в принятии решений (BI), системы электронной коммерции (е-Cоmmerсе).
Фундаментом такой интеграции стали:
-унификация понятия жизненного цикла продукции;
-переход к принципам постоянного совершенствования деятельности предприятия, что заставило отказаться от жестких административных схем управления;
-появление систем информационного описания бизнес-моделей предприятий.
Поэтому ERP-система — это своего рода интеграционный компонентный подход к управлению корпорацией, который опирается на информационные технологии, систему управления качеством, международные стандарты, модели ведения бизнеса, механизмы постоянного совершенствования управленческой деятельности. Благодаря ERP удается интегрировать свыше 90% бизнес-процессов корпорации.
В 2001 году консалтинговая компания «Gartner Group» вводит понятие систем оптимизации управления ресурсами предприятия второго поколения ERP II, которые отличаются от обычных ERP-систем следующими признаками:
-расширенный функционал ERP-систем, полная автоматизация функций системы управления в режиме реального времени;
-значимость ERP-системы в деятельности корпорации;
-переход от автоматизации внутренних бизнес-процессов компании к свободному взаимодействию компании со своими контрагентами (заказчиками, поставщиками, банками, налоговыми органами и пр.);
-пользователи ERP II-систем — внутренние и внешние компании всех секторов и сегментов рынка; отсутствие ограничений на масштабы и географическое положение объекта управления (подразделений корпорации);
-открытость ERP-системы, поддержка взаимодействия с внешними информационными системами на базе стандартных технологий и программных интерфейсов;
-единое информационное пространство для принятия управленческих решений, высокий уровень качества информации для реализации функций управления, современные информационные технологии обработки данных;
-высокая надежность функционирования КИС, защита данных от несанкционированного доступа, других угроз целостности и сохранности данных, дружественный пользовательский интерфейс и др.
Для того чтобы понять, какие бизнес-процессы автоматизирует современная ERP-система, нужно обратиться к бизнес-моделированию. Именно бизнес-модель корпорации в виде графических и текстовых описаний позволяет понять процесс управления корпорацией. В сущности, в основе бизнес-модели корпорации лежат следующие принципы:
-бизнес-функции описывают, ЧТО делает бизнес;
-бизнес-процессы описывают, КАК предприятие выполняет свои бизнес-функции;
-организационная структура определяет, ГДЕ исполняются бизнес-функции и бизнес-процессы;
-существуют фазы, определяющие, КОГДА (в какой последовательности) должны быть внедрены те или иные бизнес-функции;
-роли, определяющие, КТО исполняет бизнес-процессы;
-правила, определяющие связь «ЧТО, КАК, ГДЕ, КОГДА и КТО».
Рис. 1. Бизнес-модель корпорации для ERP-системы
Требования к КИС:
1Системный подход к комплексу задач управления
2Наличие СУБД и клиент-серверных технологий обработки
3Безопасность
4Контроль и разграничение прав доступа
5Модульный принцип построения из оперативно-функциональных блоков
6Поддержка Интернет и Интранет
7Поддержка принятых стандартов управления
8Русифицированный оргонамический интерфейс
Требования к ERP-системе
Функции, процессы, структура, фазы, роли и правила нашей СМБ-корпорации будут определять следующие требования к ERP-системе.
-Полнофункциональность — это наиболее полный охват бизнес-функций без дорогостоящих излишеств. Согласно данным исследования Aberdeen Group (август 2006 г.), 69% средних предприятий и 64% компаний малого бизнеса называют определяющим критерием при оценке решения функциональность. Выбранную ERP-систему такие организации будут эксплуатировать как минимум 3-5 лет, а с учетом быстрого роста СМБ решение должно быть не только полнофункциональным, но и масштабируемым.
-Гибкость и контроль — возможность лучшего контроля над бизнесом, что подразумевает внедрение определенных правил и процедур по всему предприятию, создание «учетного костяка».
-Легкость освоения — степень сложности инсталляции и внедрения системы. Базовым требованием являются графические интерфейсы, позволяющие пользователю легко и быстро освоить расширенные функции, которые обеспечивает ERP. Эти интерфейсы, предпочтительно локализованные, должны предлагать пользователю возможность удобного для него конфигурирования основных функций методом «drag and drop» (тащи и брось — перетаскивание иконок), с минимальным числом кликов.
-Простота в обслуживании — легкость в поддержке и обслуживании, поскольку ИТ-отделы СМБ-компаний не располагают значительными ресурсами.
-Доступность по цене и предсказуемость — не только доступность по уровню стоимости лицензий, но и определение предсказуемой общей стоимости владения (TCO).
-Использование «лучших практик» — СМБ-корпорация должна отталкиваться при внедрении ERP-системы не только от текущих, сиюминутных потребностей и задач, которые надо быстро решить с помощью автоматизации, но и ориентироваться на стратегическое видение бизнеса и опыт успешных предприятий.
Перечисленные требования определяют типовой набор автоматизируемых бизнес-процессов современной ERP-системы.
Требования к КИС:
Типовой набор автоматизируемых бизнес-процессов современной ERP-системы.
ПРОИЗВОДСТВО Планирование снабжения и сбыта на основе текущей ситуации и прогнозных оценок. Управление цеховым расписанием. Конфигурирование продукции. Графические инструменты планирования и конструирования.
БИЗНЕС-АНАЛИЗ Система взаимосвязанных показателей (Balanced Scorecard). Конструирование многомерных аналитических кубов (OLAP). Внутреннее и внешнее анкетирование. Стратегическое планирование, создание ключевых индикаторов производительности (КРI), соответствующие корпоративной стратегии, управление ими и использование их как основы для организации взаимодействия между подразделениями и внутри них. Выполнение индивидуальных настроек информационных панелей бизнес-анализа в соответствии с потребностями отдельных сотрудников. Обеспечение подотчетности путем назначения ответственных за конкретный показатель производительности.
ДИСТРИБУЦИЯ Управление системой распределенных складов. Управление запасами со сквозным применением складской аналитики. Обработка заказов с учетом коммерческих соглашений. Регулирование сроков поставки и стоимости заказа. Партионный учет и резервирование товаров под заказ.
УПРАВЛЕНИЕ ПРОДАЖАМИ И МАРКЕТИНГОМ Автоматизация деятельности отделов продаж и маркетинга. Телемаркетинг и интеграция с телефонией. Анкетирование. Управление продажами. Обслуживание клиентов через Интернет. Управление документацией. Интеграция со специализированным продуктом прогнозирования спроса.
УПРАВЛЕНИЕ ЦЕПОЧКАМИ ПОСТАВОК Прогнозное планирование. Внутрихолдинговые торговые операции. Управление снабжением. Использование Интернет для взаимодействия с партнерами. Мониторинг производительности поставщиков. Электронный документооборот с контрагентами. Инструменты поддержки принятия решений при планировании поставок/сбыта.
УПРАВЛЕНИЕ СЕРВИСНОЙ ДЕЯТЕЛЬНОСТЬЮ Поддержка широкого спектра сервисных операций. Планирование сервисной деятельности. Управление ремонтами. Организация сервисной подписки.
УПРАВЛЕНИЕ ПРОЕКТАМИ Ведение текущей деятельности по проектам различных типов. Планирование и анализ результатов. Финансовый мониторинг кратко- и долгосрочных проектов. Доступ консультантов к данным проекта через Интернет.
УПРАВЛЕНИЕ ПЕРСОНАЛОМ Организационная структура предприятия. Отслеживание перемещений персонала. Подбор персонала по определяемым критериям. Управление квалификацией и развитие персонала. Российский кадровый учет. Доступ сотрудников к информации через Интернет.
УПРАВЛЕНИЕ ФИНАНСАМИ Финансовый учет со сквозной аналитикой и аудитом операций. Управление финансами холдинга, взаиморасчеты и консолидация. Учет основных средств. Расчет заработной платы. Начисление и уплата НДС, формирование книг покупок и продаж. Учет налога на прибыль.
Требования к КИС:
1Системный подход к комплексу задач управления
2Наличие СУБД и клиент-серверных технологий обработки
3Безопасность
4Контроль и разграничение прав доступа
5Модульный принцип построения из оперативно-функциональных блоков
6Поддержка Интернет и Интранет
7Поддержка принятых стандартов управления
8Русифицированный оргонамический интерфейс
Интегративный потенциал концепции ERP II
Как показывает история развития концепции ERP, традиционные ERP-системы ориентированы на повышение гибкости, прозрачности, производительности и эффективности внутрикорпоративных бизнес-процессов. Хотя эти системы позволили сильно усовершенствовать обработку заказов, их возможностей было недостаточно для управления сложными бизнес-процессами, охватывающими несколько предприятий, партнеров в цепочке поставок. Для решения этой задачи требуется создать основанную на идеологии совместной работы «экосистему», которая подталкивала бы ценных клиентов и заинтересованных лиц делиться бизнес-знаниями друг с другом. Именно такую систему в консалтинговой компании Gartner и назвали ERP II, определив ее как «бизнес-стратегию и набор приложений, ориентированные на особенности конкретной отрасли и повышающие ценность компании для клиентов и владельцев за счет поддержки и оптимизации оперативных и финансовых процессов совместной работы внутри и между предприятиями».
Другими словами, ERP II — это конкурентная стратегия интеграции централизованной базовой ERP-системы с узкоспециализированными решениями, такими, как CRM (Customer Relationship Management), SCM (Supply Chain Management), а также системой управления знаниями (Knowledge Management, КМ).
Качественное отличие ERP II — в возможности управления информационными цепочками ценности для создания массива долгосрочных внутренних и внешних отношений. Такая интеграция всей цепочки в единое «сообщество ценности» (value network) повышает прозрачность доступа к информации, ускоряет процессы принятия решений и сокращает время отклика. Она способствует доступности знаний для совместного диалога, следствием которого может стать появление инновационных идей и оптимальных решений сложных проблем.
Требования к КИС:
Рынок ERP-систем в России.
Немного о рынке ERP-систем в России, который растет, по оценкам аналитиков, на 30-35% в год. Основные поставщики ERP-решений (ERP-вендоры) и систем, а также их рыночные доли приведены на рисунке 2.
Рис. 2. Российский рынок поставщиков ERP
На рисунке 3 отражены оценки внедрения ERP-проектов двух ведущих фирм-вендоров Oracle и SAP с точки зрения СМБ.
Рис. 3 Оценка внедрения ERP-проектов Oracle и SAP
На рис. 4 представлены наиболее значимые критерии выбора ERP — такие как функционал, удобство использования и общая стоимость владения — ТСО.
Рис. 4. Наиболее значимые критерии выбора ERP-системы
Востребованность различных ИТ-систем для СМБ, стоимость ERP-проектов, средние затраты, их временная продолжительность внедрения показаны на рисунках 5-8.
Рис. 5 Средняя стоимость ERP-систем различных производителей (для СМБ)
Рис. 6. Средние затраты компаний различного масштаба на ERP ($ тыс.)
Рис. 7. Наиболее востребованные решения в СМБ
Рис. 8. Средняя длительность IT-проектов для СМБ-компаний
Те заказчики, которые не располагают достаточными средствами для комплексного внедрения ERP-систем, обычно начинают внедрение с модулей бухгалтерского учета, учета основных средств и ТМЦ, управления персоналом и расчета заработной платы, управления производством, планирования, логистики и CRM. По мнению многих ИТ-компаний, пороговый оборот для внедрения западной ERP составляет $5 млн., причем даже $1 млн. достаточно, чтобы внедрять определенные гибкие модули ERP. Что касается отечественных ERP, то в среднем начальная стоимость внедрения, включая затраты на ИТ-аудит и консалтинг, составляет $100 тыс. Примеры подобных систем для средних предприятий эксперты называют Microsoft Dinamics AX, Microsoft Dinamics NAV, SAP Business One, Oracle Standard Edition One, «1С:Предприятие» версии 8 и другие.
Итак, по мере развития российской экономики в целом, сектор среднего и малого бизнеса развивается особенно бурно. Это ведет к совершенствованию организационной структуры и бизнес-процессов и к повышению конкуренции. Здесь уже не обойтись без управленческих ИТ-решений. Одним из таких решений является внедрение ERP-систем в практику хозяйствования.
CRM-системы. Работа с клиентом и заказчиком
Gartner Group, система управления отношениями с клиентами — это бизнес-стратегия, предназначенная для оптимизации доходов, прибыльности и удовлетворенности клиентов.
Согласно данным исследования российского рынка систем класса CRM от DSS Consulting http://www.dssconsulting.ru/ (июль 2007 года), в настоящее время более 50% внедрений CRM-решений приходится на банки, страховые и инвестиционные компании.
Рис. 9. Самые популярные CRM-системы в российском банковском секторе (2007)
Современный этап развития систем управления соответствует становлению концепции маркетинга взаимодействия, то есть построения сети «поставщик — потребитель» на основе использования информации о спросе.
Рис. 10. Сквозная цепочка «поставщик — потребитель» в CRM
Построение цепочек «поставщик-потребитель» является основой для создания сетей и организации электронного бизнеса, который существенно трансформировал структуру предприятия и повлиял на автоматизацию бизнес-процессов таких подразделений, как маркетинг, производство, снабжение и сбыт, НИОКР, финансы, кадры, а также на услуги коллективного использования, к которым относятся юридические, рекрутинговые, аудит и др.
Задачи и возможности CRM-систем
CRM-системы имеют следующие целевые задачи:
Типичные возможности CRM-систем:
Базовые функции: квотирование продаж; заказы на продажу (счета-фактуры); график продаж потребителям; конфигурирование продуктов; анализ продаж; управления ресурсами распределения. Например, одним из ключевых факторов, определяющих качество обслуживания, является способность компании быстро и точно отвечать на запросы клиентов (например, о цене и сроках доставки товаров).
Развитие концепции CRM
Развитие концепции CRM привело к появлению целого ряда других концепций и направлений, таких как:
Примеры ИТ-продуктов CRM-систем для СМБ корпораций — это АСТ, GoldMine, Maximaizer, Sales Expert, КонСи-Маркетинг, Clientele, Onyx, SalesLogix. Для крупных — Oracle, SAP, Siebel, BAAN, «“Управление деловыми процессами” Парус-Клиент» и др.
Интеграция CRM и других маркетинговых инструментов
Итак, ядром CRM-систем является функционал по управлению бизнес-процессами компании, который и определяет способность CRM-системы повышать эффективность бизнеса — за счет сокращения временных затрат на выполнение сотрудниками рутинных функций и обучение нового персонала, за счет внедрения «лучших практик» и организации работы согласно регламентам компании. Еще одной «фишкой» функционала CRM-системы являются модули, обеспечивающие качественную и быструю работу по обслуживанию клиентов — Call Centre (Центры Обработки Вызовов — ЦОВ или колл-центры) и Service Desk (Служба Сервиса). Именно этот инструментарий помогает компаниям, имеющим большое количество клиентов, управлять инцидентами и проблемами, обрабатывать запросы, вовремя обслуживать клиентов и даже учитывать затраты на обслуживание. Особую популярность снискал такой инструмент в управлении продажами, как модуль для контроля над работой менеджеров по продажам — воронка продаж (Sales Pipeline). Для директоров по продажам существует также CRM-функционал (OLAP-инструментарий), позволяющий в режиме реального времени получать разнообразные аналитические срезы (продажи по территориям, по менеджерам, по продуктам по сравнению с предыдущими периодами и т.д.). В Gartner считают, что в 2009 году мировые компании начнут замену существующих CRM-решений на системы с улучшенной аналитикой и автоматизацией маркетинговой деятельности.
Таким образом, сегодня мы имеем устойчивую тенденцию, когда три ключевых источника информации для маркетинга, продаж и рекламы — маркетинговые исследования, CRM и данные бизнес-разведки — интегрируются в единую систему, построенную на модернизированном сборе данных, их анализе и доставке. Такая система составляет основу процессов более высокого уровня: генерирования идей, принятия решений и управления знаниями.
Если прибегнуть к аналогии с полноценно функционирующим человеческим телом, то цифровая нервная система (CRM) будет состоять из мозга (маркетинговых исследований) и сенсорной системы (средств бизнес-разведки) и специализированных компонентов, каждый из которых по-своему уникален, являясь при этом частью сложной взаимосвязанной структуры. Сетевые коммуникации, аналитика, интегрирующие элементы программных приложений, сбор данных и результаты маркетинговых исследований будут находиться в полной гармонии. Чтобы понять потребителя и завоевать его нужно стремиться к концептуальным и функциональным изменениям, результатом которых должно стать объединение компаний, специализирующихся на CRM, маркетинговых исследованиях и бизнес-разведке.
Понятие «Business Intelligence» (BI) было введено Говардом Дреснером (Howard Dresner), ныне вице-президентом Gartner Group, в 1989 году для обозначения набора концепций и методик повышения эффективности принятия бизнес-решений при помощи фактографических информационных систем. Корректным переводом термина «Business Intelligence» является «деловая разведка» («бизнес-разведка», «экономическая разведка», «конкурентная разведка»), что не имеет непосредственного отношения к информационным технологиям. BI-системы появились в начале 90-х годов XX века. Основной причиной их появления было широкое внедрение корпоративных информационных систем, оперативно собиравших значительные объемы данных. Информации набиралось так много, что аналитики могли обработать лишь малую ее долю (согласно исследованию IBM, ~ 2-4%).
Таким образом, неэффективно использовались как информационные системы, так и информационные ресурсы: организации просто не могли использовать собственные уже накопленные данные. С 2004 года появилась еще одна причина, определившая развитие рынка BI-систем, — законодательная (в первую очередь для предприятий, осуществляющих деятельность в США). После нескольких скандалов, связанных с финансовыми злоупотреблениями руководства ряда корпораций (Enron, Adelphia, Global Crossing, Tyco и WorldCom) 15 ноября 2004 был окончательно введен в действие принятый в 2002 году закон Сарбанеса-Оксли (SOX), призванный обеспечить «этичное поведение» компаний за счет соблюдения ими «высоких стандартов раскрытия информации». В частности, вводится требование, чтобы финансовые отчеты содержали сведения обо «всех транзакциях, договоренностях, обязательствах и других взаимоотношениях», относящихся к бизнес-процессам компании. В этой ситуации, разумеется, резко возрос интерес компаний к компонентам BI-систем, обеспечивающим оперативный сбор данных о протекании бизнес-процессов, и компонентам, осуществляющим сложный анализ данных. Таким образом, сегодняшние BI-системы – это, с одной стороны, средство превращения накопленной информации в капитал, а с другой — реализация системы по мониторингу за менеджментом в корпорациях.
Бизнес-анализ и управление бизнесом
Для нашей СМБ-корпорации важно, чтобы информация поступала к сотрудникам в удобном для работы формате. Сегодня, как никогда прежде, необходимо уметь разбираться в огромных объемах быстро меняющихся данных, наглядно представлять информацию и систематизировать ее по степени приоритетности, а также эффективно оценивать деятельность на основе конкретных показателей. Компании, которые хотят идти в ногу со временем, должны сделать все возможное, чтобы их сотрудники всегда имели доступ к нужной информации и получали ее в нужное время и в нужном формате.
Рис. 11. Цикл управления бизнесом
Средства бизнес-анализа обеспечивают более глубокое понимание рынка, потребностей клиентов и особенностей деятельности компании. Эти средства помогают оперативно реагировать на быстро меняющиеся рыночные условия, лучше прогнозировать и использовать новые возможности, а также безошибочно выделять наиболее важные данные, контролировать бизнес-показатели, проверяя их на соответствие корпоративной стратегии.
Рис. 12. Управление бизнесом и цикл бизнес-анализа
BI-системы – это, с одной стороны, средство превращения накопленной информации в капитал, а с другой — реализация системы по мониторингу за менеджментом в корпорациях.
Структура BI-системы
На современном этапе концепция BI представляет собой отдельное направление деятельности, заключающееся в сборе данных, касающихся экономической безопасности предприятия, их анализе (статистическом и логическом) и обеспечении руководителей высшего звена важной информацией, позволяющей значительно снижать риски при принятии важных для развития бизнеса решений. Сбор и анализ данных в рамках деловой разведки охватывают макроэкономические процессы и правовое поле (для отслеживания общего состояния внешней среды), внешние микроэкономические процессы (для отслеживания состояния целевых рынков и конкурентов) и внутрифирменные процессы (для отслеживания внутреннего состояния). Типичная BI-система содержит следующие модули:
Рис. 13. Структура типичной BI-системы
BI-системы – это, с одной стороны, средство превращения накопленной информации в капитал, а с другой — реализация системы по мониторингу за менеджментом в корпорациях.
Подсистемы работы с данными.
Средства организации хранилищ и витрин данных обеспечивают BI-систему специализированными массивами данных (извлекаемых из корпоративной информационной системы), структурированными оптимальным для последующего анализа образом.
Рис. 14. Функционирование BI-системы
Как правило, здесь хранятся ретроспективные или тактические данные о бизнес-процессах. Витрины данных отличаются от хранилищ лишь тем, что содержат подмножества данных, относящихся к отдельной теме или виду деятельности. Операционную БД вместе с внешними информационными источниками следует рассматривать как «сырье» для создания предметно-ориентированных, интегрированных, неизменяемых по структуре хронологических данных (ХД), анализируемых в системах поддержки принятия решений. Витрина (киоск) — подмножество ХД, обеспечивает необходимую производительность получения и анализа данных для конечных пользователей и защиту от несанкционированного доступа. Хранилище данных — это предметно-ориентированный, интегрированный, неизменяемый, поддерживающий хронологию набор данных, организованный для целей поддержки принятия решений.
Как правило, ХД ориентированы на решение определенных задач анализа и представления данных.
Подсистема извлечения, преобразования и записи данных обеспечивает интеграцию данных между системами обработки транзакций (On-Line Transaction Processing — OLTP) и многомерными хранилищами аналитических данных. Средства ETL предназначены для осуществления доступа к базам данных разных форматов или взаимодействия с приложениями, очистки получаемых данных, их консолидации и конвертации в формат, принятый для хранилища данных и записи их туда.
Подсистема мониторинга деловой активности в реальном времени отслеживает происходящие на предприятии бизнес-процессы, получая данные от системы обработки транзакций. Данные мониторинга фиксируются в хранилище, параллельно подвергаясь всем видам анализа и контролю состояния. В случае срабатывания заложенных в систему условий (например, выхода бизнес-процесса за штатные рамки) подсистема автоматически уведомляет ответственное за процесс лицо о возникновении проблемной ситуации и предоставляет подробные данные для принятия оптимального решения.
Подсистема генерации запросов и отчетов предназначена для оперативной выборки из хранилища требуемых пользователю данных за указанный период. Подобные выборки обеспечивают возможность ретроспективного анализа бизнес-процессов в нужном разрезе с заданной степенью детализации. Сюда же входят и средства обработки произвольных запросов (Ad-hoc query), реализующие выборку данных более гибко, в соответствии с нестандартными потребностями поиска данных. Сильными сторонами систем Query & Reporting по сравнению с OLAP-системами являются высокая гибкость при создании сложных по формату отчетов и возможность получения информации пользователями в режиме реального времени.
BI-системы – это, с одной стороны, средство превращения накопленной информации в капитал, а с другой — реализация системы по мониторингу за менеджментом в корпорациях.
Средства анализа данных. Этапы цикла DM (Data Mining)
Средства анализа данных BI-системы делятся на две основные категории:
Средствами OLAP в реальном времени осуществляется многомерный анализ данных по выбранным пользователем показателям и измерениям и сводятся в кросс-таблицы результаты — с возможностью раскрытия деталей по каждому показателю, формирующие так называемые «OLAP-кубы».
Средства DM и KDD предназначены для обнаружения связей и корреляций между хранимыми данными, а также выявления трендов. Для этого используются сложные механизмы шаблонов, статистические и специальные математические.
Методы интеллектуального анализа данных помогают найти и наглядно представить скрытые связи между объектами и процессами, связанными с экономической деятельностью компании, и строить математические и имитационные модели бизнес-процессов, позволяющие прогнозировать последствия принятия тех или иных решений или наступления определенных событий. Обычно цикл DM состоит из пяти этапов.
Рис. 15. Цикл использования технологии DM
Этап 1. Постановка бизнес-задачи. На первом этапе формулируются конкретные бизнес-задачи. При первом прохождении этого цикла задача может быть поставлена довольно широко: например, построить профили высоко прибыльных клиентов или определить группы нелояльных покупателей. Во время дальнейших проходов поставленные задачи можно уточнять, расширять и углублять. При формулировании задачи учитывается наличие данных, необходимых для ее решения.
Этап 2. Первичное исследование данных. После того как бизнес-задача сформулирована, начинается этап предварительного исследования данных, необходимых для решения поставленной задачи.
Этап 3. Подготовка данных. Третий этап — подготовка данных для их дальнейшего анализа.
Этап 4. Анализ данных. Основной этап — четвертый — непосредственно анализ данных. Это полностью технический процесс, и спектр применяемых алгоритмов очень широк: от методов нечеткой кластеризации и деревьев решений до нейронных сетей и методов извлечения нечетких лингвистических правил.
Этап 5. Интерпретация результатов. На последнем этапе цикла идет процесс интерпретации полученных знаний. Найденные знания представляются в удобной и понятной форме, определяется их значимость для бизнеса.
Задачи анализа данных
Как правило, пользователям при проведении бизнес-разведки и анализа данных нужно решить три задачи.
Рис. 16. Концептуальная структура системы BI и Data Mining по бизнес-разведке
Первая задача частично решается путем интеграции неструктурированных источников при помощи корпоративных поисковых систем (corporate search).
«Поисковик» может проиндексировать документы и централизовать доступ к ним. В данном случае корпоративная поисковая система — точка входа для всех источников. Пользователь всегда может получить указание, где находится тот или иной материал. Вторую задачу можно решить при помощи инструментов извлечения данных (information extraction). Это системы классификации и рубрикации, инструменты дополнительной обработки данных, позволяющие единообразно представлять форматы дат, денег, адресов и т.п. Так называемые «экстракторы» могут использоваться отдельно или входить в состав платформенного решения. Что касается инструмента анализа (третья задача), такие средства, как BI и Data Mining, позволяют не только применять стандартные средства анализа, но и интегрировать информацию из структурированных и неструктурированных источников. Трехкомпонентное решение (средство поиска — средство добычи информации — средство анализа) является оптимальной технологической связкой для решения задач по бизнес разведке.
BI-системы – это, с одной стороны, средство превращения накопленной информации в капитал, а с другой — реализация системы по мониторингу за менеджментом в корпорациях.
Возможности бизнес-анализа
Итак, благодаря средствам бизнес-анализа СМБ-корпорация сможет:
Использование систем электронного документооборота (СЭД)
Инфраструктура современного предприятия состоит из множества взаимосвязанных подсистем, которые в совокупности предъявляют многочисленные требования к системе электронного документооборота — СЭД. Именно поэтому использование систем СЭД обусловлено организационной структурой корпорации, направленности и прозрачности информационных потоков, принципов отчетности. Важными факторами СЭД являются требования к отслеживанию всех стадий жизненного цикла документа, маршрута его движения с ведением протоколов действий пользователей и определением прав доступа к документу. Обязательным требованием СЭД является внутренний аудит КИС в целом, анализ совместимости параметров СЭД с приложениями, оборудованием и программным обеспечением, используемым в архитектуре информационной системы предприятия. На рис.18. показано место СЭД в типичной структуре КИС, в которую сегодня входят четыре ключевые подсистемы: управление ИТ-инфраструктурой, ERP-система, управление документами и контентом, бизнес-аналитика.
Рис. 17. Составляющие корпоративной информационной системы
СЭД: Workflow-технология
Ядром любой СЭД является workflow-технология.
Термин WorkFlow характеризует системы, направленные на автоматизацию большого числа бизнес-процессов компании, при этом удельный вес каждого из них невелик. WorkFlow позволяет работать с такими бизнес-процессами, контент которых подвергается постоянным изменениям и дополнениям. Важной особенностью является возможность работы с неструктурированными данными. Workflow-технологии (технологии поддержки потоков заданий) создают при помощи графического редактора произвольные маршрутные схемы, назначают правила перехода этапов бизнес-процессов от одного пользователя к другому через диалоговый интерфейс без программирования и обеспечивают графический или формальный мониторинг прохождения процессов между пользователями с возможностью расхождения, схождения, вложенности, условных переходов. Они дают возможность внесения изменений, позволяющих оптимизировать любой процесс на основе анализа его текущего состояния, и одновременно с этим документировать изменение и автоматизировать новые действия операторов в ходе его выполнения. Например, одним из назначений этих систем является документальная регистрация тех или иных свершившихся действий и событий (например, «документ принят к исполнению», «документ передан на исполнение конкретному сотруднику», «на документ дан соответствующий ответ» и т.д.) в соответствии с принятыми правилами.
Рис. 18. Маршрут движения организационно-распорядительных документов
Инфраструктура современного предприятия состоит из множества взаимосвязанных подсистем, которые в совокупности предъявляют многочисленные требования к системе электронного документооборота — СЭД.
Enterprise Content Management (ECM)
Однако дальнейшее развитие систем типа СЭД связано с концепцией Enterprise Content Management — ECM.
Корпорации накопили действительно много документов, критически важных для бизнеса, существующих только в электронном виде и для работы с ними нужна стройная взаимосвязанная система. До поры поддерживать эту систему удавалось при помощи простых, но разрозненных средств, начиная от папок на файловой системе и электронной почты и заканчивая специализированными приложениями. При этом вся «бизнес-логика» содержалась в головах людей, которые и служили своего рода справочно-поисковой системой для остальных сотрудников, всегда зная, где найти нужный документ или у кого еще спросить. Поэтому ЕСМ — это ответ на вызов момента, когда ни один человек уже не в состоянии удержать в голове все взаимосвязи и всю логику организации информации.
Что такое ECM? Понятие Enterprise Content Management (ECM) объединяет технологии, которые используются для обработки неструктурированной информации, связанной с реализацией тех или иных организационных процессов. В этом определении нужно сделать некоторые уточнения. Под неструктурированной информацией подразумеваются документы, а также более общее понятие — содержание, контент (в частности, это может быть информационное наполнение Интернет-сайтов или других хранилищ данных). В качестве отдельной категории выделяются также «записи» (records) — сведения, которые должны храниться в неизменном виде. В русском языке это иногда переводится как «архивные данные», но, наверное, это не вполне точно, так как архив ассоциируется с чем-то объемным и малоиспользуемым, а записи могут применяться в работе организации постоянно, но в режиме «только для чтения». Хотя ECM традиционно связывают с обработкой неструктурированной информации, в последние годы здесь как отдельное направление активно развивается работа с формами, которые относятся к категории полуструктурированных документов.
Понятие «обработка информации» раскрывается следующим образом:
Отдельно нужно обратить внимание на термин capture (захват, фиксация), поскольку в русском языке нет краткого адекватного его перевода в профессиональном смысле. Речь идет о совокупности различных методов поступления данных из внешних источников в информационную систему предприятия, в числе которых ручной ввод, автоматизированное преобразование данных с бумажных носителей в электронный вид (включая оцифровку), преобразование форматов документов. Проблема обеспечения автоматизированного потокового ввода является очень актуальной.
Рис. 19. Системы управления документами
Стоит отдельно упомянуть еще два важных элемента: создание содержания (в том числе в режиме групповой работы) в ходе реализации бизнес-процессов или проектов, а также управление знаниями (включая и поисковые задачи).
Инфраструктура современного предприятия состоит из множества взаимосвязанных подсистем, которые в совокупности предъявляют многочисленные требования к системе электронного документооборота — СЭД.
Виды систем электронного документооборота
Все системы электронного документооборота (СЭД), представленные на мировом рынке, можно с достаточной степенью условности разделить на две базовые группы: универсальные системы и приложения для автоматизации отдельных бизнес-функций корпораций. Универсальные системы не привязаны к отдельным процессам, а предлагаются, как правило, в качестве базового пакета, который корпорация настраивает под свои специфические нужды. Практически все западные поставщики программного обеспечения ориентированы на разработку таких универсальных пакетов с возможностями расширения функций. Российские поставщики, напротив, продвигают в основной массе продукты для автоматизации специфических бизнес-функций предприятия, к примеру, бухгалтерские приложения. На мировом рынке господствует тенденция к лидерству комплексных универсальных систем, в то время как для российского рынка характерен высокий спрос на продукты, автоматизирующие отдельные функции или процессы с последующей интеграцией их в структуру информационной сети.
Рис. 20. Типовые функции СЭД
Инфраструктура современного предприятия состоит из множества взаимосвязанных подсистем, которые в совокупности предъявляют многочисленные требования к системе электронного документооборота — СЭД.
Компоненты технологии ECM
Таким образом, сегодня технологии ECM направлены на поддержку работы с содержимым самых разных типов и форматов на протяжении всего его жизненного цикла. В то же время многие ECM-пакеты включают совместно работающие приложения, которые можно также продавать и использовать отдельно. Gartner выделяет по целевому назначению следующие их ключевые компоненты:
Решения в области ECM
В Gartner выделяют шесть основных составляющих решений в области ECM.
Gartner считает, что ведущими игроками на этом рынке становятся те производители, которые имеют все шесть выделяемых компонент в виде встроенного в продукт функционала, а также обеспечивают высокий уровень масштабируемости приложений и преобразования контента в транзакционный, деловой и мотивационный контекст.
Рис. 21. Преобразование контента в транзакционный, деловой и мотивационный контекст
Инфраструктура современного предприятия состоит из множества взаимосвязанных подсистем, которые в совокупности предъявляют многочисленные требования к системе электронного документооборота — СЭД.
Классификация систем электронного документооборота
Имеет смысл познакомиться и с российскими исследованиями рынка СЭД.
Так, в рамках своего исследования «РБК Консалтинг» http://www.rbc.ru/ предлагает классификацию систем в зависимости от специфических задач, определенной «ниши», занимаемой в рамках общей системы документооборота. Особенности таких продуктов обусловлены специфическими концепциями и моделями бизнес-процессов предприятия, что является основой для автоматизации различных областей делопроизводства. В этом случае эксперты выделяют такие группы решений, как системы WorkFlow, системы делопроизводства, электронные архивы документов, системы коллективной обработки документов и комплексные системы управления документами.
Системы делопроизводства — это автоматизированные системы построения и контроля над выполнением потоков документов в соответствии с представлением заданной логики делопроизводства в программном обеспечении.
То есть речь идет о создании документов в потоковом режиме с возможностью слабой или жесткой маршрутизации и отслеживанием жизненного цикла каждого документа. Системы делопроизводства, как правило, ориентированы на отдельные сферы применения: финансы, производство, управление продажами, хотя отчетность, генерируемая ими, пронизывает всю организационную структуру предприятия.
Электронные архивы представляют собой интегрированные в информационную систему предприятия систематизированные каталоги корпоративных документов. Перевод бумажных версий в электронные осуществляется посредством разных методов, в том числе, методов поточного сканирования. Электронные архивы состоят непосредственно из хранилища документов, программного обеспечения, обеспечивающего взаимодействие архива с элементами информационной системы: серверами, приложениями (модульная система), а также верхней надстройки — программного обеспечения, управляющего хранилищем и модульной системой ввода-вывода.
Системы коллективной обработки документов предполагают поддержку совместной работы с документом, включая разработку маршрутов движения документа и описание сценария движения, определения круга лиц, причастных к работе с документом, установку уровня их прав и полномочий.
Комплексные системы синтезируют функции отдельных приложений и подсистем и подразумевают комплексную автоматизацию бизнес-процессов предприятия.
Существует также еще одна классификация от компании IDC http://www.idc.com/russia, которая предлагает расширенную классификацию СЭД, выделяя в этом секторе системы, ориентированные на автоматизацию бизнес-процессов (business process EDM), корпоративные СЭД (enterprise-centric EDM), системы управления контентом (Content Management Systems), cистемы управления информацией (Information Management Systems) — или порталы, системы управления изображениями/образами (Imaging Systems) и системы управления потоками работ (WorkFlow Management Systems).
Число средств связи, окружающих менеджера, сегодня просто огромное: электронная и голосовая почты, сотовый телефон (иногда несколько), офисный стационарный, Wi-Fi–трубка, а также множество служб мгновенных сообщений (IM-приложений – Instant Messenger), начиная от ICQ и заканчивая Skype. Кроме того, объединение офисов компаний в единую защищенную корпоративную сеть (VPN ? virtual private network) становится все более востребованной услугой на российском рынке. Как правило, телекоммуникационные потребности СМБ-компаний с лихвой покрываются локальными сетями (проводными или беспроводными). Однако с ростом масштабов бизнеса и появлением удаленных офисов и филиалов такие сети необходимо объединять — на предприятии вне зависимости от его структуры должно быть единое инфокоммуникационное пространство, в котором «живут» все виды трафика. VPN, построенные на базе серьезной операторской инфраструктуры, имеют, по сравнению с публичной сетью Интернет, ряд принципиальных отличий. Это и закрытость таких сетей для внешнего мира, и их качество, гарантирующее сохранность информации и высокую скорость ее доставки. VPN обеспечивают надежное функционирование «тяжелых» ERP-систем, CRM-систем, бухгалтерских, логистических программ и пр. Последний фактор очень важен — ведь для территориально разбросанных офисов критично создание единого офисного пространства вне зависимости от расстояний и объема трафика. Действительно, внедрение на предприятиях систем автоматизации — ERP, комплексов САПР, систем документооборота, кадровой службы и т. д. — резко повышает требования к сети передачи данных.
Какие существуют решения для корпораций типа СМБ? Остановимся на наиболее популярных. Прежде всего, это беспроводной доступ.
Беспроводные информационные технологии.
Современные беспроводные технологии условно можно разбить на несколько типов. Наиболее «короткодействующие», предназначенные для связи оборудования в пределах рабочего места, например, связи сотового телефона и ноутбука или компьютера и принтера, относят к классу персональных беспроводных сетей (Wireless Personal Area Network, WPAN). Наиболее распространенная технология из этой категории — Bluetooth. Она имеет типичный радиус действия до 10 м (в отдельных случаях — до 100 м). Очевидно, что такие сети обслуживаются самим пользователем или системным (сетевым) администратором без привлечения телекоммуникационного оператора.
Следующим по «дальнобойности» типом беспроводных коммуникаций являются беспроводные локальные сети (Wireless Local Area Network, WLAN), которые также называют Wi-Fi сетями — по названию ассоциации (форума) производителей соответствующего этой технологии оборудования — Wireless Fidelity. Основное предназначение таких систем, отвечающих стандартам семейства 802.11, — развертывание беспроводных сетей внутри помещений, хотя не исключено их использование на ограниченных открытых площадках, например, университетских кампусных территориях. Также набирают популярность публичные беспроводные сети в гостиницах, аэропортах, кафе, ресторанах, выставочных залах — хот-споты. Типичный радиус действия таких систем — около 100 метров, а их базовая услуга — это доступ в Интернет или корпоративную сеть (в последнее время еще и офисная телефония или альтернативная сотовой IP-телефония по беспроводным сетям).
Для построения распределенных беспроводных операторских сетей масштаба города (Wireless Metropolitan Area Networks, WMAN) или крупных корпоративных сетей используют оборудование, относящееся к классу фиксированного широкополосного беспроводного доступа (Fixed Broadband Wireless Access, FBWA). Характерный радиус действия базовой станции — до 10 км. К этой категории относится новое оборудование семейства стандартов 802.16, которое сертифицирует WiMAX форум. Кроме того, в проекте находится стандарт 802.20, предназначенный для построения беспроводных сетей масштаба региона (Wireless Wide Area Networks, WWAN), «дальнобойность» которых составляет до 50 км. Сети FBWA зачастую являются единственным экономически оправданным решением — либо когда кабельная инфраструктура отсутствует или она низкого качества, либо подключение по проводному каналу слишком дорого, и его прокладка занимает слишком много времени. Основное предназначение таких сетей — организация широкополосного радиоканала, беспроводной «последней» мили до конечного пользователя. Базовые услуги — высокоскоростной доступ в Интернет и телефония.
В отдельную категорию беспроводных коммуникаций попадают технологии сотовой связи, которые ориентированы не столько на телефонию, сколько на Интернет и передачу данных (GPRS/EDGE, 3G, HSDPA, CDMA2000 в диапазоне 450 МГц, а в крупных мегаполисах — сети CDMA 1х EV-DO в более высокочастотных диапазонах 1,9 или 2,1 ГГц. и др.). Кроме того, появилась мобильная версия WiMAX (стандарт 802.16е), которая может составить достойную конкуренцию (и, может быть, станет хорошим дополнением) указанным выше сотовым технологиям.
Разнообразие технологий беспроводной связи продолжает увеличиваться, в то время как число базовых сервисов довольно ограничено. Телефония, доступ в Интернет, построение сетей — вот, пожалуй, и все. Другими словами, одну и ту же услугу потребитель может теоретически получать с помощью различных радиотехнологий (и разных провайдеров), доступных ему в данный момент времени в данной точке пространства за определенный тариф. Например, доступ в Интернет можно получить не только при помощи технологий сотовой связи (GPRS/EDGE или CDMA 2000), но из хот-спотов (Wi-Fi) и WiMax-сетей, а в скором будущем — и при помощи мобильного WiMax, интегрированного в портативные устройства.
Число средств связи, окружающих менеджера, сегодня просто огромное: электронная и голосовая почты, сотовый телефон (иногда несколько), офисный стационарный, Wi-Fi–трубка, а также множество служб мгновенных сообщений (IM-приложений – Instant Messenger), начиная от ICQ и заканчивая Skype. Кроме того, объединение офисов компаний в единую защищенную корпоративную сеть (VPN ? virtual private network) становится все более востребованной услугой на российском рынке. Как правило, телекоммуникационные потребности СМБ-компаний с лихвой покрываются локальными сетями (проводными или беспроводными). Однако с ростом масштабов бизнеса и появлением удаленных офисов и филиалов такие сети необходимо объединять — на предприятии вне зависимости от его структуры должно быть единое инфокоммуникационное пространство, в котором «живут» все виды трафика. VPN, построенные на базе серьезной операторской инфраструктуры, имеют, по сравнению с публичной сетью Интернет, ряд принципиальных отличий. Это и закрытость таких сетей для внешнего мира, и их качество, гарантирующее сохранность информации и высокую скорость ее доставки. VPN обеспечивают надежное функционирование «тяжелых» ERP-систем, CRM-систем, бухгалтерских, логистических программ и пр. Последний фактор очень важен — ведь для территориально разбросанных офисов критично создание единого офисного пространства вне зависимости от расстояний и объема трафика. Действительно, внедрение на предприятиях систем автоматизации — ERP, комплексов САПР, систем документооборота, кадровой службы и т. д. — резко повышает требования к сети передачи данных.
Какие существуют решения для корпораций типа СМБ? Остановимся на наиболее популярных. Прежде всего, это беспроводной доступ.
IP-телефония, унифицированные коммуникации, ADSL
Ежемесячные затраты российских компаний на телекоммуникации, по некоторым оценкам, составляют примерно от $300-500 у мелких фирм, до $1000 и более – у средних. Самыми популярными остаются услуги телефонной связи и доступ в Интернет. Последний уже сейчас позволяет отказаться от классической телефонии — оплота традиционных операторов, которые сами активно развивают Интернет-доступ. Растет популярность альтернативных способов голосовой связи через широкополосный Интернет-канал (Skype, Google Talk, Sipnet и др.), активно развивается IP-телефония.
Основные преимущества использования IP-телефонии хорошо известны. Это экономия на междугородных и международных телефонных разговорах за счет передачи голосового трафика через глобальные сети передачи данных, сокращение затрат на администрирование (вместо двух сетей — телефонной и передачи данных — у потребителя остается лишь одна), богатый функционал и пр. Вместе с тем у VoIP есть и существенные недостатки, например, недостаточно проработанные вопросы информационной безопасности (ИБ).
Одним из самых современных технологических прорывов в области связи является концепция унифицированных коммуникаций (Unified Communications). По сути, это новый этап эволюции IP-коммуникаций. Модель унифицированных коммуникаций использует такие технологии, как протокол инициирования сессий (Session Initiation Protocol, SIP) и контроль присутствия, а также мобильные решения, позволяющие упростить и унифицировать все формы коммуникаций независимо от места, времени и устройства. Пользователи могут связываться друг с другом в любой момент времени с учетом своих предпочтений, причем эта связь может осуществляться по любым каналам и не зависит от используемых устройств доступа. Модель унифицированных коммуникаций объединяет различные устройства связи и сети (фиксированные, Интернет, кабельные, спутниковые, мобильные).
Наиболее распространенным способом скоростного доступа в глобальную сеть — как для частных пользователей, так и для небольших компаний — остается технология ADSL. Теоретически она позволяет получать доступ по стандартной медной телефонной линии, которая не будет при этом занята, на скорости к абоненту до 7,5 Мбит/c (от абонента — до 1,5 Мбит/с). На практике скорости, которые зависят от множества факторов (расстояние до узла оператора, состояние кабеля, различные наводки, загрузка каналов и пр.) могут оказаться существенно ниже. Поэтому гарантированную скорость обещают лишь немногие российские провайдеры, большинство же, как правило, указывает ее верхний предел.
Число средств связи, окружающих менеджера, сегодня просто огромное: электронная и голосовая почты, сотовый телефон (иногда несколько), офисный стационарный, Wi-Fi–трубка, а также множество служб мгновенных сообщений (IM-приложений – Instant Messenger), начиная от ICQ и заканчивая Skype. Кроме того, объединение офисов компаний в единую защищенную корпоративную сеть (VPN ? virtual private network) становится все более востребованной услугой на российском рынке. Как правило, телекоммуникационные потребности СМБ-компаний с лихвой покрываются локальными сетями (проводными или беспроводными). Однако с ростом масштабов бизнеса и появлением удаленных офисов и филиалов такие сети необходимо объединять — на предприятии вне зависимости от его структуры должно быть единое инфокоммуникационное пространство, в котором «живут» все виды трафика. VPN, построенные на базе серьезной операторской инфраструктуры, имеют, по сравнению с публичной сетью Интернет, ряд принципиальных отличий. Это и закрытость таких сетей для внешнего мира, и их качество, гарантирующее сохранность информации и высокую скорость ее доставки. VPN обеспечивают надежное функционирование «тяжелых» ERP-систем, CRM-систем, бухгалтерских, логистических программ и пр. Последний фактор очень важен — ведь для территориально разбросанных офисов критично создание единого офисного пространства вне зависимости от расстояний и объема трафика. Действительно, внедрение на предприятиях систем автоматизации — ERP, комплексов САПР, систем документооборота, кадровой службы и т. д. — резко повышает требования к сети передачи данных.
Какие существуют решения для корпораций типа СМБ? Остановимся на наиболее популярных. Прежде всего, это беспроводной доступ.
Понятие call-центра
Сейчас в стране прослеживается рост интереса к услугам call-центров, так как они позволяют компаниям, с одной стороны, быть максимально клиентоориентированными, а с другой — сосредотачиваться на профильной деятельности, используя поддержку профессиональных операторских центров. Наибольшей популярностью пользуются услуги, входящие в общий блок «Обработка входящих вызовов» («Виртуальный офис», «Служба поддержки», «Служба приема заказов», «Единая справочная», «Линия информационной поддержки клиентов»), а также услуги исходящего телемаркетинга и аренды рабочих мест.
Что же такое call-центр (ЦОВ)? К сожалению, в настоящее время не существует четкого определения call-центра. Call-центр, контакт-центр или центр обслуживания вызовов (ЦОВ) используют приблизительно одинаковые принципы удовлетворения информационных потребностей клиента в реальном (или почти реальном) времени.
ЦОВ —– это совокупность аппаратных и программных средств и алгоритмов, предназначенных для регистрации заявок пользователей (поступающих по телефону или с помощью других средств связи — радио, пейджинговая связь и т.д.), их маршрутизации, контроля решения задач и выдачи результирующей информации пользователю.
На самом деле ЦОВ состоит не только из «железа» и «софта» — технических средств для интеллектуальной маршрутизации входящих вызовов, — но и из сотрудников: операторов и менеджеров. Поэтому другими словами ЦОВ — это прием и обработка звонков\заявок по заданному сценарию с последующим предоставлением данных для дальнейшей обработки (от статистики по приему звонков\заявок, качества их обработки до продажи услуг или товаров по телефону).
Задачи, которые позволяют решать ЦОВ
Преимущества call-центра
Помимо решения перечисленных выше основных («классических») задач call-центр обладает рядом дополнительных преимуществ:
Хороший операторский центр помимо обработки входящих вызовов способен обслуживать исходящие. Такая необходимость возникает при проведении социологических и иных опросов населения или, например, для организации поддержки прямых продаж и обслуживания постоянных клиентов.
Рис. 22. Схема программно-технического оснащения call-центра
Число средств связи, окружающих менеджера, сегодня просто огромное: электронная и голосовая почты, сотовый телефон (иногда несколько), офисный стационарный, Wi-Fi–трубка, а также множество служб мгновенных сообщений (IM-приложений – Instant Messenger), начиная от ICQ и заканчивая Skype. Кроме того, объединение офисов компаний в единую защищенную корпоративную сеть (VPN ? virtual private network) становится все более востребованной услугой на российском рынке. Как правило, телекоммуникационные потребности СМБ-компаний с лихвой покрываются локальными сетями (проводными или беспроводными). Однако с ростом масштабов бизнеса и появлением удаленных офисов и филиалов такие сети необходимо объединять — на предприятии вне зависимости от его структуры должно быть единое инфокоммуникационное пространство, в котором «живут» все виды трафика. VPN, построенные на базе серьезной операторской инфраструктуры, имеют, по сравнению с публичной сетью Интернет, ряд принципиальных отличий. Это и закрытость таких сетей для внешнего мира, и их качество, гарантирующее сохранность информации и высокую скорость ее доставки. VPN обеспечивают надежное функционирование «тяжелых» ERP-систем, CRM-систем, бухгалтерских, логистических программ и пр. Последний фактор очень важен — ведь для территориально разбросанных офисов критично создание единого офисного пространства вне зависимости от расстояний и объема трафика. Действительно, внедрение на предприятиях систем автоматизации — ERP, комплексов САПР, систем документооборота, кадровой службы и т. д. — резко повышает требования к сети передачи данных.
Какие существуют решения для корпораций типа СМБ? Остановимся на наиболее популярных. Прежде всего, это беспроводной доступ.
Электронные платежные системы.
Другим фактором, влияющим на продвижение ИТ-продуктов и решений в банковской сфере, является идея введения электронных денег, которая появилась в 1880 году, когда американский ученый Эдвард Беллами предложил использовать предоплаченные карточки. Но из-за технологической невозможности ее пришлось отложить на долгие годы. Сегодня в мире существуют значительное количество электронных платежных систем, наиболее значимые из них ? PayPal, Neteller, E-Gold, StormPay, PayAce и многие другие. В России заметны Cyberplat, E-port, WebMoney, «Яндекс.Деньги» и другие. Исторически первой российской электронной платежной системой является CyberPlat, которая была образована в 1997 году.
В широком смысле электронная платежная система (ЭПС) ? это технология (если говорить о реализации, то сервис), представляющая собой совокупность методов, договоренностей и программных решений, позволяющая производить расчеты между контрагентами по сетям передачи данных (в основном через Интернет).
В мире существует несколько видов ЭПС, которые очень условно можно классифицировать по трем основным типам: 1) карточные системы; 2) операторы цифровой наличности; 3) платежные шлюзы.
К первым относятся ЭПС, работающие с обычными банковскими картами (Visa, MasterCard и т. д.). Системы второго типа оперируют с так называемой цифровой наличностью ? некой внутренней валютой, которую можно обналичить у соответствующих участников ЭПС. Платежные шлюзы представляют собой синергию карточных систем и операторов цифровой наличности, предоставляя широкие возможности для взаимной конвертации и способов оплаты товаров и услуг в Интернет.
Таблица 3. Крупнейшие электронные платежные системы России
№ |
Платежные системы |
Год основания |
Тип |
Оборот, $ млн. |
Рост, % |
|
2005 год |
2004 год |
|||||
1 |
Киберплат |
1997 |
Платежный шлюз/карточная система/Интернет-банк |
1120 |
459 |
144 |
2 |
ОСМП |
2004 |
Платежный шлюз |
498 |
83 |
500 |
3 |
Платежные системы |
2005 |
Платежный шлюз |
28,5 |
- |
- |
4 |
Элекснет |
2000 |
Система по приему платежей |
346,6 |
190,9 |
82 |
5 |
«Яндекс.Деньги» |
2002 |
Система цифровой наличности |
125 |
н/д |
- |
6 |
Assist |
1998 |
Платежный шлюз/карточная система |
40 |
25 |
60 |
7 |
Chronopay |
2003 |
Карточная система |
7 |
- |
- |
8 |
e-port |
1999 |
Платежный шлюз |
740,5 |
325,2 |
128 |
9 |
Fethard |
2001 |
Платежный шлюз |
100 |
н/д |
- |
10 |
WebMoney |
1998 |
Система цифровой наличности |
647,9 |
338,9 |
91 |
Рассмотрим более детально схему прохождения электронного платежа — основу любой карточной системы.
Рис. 23. Типовая схема реализации банковского платежа
Прием (процессинг — обработка) пластиковых карт в качестве средств оплаты за товары и услуги в Интернет называется Интернет-эквайрингом. Основные участники электронного платежа:
1) покупатель;
2) интернет-магазин;
3) банк-эмитент (выдавший карточку);
4) банк-эквайер (проводит первичную обработку транзакции и обеспечивает весь спектр операций с карточками, реализуемыми партнерами);
5) платежный сервер (электронная платежная система, обеспечивающая безопасность прохождения платежа и многое другое).
Таким образом, карточная электронная платежная система по большому счету является гарантом безопасного транспорта карточных данных к процессинговому центру банка-эквайера.
Российский рынок банковских карт стремительно развивается. По данным Центробанка, среднегодовой прирост количества банковских карт в России за период 2001-2005 годы составил 50,79%.
Идея цифровой наличности, основы которой заложил Давид Чаум в своей технологии eCash, является перспективной, несмотря на некоторую неопределенность юридического статуса самих систем. Строго говоря, даже называть их электронными платежными системами не совсем верно, так как они оперируют виртуальными единицами (так, WebMoney называет себя «системой имущественных прав», а «Яндекс.Деньги» — «предоплаченным финансовым продуктом»). Вместе с тем, данные платежные системы принимают весьма активное (и все более возрастающее) участие в электронной коммерции, а далее и в товарно-денежном обороте страны. При этом их деятельность не регламентируется центральным финансовым институтом страны, и непонятно, как будут развиваться электронные платежные системы этого типа, если соответствующий орган вдруг решится навести порядок. По состоянию на июнь 2006 года, количество магазинов, принимающих к оплате WebMoney, «Яндекс.Деньги» или через систему «КредитПилот» и зарегистрированных на официальных сайтах указанных электронных платежных систем, составляло 2772, 950 и 126 штук соответственно. Для Rupay аналогичный показатель достиг 1109 Интернет-магазинов по состоянию на июнь 2006 года. По официальной информации Assist количество Интернет-магазинов, работающих с системой, по состоянию на июнь 2006 года, превысило 800.
Платежные шлюзы
Третий вид платежных систем — платежные шлюзы — нередко используют предоплаченные карты, обеспечивая при этом еще и возможность перевода между платежными системами, оперирующими с цифровой наличностью. Правовая природа платежных шлюзов является в этом смысле симбиозом карточной и сетевой (цифровая наличности) моделей, требуя учета особенностей функционирования обоих типов. В этих системах клиент не покупает никакой цифровой наличности и не оперирует своим реальным карточным счетом, а получает при регистрации свой собственный счет в системе. Пользователь получает большое количество сервисов, возможных благодаря открытию этого счета, а оператор системы зарабатывает на комиссии при их реализации. Достоинства платежных шлюзов как для покупателя, так и для продавца — большое количество способов оплаты. Из недостатков можно отметить не всегда четкое определение прав и обязанностей сторон. Кроме того, очевидно, что применение нескольких схем расчетов и обмена между принципиально различными платежными системами, существенно повышает риски при использовании платежных шлюзов.
Примитивные типы
Все типы данных разделяются на две группы. Первую составляют 8 простых, или примитивных (от английского primitive), типов данных. Они подразделяются на три подгруппы:
- целочисленные
* byte (8-bit, -128..127)
* short (16-bit, -2^15..(2^15)-1)
* int (32-bit, -2^31..(2^31)-1)
* long (64-bit, -2^63..(2^63)-1)
* char (16-bit, Unicode 0..Unicode 2^16-1)
- дробные
* float (32-bit, IEEE754)
* double (64-bit, IEEE754)
- булево
* boolean (true/false)
Переменные с плавающей точкой могут хранить не только численные значения, но и любой из особо определенных флагов (состояний): отрицательная бесконечность, отрицательный нуль, положительная бесконечность, положительный нуль и "отсутствие числа" (not-a-number, NaN). Поскольку все эти флаги определены в языке Java, вы можете предусматривать в своем коде соответствующие проверки. Как правило, эти особые состояния являются результатом ошибочных действий; например, если 0 поделить на 0, результатом будет NaN, и вы сможете в программе явным образом выяснить это. Таким образом, поддержка языком этих особых состояний существенно облегчает поиск ошибок.
Все символьные константы с плавающей точкой подразумеваются принадлежащими к типу double, если не указано обратное. Чтобы задать 16-битное число с плавающей точкой типа float, вы должны приписать в конец его цифровой записи букву "f". После этого компилятор будет считать эту константу принадлежащей к типу float. Поскольку Java требует точного согласования типов, вы обязательно должны будете прибегнуть к этому приему, чтобы инициализировать переменную типа float.
Например, следующая строка кода приведет к ошибке при компиляции из-за несоответствия типов:
float num = 1.0;
Все константы с плавающей точкой по умолчанию относятся компилятором к типу double, поэтому, чтобы явным образом указать, что данная константа имеет тип float, припишите к цифровой записи этого числа букву "f":
float num = 1.0f;
Преобразование примитивных типов.
Язык Java требует обязательного соответствия типов. Вы должны преобразовывать типы явным образом с помощью механизма приведения типа (type cast), который позволяет указать компилятору, к какому типу следует преобразовать те или иные данные.
В Java приведение типа осуществляется так же, как и в C/C++. Для этого достаточно указать идентификатор требуемого типа в круглых скобках перед приводимым выражением. Если затребованное преобразование типа возможно, оно будет осуществлено, и вы получите значение нужного типа.
Допустим, у нас есть две переменные shortVar и intVar, первая из которых принадлежит к типу short, а вторая - к типу int. Между этими типами существуют два возможных преобразования:
short shortVar = 0;
int intVar = 0;
intVar = shortVar;
shortVar = intVar; // несовместимые типы в операторе присваивания
При компиляции этого кода присваивание значения intVar переменной shortVar приведет к выдаче сообщения об ошибке. Дело в том, что вы пытаетесь тем самым присвоить значение с большим диапазоном переменной с меньшим диапазоном. Такой тип преобразования называется сужающим (narrowing conversion), так как при этом уменьшается количество бит, отведенных для хранения данных. Понятно, что при сужающем преобразовании вы можете потерять часть информации, содержащейся в числе, - либо изменив его значение, либо (в случае числа с плавающей точкой) уменьшив разрядность и тем самым точность представления.
Язык Java заставляет вас расписаться в том, что вы отдаете себе отчет, что происходит при таком преобразовании: при любых сужающих преобразованиях вы должны прибегать к явному приведению типа. Это еще один пример того, как Java пытается почти насильственными методами внедрить хороший стиль программирования. Каждый раз, когда вы преобразуете, к примеру, значение типа long (64 бита) в значение типа int (32 бита), вы должны предусмотреть, что именно должна будет делать программа при возможности потери информации. Если вы абсолютно уверены, что значение приводимого целого типа long будет всегда попадать в диапазон типа int, то смело прибегайте к приведению типа; в противном случае вам нужно будет предусмотреть какие-то дополнительные действия (например, выдачу предупреждения пользователю о возможной потере данных).
Так, чтобы заставить компилироваться приведенный выше фрагмент, нужно добавить в него явное приведение типа. Вот как это будет выглядеть:
short shortVar = 0;
int intVar = 0;
intVar = shortVar;
shortVar = (short) intVar;
Теперь ничто не мешает компьютеру произвести требуемое присваивание. Значение intVar будет при этом преобразовано к типу short, в результате чего старшие биты числа будут отброшены, но знак числа при этом сохранится. В нашем случае это приведет к тому, что 32-битное целое число превратится в 16-битное.
В табл. 4-4 перечислены все преобразования примитивных типов, допустимые в языке Java. Буква "C" в клетке таблицы означает, что для данного преобразования требуется явное приведение к типу, иначе компилятор выдаст сообщение об ошибке. Буква "L" означает, что при данном преобразовании может измениться величина или уменьшиться разрядность числа из-за частичной потери информации. Буква "X" обозначает, что данное преобразование типов в Java запрещено.
Таблица 4-4. Преобразование примитивных типов
Исх Тип, к которому происходит преобразование
^ byte short int long float double char Boolean
byte C X
short C, L C X
int C, L C, L C, L X
long C, L C, L C, L C, L C C, L X
float C, L C, L C, L C, L C, L X
double C, L C, L C, L C, L C, L C, L X
char C, L C C C C C X
boolean X X X X X X X
Буква "C" означает, что для данного преобразования требуется явное приведение типа; буква "L" означает, что при данном преобразовании может измениться величина или уменьшиться разрядность числа из-за частичной потери информации; буква "X" обозначает, что данное преобразование типов в Java запрещено.
Преобразование значений с плавающей точкой в целочисленные значения
При преобразовании значения с плавающей точкой в любой из целых типов вы теряете информацию о дробной части числа. Java отсекает дробную часть, округляя таким образом число в направлении к нулю. После этого получающееся в результате целое число будет преобразовано к требуемому целому типу путем увеличения или уменьшения количества бит.
Преобразование числа с плавающей точкой двойной разрядности к обычной разрядности
Преобразование числа типа double к числу типа float происходит в соответствии с требованиями режима "округления к ближайшему" (round-to-nearest) стандарта IEEE 754. Если преобразуемое значение слишком велико и выходит за пределы диапазона типа float, результатом преобразования будет положительная или отрицательная бесконечность. Преобразование значения NaN дает в результате также NaN.
Преобразования типа boolean
Тип boolean не допускает преобразования в какой-либо другой тип, так же как и никакой из типов Java не может быть преобразован к булевскому типу. Если вам требуется преобразовать целое значение в булевское или булевское в строковое, вы можете присвоить нужное значение вручную с помощью, например, такого фрагмента кода:
boolean bool;
int i = 15;
String st = null;
if (i == 0) bool = false; else bool = true;
if (bool) st = "true"; else st = "false";
Объявление переменных
Переменная в Java может принадлежать к примитивному типу либо быть объектом или интерфейсом. Создавать новые переменные можно в любом месте программы. За оператором объявления новой переменной может следовать оператор инициализации, с помощью которого созданной переменной присваивается начальное значение.
Мы уже не раз создавали новые переменные в рассматриваемых примерах. Вот еще несколько примеров объявления и инициализации переменных в Java:
int i = 42;
String st = "Hello World";
float pi = 3.14f;
boolean cont;
Любое объявление переменной имеет свою область действия, границы которой зависят от того, где именно расположено это объявление. Всякий раз, когда вы помещаете фрагмент кода в пару фигурных скобок { }, вы тем самым вводите новый уровень группирования. Границы этого нового уровня определяют, где созданные в нем переменные будут доступны, а где станет возможным их уничтожение. Переменная доступна только в том случае, если она определена на текущем уровне группирования или на одном из вышестоящих уровней.
Когда текущий уровень завершается, все объявленные в нем переменные становятся недоступными. Однако это не значит, что они обязательно уничтожаются, так как правила уничтожения переменных более сложны и учитывают дополнительные обстоятельства. При объявлении переменной, как вы уже знаете, для нее выделяется участок памяти. Когда текущий блок, в котором эта переменная была объявлена, заканчивается, она становится доступной для уничтожения, и теперь решение о ее уничтожении будет принимать сборщик мусора. Уничтожение произойдет в тот момент, когда на эту переменную больше никто не будет ссылаться. Это означает, что примитивные типы данных всегда уничтожаются сразу же, как только кончается соответствующий блок. Напротив, уничтожение переменных ссылочных типов может быть отложено.
Уровни группирования и области действия объявлений легче всего представить себе в виде дерева, где каждый блок соответствует одной из ветвей. Чем больше вложенных друг в друга уровней группирования, тем выше будет это воображаемое дерево. Давайте возьмем для примера фрагмент программы и нарисуем соответствующее ему дерево уровней группирования:
class foo {
int cnt;
public void test1 {
int num;
}
public void test2;
for(int cnt=0; cnt < 5; cnt++) {
System.out.println(cnt);
}
}
}
Возможность создания нескольких уровней группирования позволяет производить затенение переменных. Например, если на уровне класса объявлена переменная cnt, ничто не мешает вам объявить переменную с тем же именем внутри какого-нибудь из методов этого класса, которая затенит вышестоящую переменную и сделает ее недоступной. Внутри метода идентификатор cnt будет относиться именно к локальной переменной cnt, объявленной в этом методе. Однако существует и возможность получить доступ к переменной класса, даже если она затенена. Для этого нужно воспользоваться особой ссылочной переменной this. Переменная this всегда указывает на текущий класс, поэтому, чтобы получить доступ к затененной переменной, объявленной в текущем классе, нужно явным образом указать принадлежность переменной к классу, а не к методу. Вот как это делается:
this.cnt = 4;
Правила именования переменных
Имя переменной должно начинаться с буквы. Оно может быть любой длины и содержать в себе буквы, цифры и любые символы пунктуации за исключением точки. Имя переменной не может совпадать с каким бы то ни было идентификатором, уже существующем на данном уровне группирования. Это значит, в частности, что имена переменных не могут совпадать с именами:
* меток;
* других переменных на данном уровне группирования;
* параметров текущего метода.
Побитовое дополнение
Знак операции побитового дополнения применяется только к целым типам. Эта операция интерпретирует целое число как набор битов и меняет в этом наборе все нули на единицы, а все единицы на нули. Применив этот знак операции к числу x, вы получите в результате число (-x)-1. Те, кому не доводилось работать с данными на уровне битов, возможно, найдут эту операцию непривычной для себя. Область ее применения - те задачи, в которых нас не интересует числовое значение целой переменной: мы используем эту переменную просто как набор отдельных битов. Например, число типа short, равное нулю (0x0000), после операции побитового дополнения превращается в -1 (0xFFFF).
Знаки операций инкремента и декремента
Термины "инкремент" и "декремент" означают прибавление и вычитание единицы. Знаки операций инкремента и декремента могут размещаться как до, так и после переменной. Эти варианты называются соответственно префиксной и постфиксной записью этих операций.
Знак операции в префиксной записи возвращает значение своего операнда после вычисления выражения. При постфиксной записи знак операции сначала возвращает значение своего операнда и только после этого вычисляет инкремент или декремент. Рассмотрим фрагмент кода, иллюстрирующий эту разницу:
int i=0;
int j=0;
System.out.println(++i);
System.out.println(j++);
На выходе этой программы вы получите сначала 1, а затем 0. В первом операторе печати мы использовали префиксную запись операции, при которой переменная i сначала инкрементируется, а затем ее значение выводится на печать. Во втором случае переменная j сначала выводится на печать, а затем инкрементируется. В обоих случаях после обоих операторов печати значением как i, так и j будет единица.
К побитовым знакам операций относятся те, что оперируют с числами в их битовом представлении. Эти операции применяются только к целочисленным значениям. Поскольку в числах с плавающей точкой битовые цепочки кодируют не само число, а его особое математическое представление, применение побитовых операций к таких числам не имеет смысла. Как правило, побитовые операции логического И (&), логического ИЛИ (|) и исключающего ИЛИ (^) используются для изменения и получения значения отдельных битов числа.
Представьте, что каждый бит целого числа хранит в себе какой-то флаг. Работая с целыми числами типа byte, мы можем хранить в них восемь таких флагов, по одному на каждый бит. Ниже показаны некоторые распространенные манипуляции с флагами, упакованными в целом числе:
byte flags=0xff; // исходное значение флагов 11111111
byte mask=0xfe; // битовая маска 11111110
flads = flags & mask;// установить флаг номер 8
flads = flags | mask; // сбросить флаг номер 8
flads = flags ^ mask; // = 00000001
Вниманию пользователей C/C++
Язык Java не поддерживает битовые поля. Чтобы получить доступ к отдельным битам,, вы должны пользоваться либо битовыми масками,, либо средствами класса java.util.BitSet.
Знак операции сдвига
Java поддерживает три операции сдвига - сдвиг влево (<<), сдвиг вправо (>>) и сдвиг вправо без учета знака (>>>>). Запись этих операций имеет следующий вид: сначала идет выражение, над которым производится сдвиг, затем знак операции сдвига и наконец количество битов, на которое производится сдвиг. Оба операнда должны быть целыми числами. Если вы хотите произвести сдвиг с числом с плавающей точкой, вы должны прибегнуть к явному преобразованию типа.
Операции сдвига с учетом знака сохраняют знак операнда. Сдвиг без учета знака заменяет каждую освобождающуюся при сдвиге позицию нулевым битом, игнорируя бит знака. Сдвиг на ноль битов в любую сторону допустим, хотя и не изменяет значения операнда.
С помощью операции сдвига можно эффективно производить умножение и деление на степени двойки. Например, сдвиг вправо на один бит равнозначен делению целого числа нацело на 2. При этом теряется крайний правый бит числа, поэтому нечетное число при этой операции даст тот же результат, что и меньшее на единицу четное число:
int i = 129; // в двоичном представлении это число равно 10000001
i = i >> 1; // теперь мы имеем 1000000, или 64
Комбинированные знаки операций
Некоторые знаки операций можно комбинировать со знаком операции присваивания, так что получающийся в результате комбинированный знак операции сначала вычисляет результат операции, а затем присваивает его переменной в левой части оператора присваивания. Такое комбинирование допустимо для всех знаков операций, возвращающих численный результат, - иными словами, для всех операций Java за исключением операций сравнения (см. раздел "Знаки операций сравнения" ниже).
Программист должен отдавать себе отчет в том, как происходит загрузка операндов в регистровую память процессора при выполнении таких комбинированных операций. Если в выражении, стоящем в правой части, вы измените значение переменной, стоящей в левой части оператора присваивания, то это изменение будет проигнорировано. Лучше всего проиллюстрировать это примером:
int i = 0;
i += ++i;
На первый взгляд этот фрагмент кода должен присваивать переменной i значение 2. Однако это не так. Вычисление производится следующим образом. Прежде всего, текущее значение i загружается в регистр. Затем вычисляется выражение ++i, его результат присваивается переменной i и используется как второй операнд операции сложения. Тонкость этого момента в том, что первым операндом в сложении служит исходное значение i, то есть 0. В результате мы получим значение 1, которое и будет окончательно занесено в переменную i. Таким образом, приведенный выше фрагмент программы присваивает i значение 1.
Знаки операций сравнения
Помимо знаков операций, производящих вычисления с числами, в Java есть группа знаков операций, предназначенных для сравнения двух значений. Эти операции так и называются - операции сравнения. Они имеют по два параметра и возвращают булевское значение, соответствующее результату сравнения. Знаки операций сравнения языка Java перечислены в табл. 4-9.
Таблица 4-9. Знаки операций сравнения
Знак операции Описание
< меньше чем
> больше чем
<= меньше или равно
>= больше или равно
== равно
!= не равно
Булевские знаки операций
Булевские знаки операций аналогичны соответствующим знакам операций для чисел. Все булевские операции возвращают значения типа boolean. Список булевских знаков операций языка Java приведен в табл. 4-10.
Таблица 4-10. Булевские знаки операций
Знак операции Описание
! отрицание
& логическое И
| логическое ИЛИ
^ исключающее ИЛИ
&& условное И
|| условное ИЛИ
== равно
!= не равно
op= комбинация оператора присваивания и одной из операций
?: условный оператор
Условный оператор - это единственная операция языка Java, имеющая три операнда. Этот оператор записывается в форме a?b:c. При этом сначала вычисляется выражение a, которое должно дать булевское значение, а затем, в соответствии с полученным результатом, возвращается либо b, если a имеет значение true, либо c, если a имеет значение false. По своей функции этот оператор аналогичен оператору if:
int i;
boolean cont=false;
// обычный оператор if
if (cont) i=5; *
else i=6;
// сокращенная запись с помощью условного оператора
i = (cont?5:6);
Один из самых частых случаев совмещения знаков операций - это определение операций для работы со строками. Поэтому в качестве компромисса авторы Java предусмотрели совмещение знака операции сложения, который благодаря этому может использоваться для конкатенации (сложения) строк. Если хотя бы один из двух операндов операции сложения является строкой, результат так же будет строкой. Это значит, что второй операнд, если он не принадлежит к типу String, будет искусственно приведен к этому типу. Правила преобразования операндов различных типов в строки суммированы в табл. 4-12.
Таблица 4-12. Правила преобразования нестроковых значений в строки для конкатенации
Операнд Правило
Пустая переменная. Любая ссылочная переменная, не указывающая ни на
какой объект, преобразуется в строку "null".
Целочисленное значение Преобразуется в строку, содержащую десятичное
представление данного целого числа, возможно, со знаком
минус впереди. Возвращаемое значение никогда не
начинается с символа "0" за единственным исключением:
если преобразуемое значение равно 0, возвращается строка
из одного символа "0".
Значение с плавающей точкой Преобразуется в строку, представляющую данное число в
компактной записи. Это значит, что если представление
числа занимает более десяти символов, число будет
преобразовано в экспоненциальную форму. Для
отрицательных чисел возвращаемая строка начинается со
знака минус.
Одиночный символ Преобразуется в эквивалентную строку длиной в один
символ.
Булевское значение Преобразуется в одну из двух строк символов - "true" или
"false", в зависимости от преобразуемого значения.
Объект Для преобразования в строку вызывается метод toString(),
определенный в данном объекте.
После того как оба операнда преобразованы к строковому типу, они конкатенируются. Вот несколько примеров того, как это происходит:
String foo = "Hello ";
String bar = "World";
int i = 42;
boolean cont = false;
String result = null;
result = foo + bar; // = "Hello World"
result = foo + i; // = "Hello 42"
result = foo + cont; // = "Hello false"
Использование знака операции плюс со строками весьма удобно. Однако зададимся вопросом: если знак операции плюс имеет со строками такое значение, то что должен в аналогичной ситуации делать знак операции минус? Ответ прост - он не делает ничего. А как насчет операций сравнения == и !=? Давайте проведем такой эксперимент:
String foo = "Hello";
String bar = "Hello";
if (foo == bar) System.out.println ("Равно");
else System.out.println ("Не равно");
В результате этой последовательности операторов на выходе вы получите строку "Равно". На первый взгляд все совершенно правильно - ведь наши строки в действительности равны друг другу. Однако давайте вспомним, как работает операция равенства для ссылочных переменных, указывающих на объекты. Как вы уже знаете, эта операция проверяет лишь, действительно ли сравниваемые объекты находятся в одном и том же месте памяти, а не то, равны ли они друг другу в каком-то ином смысле. Вот еще один пример применения знака операции равенства к строковым значениям.
class testString {
String st = "Hello";
}
class testString2 {
String st = "Hello";
String st2 = "Hello";
public static void main(String args[]) {
testString test = new testString();
testString2 test2 = new testString2();
if (test.st == test.st2) System.out.println ("Равно");
else System.out.println ("Не равно");
if (test.st == test2.st) System.out.println ("Равно");
else System.out.println ("Не равно");
}
}
На сей раз результат может показаться неожиданным. Первое сравнение дает результат "Равно", а второе - "Не равно". Дело здесь в том, что компилятор в подобных случаях производит оптимизацию кода с целью уменьшения занимаемой программой памяти. В частности, переменные st и st2, объявленные в пределах одного класса, на самом деле указывают на один и тот же экземпляр объекта в памяти - увидев, что вы заводите две одинаковые строки, компилятор решает сэкономить и поместить в код только один экземпляр этой строки, на который будут ссылаться две строковые переменные. Вот почему, оказывается, операция сравнения со строками иногда работает правильно, а иногда - нет.
Мораль проста: операцию сравнения == нельзя использовать для сравнения двух строк. Вместо этого вы должны пользоваться методом equals, определенном в классе String. Используя приведенное выше объявление переменных, мы могли бы переписать метод main, чтобы получить верный результат сравнения, следующим образом:
public static void main(String args[]) {
testString test = new testString();
testString2 test2 = new testString2();
if (test.st.equals(test.st2))
System.out.println ("Равно");
else
System.out.println ("Не равно");
if (test.st.equals(test2.st))
System.out.println ("Равно");
else
System.out.println ("Не равно");
}
}
public String substring(int start)
public String substring(int start, int len)
Первый метод из показанных выше возвращает новую строку, которая начинается с первой позиции в символьном массиве и содержит все символы после этой позиции. Второй метод возвращает только len символов после начала. Первый символ строки находится в нулевой позиции. Рассмотрим некоторые примеры:
String S="0123456789";
String S1=S.substring(0);
String S2=S;
// строки SI, S2 и S3 эквивалентны.
String S4=S.substrilng(4);
// S4 имеет значение "456789"
String S5=S.substring(4,3);
// S5 имеет значение "456"
Методы работы с подстроками неудобны единственно тем, что им всегда нужно передавать параметры. Это тот случай, когда можно применить методы indexOf. Методы indexOf позволяют вам исследовать класс String на индивидуальные символы или строки и возвращать индекс массива. Возвращенное целочисленное значение может затем быть передано методам работы с подстроками для извлечения или к методам regionMatches для сравнения. В табл. 6-6 описаны различные методы indexOf.
Таблица 6-6. Методы indexOf класса String
Объявление метода Параметры Возвращаемое значение
lilt indexOf(char a) a - символ для поиска. Индекс первого местонахождения.
IndexOf(char a, int start) a - символ для поиска, start - начальная позиция для поиска. Индекс первого местонахож-дения от начальной позиции.
IndexOf(String S) S - строка для поиска. Индекс первого местонахождения S.
indexOf(String S, int start) S - строка для поиска, start -начальная позиция для поиска. Индекс первого местонахождения от начальной позиции.
Метод lastIndexOf()
Для каждого метода indexOf имеется соответствующий метод lastlndexOf. Как и обязывает его имя,, метод lastlndexOf начинает поиск от конца строки.
Изменение строк
На протяжении этой главы мы изменяли строки следующими операторами:
String S="some string";
S=S+", more of the string",
S=S.substring(14,4);
// присвоение подстроки первоначальной строке
Обратите внимание, что, когда мы делаем эти перестановки, мы должны присвоить результат первоначальной строке. Этот шаг необходим, потому что, однажды созданный, экземпляр строки не может быть изменен. Так что мы создаем новую копию строки, вызывая один из методов класса String, и затем присваиваем новую копию обратно первоначальной переменной.
Кроме методов работы с подстроками, есть еще четыре других метода, которые мы можем использовать для перестановок. Эти методы приведены в табл. 6-7. Не забудьте, что в каждом случае создана копия строки, перестановки сделаны на этой копии и копия возвращена обратно. Ни один из методов в классе String фактически не изменяет строки непосредственно - взамен вы присваиваете новую копию или той же самой, или другой переменной.
Таблица 6-7. Методы изменения строк
Методы, используемые со строкой S1 Параметры Возвращаемое значение
S1.concat(S2) S2 - строка, которая будет конкатенирована к S1. SI+S2
S1.replace(a,b) Символ b заменяет символ a. S1 с замененным символом b.
S1.toLowerCase() нет S1 без символов верхнего регистра.
S1.toUpperCase() нет S1 без символов нижнего регистра.
S1.trim() нет S1 без начальных или конечных пробелов.
Разбор строк
Теперь у нас есть все инструменты для анализа строк. Мы можем сравнивать и, используя методы indexOf, отыскивать подстроки. Но давайте допустим, что строка может иметь несколько вхождений одной и той же подстроки. Например, математическое выражение может иметь несколько вхождений знака +. Компьютерные специалисты отнеслись бы к такой подстроке как к разделителю. Единицы между разделителями называются токенами.
Токены, разделители и компиляция
Слово "токен" может встретиться вам в литературе, связанной с компьютерными языками. Компиляторы должны разбить файл исходного текста, используя разделители, запятые и незаполненное пространство. Простой пример - процесс разбора списка параметров. Компилятор сначала изолирует символы внутри круглых скобок. Затем, используя запятую в качестве разделителя, он может идентифицировать токены - индивидуальные объявления переменных. После этого он может начинать компилировать остальную часть подпрограммы.
С помощью методов indexOf и методов работы с подстроками, о которых мы уже говорили, можно было бы написать некоторый код, который бы анализировал токены, основываясь на специфическом разделителе. Но это было бы нелегкой работой. К счастью, пакет java.util содержит класс StringTokenizer, который разобьет данную строку, основываясь на данном разделителе.
Предположим, что у нас есть строка "3 + 4 + 2 + 7" и нам нужно вычислить сумму целых чисел. Класс StringTokenizer расчленит строку, основываясь на знаках +, начиная слева.
Пример 6-4. Разбор строки с использованием класса StringTokenizer.
public int getSum(String S) {
int runningSum=0;
StringTokenizer tok=new StringTokenizer(S,"+");
// S - строка для разбора
// "+" - разделитель
while (tok.hasMoreTokens()) {
String thisToken=tok.getNextToken();
runningSum=Add(thisToken);
}
return runningSum;}
private int Add(String S) {
// мы должны знать преобразование примитивного типа
// прежде, чем сможем выполнить его
}
Как вы можете видеть, с помощью методов indexOf и regionMatches сделать то же самое было бы намного труднее. Мы можем резервировать использование методов indexOf и regionMatches, если мы должны расчленить строку только один раз. В табл. 6-8 описаны конструкторы, а в табл. 6-9 показаны основные методы, которые вы должны знать, чтобы эффективно использовать этот класс.
Таблица 6-8. Конструкторы класса StringTokenizer
Конструктор Описание
StringTokenizer(String S, String delim) Разбивает S, основываясь на delim.
StringTokenizer(String S, String delim, boolean returnDetims) Как и выше, но если delims == true, разделители возвращаются как токены.
StringTokenizer(String S) Разбивает строку, основываясь на незаполненном пространстве ("\t\n\r").
Таблица 6-9. Методы класса StringTokenizer
Метод Описание
String nextToken() Возвращает следующий токен.
String nextToken(String newDelim) Возвращает следующий токен после переключения разделителя на newDelim.
boolean hasMoreTokens() Возвращает true, если имеются токены, которые не были возвращены.
int countTokens() Возвращает число токенов в строке.
Создание строк
Строки можно создавать одним из двух способов:
* использованием одного из семи конструкторов класса String;
* использованием знака операции присваивания (=).
До сих пор для создания строк мы применяли знак операции присваивания. Оператор
String S="My string,'';
является самым простым способом создать строку. Знак операции + также может использоваться со строками для простой конкатенации строк, как показано в примере 6-1.
Пример 6-1a. Формирование строк из простых типов.
String S2="another string";
String S3=S+S2+"and a primitive type:"
int i=4;
S3=S3+i
S3 будет теперь иметь значение "My string, another string and a primitive type: 4". Знак операции + работает со всеми примитивными типами. Однако знак операции = этого не делает. Следующий фрагмент кода не будет компилироваться из-за последних двух операторов:
int i=4;
String SI=i;
// Не будет компилироваться!
String S2=4;
// Не будет компилироваться!
Это ограничение неприятно, но его можно легко, хотя и не очень изящно, обойти. Надо создать пустую строку и добавить к ней нужное значение с помощью знака операции +.
Пример 6-1b. Присваивание значений примитивных типов непосредственно строкам.
int i=4;
String S1=""+i;
String S2=""+4;
Знак операции присваивания работает только со строковыми константами (то есть текстом, содержащимся внутри кавычек) и другими строками. Знак операции + будет работать со строковыми константами, другими строками и всеми примитивными типами. Кроме того, строки могут быть созданы с помощью конструкторов, описанных в табл. 6-4.
Таблица 6-4. Конструкторы строк
Конструктор Объект, который он создает
String() Пустая строка.
String (String S) Новая строка, которая является копией S.
String(char charArray[]) Строка, основанная на массиве charArray.
String(char charArray[], int offset, int len) Строка, основанная на подмассиве, начинающаяся со смещения ang и длиной len символов.
String(byte byteArray[], int hibyte) Строка, основанная на массиве byteArray, где hibyte описывает старший байт Unicode каждого символа.
String(char charArray[], int hibyte, int offset, int len) Строка, основанная на подмассиве, начинающаяся по смещению offset и длиной len байт.
Хотя знак операции присваивания не совместим с примитивными типами, это действительно единственное, что является интуитивно неясным при создании строк в Java. Как только мы проберемся через причуды сравнения строк, вы будете готовы спокойно использовать строки.
Сравнение строк
Теперь, умея создавать строки, мы должны научиться их сравнивать. В классе String есть два метода сравнения - equals и compareTo. Метод equals заменяет метод equals в классе Object и возвращает true, когда строки совпадают, в то время как метод compareTo при эквивалентности строк возвращает 0.
Пример 6-2a. Сравнение строк с помощью методов compareTo и equals.
if (S1.compareTo(S2)!=0)
System.out.println("S1 is equivalent to the String S2");
if (S1.equals(S2))
System.out.println("S1 is equivalent to the String S2");
Значение, возвращаемое методом compareTo, равно -1, если S1 - лексически меньше, чем S2, и 1, если S1 лексически больше, чем S2. Как можно видеть из нижеприведенного примера, "лексическое" сравнение по существу означает сравнение в алфавитном порядке. Различие состоит в том, что этот порядок простирается на цифры и другие небуквенные символы так, что "a113" оказывается лексически больше, чем "a112". Упорядочение определено значениями Unicode.
Пример 6-2b. Сравнение строк с помощью метода compareTo.
String S1="alpha";
String S2="alpha";
if (S1.compareTo(S2)==-1)
System.out.println("this will always be printed");
if (S2.compareTo(S1)==1)
System.out.println("this will always be printed");
Лексическое упорядочение получено из целочисленных значений символов Unicode. Метод compareTo последовательно сравнивает каждый символ строк, основываясь на его целочисленном значении. Когда он находит различие, то возвращает 1 или -1, как описано выше.
Более сложным является сравнение строк при использовании символов "неанглийского" алфавита и служебных символов типа %, ! или *. Самый простой способ узнать численные значения подобных символов, не показанных на рис. 6-7, - привести тип char к int и напечатать его значение:
public void strangeCharacter(char Strange) {
int val =( int) Strange;
System.out.println ("One strange character!'');
System.out.println ( "The char " +Strange+" = " +val); }
Знак операции == и сравнение строк
Вам может встретиться Java-код, который сравнивает строки с помощью знака операции ==. В простых случаях знак операции == работает для строк так же, как для примитивных типов. Но такое использование не является частью спецификации языка - оно зависит от компилятора и может не работать в некоторых случаях. Так что лучше им не пользоваться, чтобы не допустить неприятную и труднонаходимую ошибку, и всегда применять методы compareTo или equals.
В дополнение к двум методам сравнения, которые мы только что рассмотрели, есть еще шесть других, сравнивающих две строки различными способами. Все они помещены в табл. 6-5 в порядке увеличения сложности.
Таблица 6-5. Методы сравнения строк
Метод Возвращает true, когда
equalsIgnoreCase(String S) Строка равняется S, независимо от регистра символов.
beginsWith(String S) Строка начинается с S.
beginsWith(String S,int len) Строка начинается с первых len символов S.
endsWith(String S) Строка заканчивается S.
regionMatches(int toffset, String S,int ooffset, int len) Подстрока в строке, начиная с позиции со смещением toffset, соответствует подстроке, начинающейся по смещению ooffset, для len символов.
regionMatches(boolean ignoreCase, int toffset, String S, int ooffset, int len) Тот же, что и предыдущий метод, но игнорирует регистр символов, когда ignoreCase == true.
Большинство методов сравнения строк интуитивно понятны, но методы regionMatches нуждаются в некотором объяснении. Они позволяют сравнивать любую из подстрок в S1 и S2. Параметр first определяет позицию в строке образца, c которой вы хотите начать сравнение. Второй параметр - некоторая другая строка с позицией, определенной в третьем параметре, с которой вы хотите начать сравнение в этой строке. Последний параметр - длина области сравнения. Использование методов regionMatches
Когда мы начнем анализировать строки, вы увидите, что эти два метода сравнения чрезвычайно полезны. Их правые целые числа можно передавать как параметры. К примеру, их можно использовать для эмуляции методов endsWith и startsWith. Ниже приведен фрагмент кода, который всегда возвращает true.
Пример 6-3a. Подражание методу startsWith с помощью метода regionMatches.
int lengthB=B.length();
if (A.startsWith(B)==A.regionMatches(0,B,0,lengthB))
// всегда true
Еще лучше проиллюстрировать, как добиться правильных параметров, можно, сделав эквивалент метода endsWith при помощи метода regionMatches.
Пример 6-3b. Подражание методу endsWith с помощью метода regionMatches.
int lengthB=B.length();
int startA=A.length()-B.length();
if (A.endsWith(B)== A. regionMatches( startA, B, 0, IengthB))
DealingWith Substrings
Работа с подстроками
Мы только что видели, как класс String позволяет сравнивать подстроки. Существует также множество методов для извлечения подстрок. Давайте начнем с простых методов работы с подстроками:
public String substring(int start)
public String substring(int start, int len)
Первый метод из показанных выше возвращает новую строку, которая начинается с первой позиции в символьном массиве и содержит все символы после этой позиции. Второй метод возвращает только len символов после начала. Первый символ строки находится в нулевой позиции. Рассмотрим некоторые примеры:
String S="0123456789";
String S1=S.substring(0);
String S2=S;
// строки SI, S2 и S3 эквивалентны.
String S4=S.substrilng(4);
// S4 имеет значение "456789"
String S5=S.substring(4,3);
// S5 имеет значение "456"
Методы работы с подстроками неудобны единственно тем, что им всегда нужно передавать параметры. Это тот случай, когда можно применить методы indexOf. Методы indexOf позволяют вам исследовать класс String на индивидуальные символы или строки и возвращать индекс массива. Возвращенное целочисленное значение может затем быть передано методам работы с подстроками для извлечения или к методам regionMatches для сравнения. В табл. 6-6 описаны различные методы indexOf.
Таблица 6-6. Методы indexOf класса String
Объявление метода Параметры Возвращаемое значение
lilt indexOf(char a) a - символ для поиска. Индекс первого местонахождения.
IndexOf(char a, int start) a - символ для поиска, start - начальная позиция для поиска. Индекс первого местонахож-дения от начальной позиции.
IndexOf(String S) S - строка для поиска. Индекс первого местонахождения S.
indexOf(String S, int start) S - строка для поиска, start -начальная позиция для поиска. Индекс первого местонахождения от начальной позиции.
Метод lastIndexOf()
Для каждого метода indexOf имеется соответствующий метод lastlndexOf. Как и обязывает его имя,, метод lastlndexOf начинает поиск от конца строки.
Изменение строк
На протяжении этой главы мы изменяли строки следующими операторами:
String S="some string";
S=S+", more of the string",
S=S.substring(14,4);
// присвоение подстроки первоначальной строке
Обратите внимание, что, когда мы делаем эти перестановки, мы должны присвоить результат первоначальной строке. Этот шаг необходим, потому что, однажды созданный, экземпляр строки не может быть изменен. Так что мы создаем новую копию строки, вызывая один из методов класса String, и затем присваиваем новую копию обратно первоначальной переменной.
Кроме методов работы с подстроками, есть еще четыре других метода, которые мы можем использовать для перестановок. Эти методы приведены в табл. 6-7. Не забудьте, что в каждом случае создана копия строки, перестановки сделаны на этой копии и копия возвращена обратно. Ни один из методов в классе String фактически не изменяет строки непосредственно - взамен вы присваиваете новую копию или той же самой, или другой переменной.
Таблица 6-7. Методы изменения строк
Методы, используемые со строкой S1 Параметры Возвращаемое значение
S1.concat(S2) S2 - строка, которая будет конкатенирована к S1. SI+S2
S1.replace(a,b) Символ b заменяет символ a. S1 с замененным символом b.
S1.toLowerCase() нет S1 без символов верхнего регистра.
S1.toUpperCase() нет S1 без символов нижнего регистра.
Подавляющее большинство программ выполняются не линейно, а содержат в себе особые конструкции, позволяющие изменять порядок выполнения операторов в зависимости от некоторых условий. Операторы, ответственные за изменение порядка выполнения других операторов, называются операторами передачи управления. К этим операторам относятся, например, часто употребляемые оператор if-else и операторы цикла. Все языки программирования имеют те или иные операторы передачи управления, и большинство этих операторов в Java должны быть вам уже хорошо знакомы. Тем не менее мы рассмотрим каждый из них в подробностях. Особенного внимания заслуживает оператор switch- если вы никогда не программировали на C, то вы, вероятно, найдете этот оператор полезным добавлением к своему инструментарию. Прежде чем переходить к подробному рассмотрению, приведем краткую сводку всех операторов передачи управления Java:
if (boolean) statement;
else statement2;
for (expression; boolean; expression) statement;
while (boolean) statement;
do statement; while (boolean);
break label;
continue label;
return expression;
switch (expression) {
case value : statement;
...
default : statement;
}
Оператор if-else
Несомненно, самый распространенный оператор передачи управления - оператор if-else. При его выполнении вычисляется некое выражение, которое должно дать результат булевского типа. В зависимости от этого результата выполняется либо оператор, стоящий непосредственно после проверяемого условия (если результат равен true), либо оператор, стоящий после ключевого слова else (если результат равен false). Второй оператор и само ключевое слово else являются факультативными; их пропуск приведет к тому, что при ложности проверяемого условия ничего не произойдет. Вот простой пример использования оператора if-else:
if (done == true) System.out.println("Готово!");
else System.out.println("Продолжим...");
Существует также возможность комбинировать несколько операторов if-else, выстраивая их в цепочку. При этом условия в каждом операторе if проверяются в порядке их записи, до тех пор пока одно из условий не окажется истинным. Если ни одна из проверок не окажется успешной, выполняется оператор, следующий за последним из операторов else. Проиллюстрируем это примером, в котором на печать выводится некое сообщение, зависящее от температуры по Цельсию:
int temp;
if (temp < 0) System.out.println("Брр, ну и холод!");
else if (temp > 100) System.out.println("Что, вода уже кипит?");
else System.out.println("Какая чудная погода, не правда ли?");
Вы, вероятно, заметили, что в этих примерах каждый из операторов if приводил к выполнению только одного оператора программы. Но что делать, если вам в зависимости от какого-то условия нужно выполнить или не выполнить сразу несколько операторов? Для этого язык Java позволяет объединять операторы в блоки. Блоком называется фрагмент кода из одного или нескольких операторов, который можно поместить в любое место, где правила синтаксиса допускают одиночный оператор. Отличительным признаком блока является то, что его содержимое заключено в фигурные скобки. Приведем еще один пример ветвления с использованием блоков:
int itemCount;
boolean checkout;
if (itemCount <= 10) {
System.out.println("Спасибо, начинаем проверку...");
checkout = true;
}
else {
System.out.println("Извините, больше десяти нельзя!")
checkout = false;
}
СОВЕТВ языке Паскаль блоки выделяются с помощью пар ключевых слов begin...end. Язык Java унаследовал традицию использования фигурных скобок из C/C++.
Следует обратить особое внимание на одну проблему, связанную с использованием оператора if-else, - проблему неоднозначной принадлежности ключевого слова else. Можно представить себе ситуацию, в которой, если не ввести дополнительное правило, невозможно будет понять, к какому из операторов if относится данное else. Вот пример:
int checkCost, moneySaved;
boolean overDraft;
if (checkCost > moneySaved) // двусмысленная запись
if (overDraft == true)
System.out.println("Перерасход средств");
else System.out.println("Покупка произведена");
В приведенном примере, который мог бы быть частью программы обслуживания покупок в магазине, содержится неоднозначность. Рассмотрим ключевое слово else. Если судить по отступу от левого края, то этот оператор должен относиться к первому оператору if. Однако на самом деле компилятор, который, как известно, на отступы внимания не обращает, воспримет его как часть второго, вложенного оператора if. Общее правило здесь таково: каждый блок с else ассоциируется с ближайшим предшествующим ему блоком if, не имеющем else. Если это не соответствует вашим намерениям, вы должны явным образом соотнести else с требуемым if с помощью заключения внутреннего if в блок. Разумеется, компилятор никогда не соотнесет else с тем из операторов if, который находится во вложенном блоке. Вот как это делается:
int checkCost, moneySaved;
boolean overDraft;
if (checkCost > moneySaved) {
if (overDraft == true) {
System.out.println("Перерасход средств");
}
}
else System.out.println("Покупка произведена");
Помимо того, что использование блоков позволяет предотвратить такого рода ошибки, которые бывает иногда очень сложно обнаружить, оно также делает программу более удобочитаемой. Оператор if-else - весьма полезный инструмент в программировании, но вы должны следить за тем, чтобы каждое else единственным способом соотносилось с одним из операторов if.
Операторы while и do-while
Операторы while и do-while позволяют реализовать особого рода циклическое выполнение фрагмента программы. Оператор while следует употреблять тогда, когда есть вероятность, что тело цикла вообще не нужно будет выполнять. Логическое выражение в этом операторе вычисляется первый раз до того, как произойдет первое выполнение тела цикла, и если оно с самого начала будет равно false, оператор while никогда не передаст управление телу цикла. Следует помнить, что значение какой-либо из переменных, от которых зависит логическое выражение, обязательно должно изменяться внутри цикла, иначе цикл никогда не завершится. Часто порядок действий таков: мы заводим булевскую переменную где-то в нашей программе, после чего выполняем некую последовательность действий до тех пор, пока эта переменная равна true. Предположим, что у нас есть функция с именем result(), которая возвращает это булевское значение:
while( result() ) {
// какие-то операторы
...
}
Этот цикл будет выполняться до тех пор, пока функция result() возвращает true. Если при первом же вызове функция вернет false, тело цикла никогда не получит управления. Если же нам нужно, чтобы тело цикла обязательно выполнилось хотя бы один раз, нужно использовать цикл do-while. Тело цикла этого типа получает управление до того, как в первый раз вычисляется контрольное выражение; если оно при этом будет равно false, цикл прервется, но тело его будет к этому моменту уже пройдено один раз. Пользуясь той же функцией result(), возвращающей булевское значение, мы можем переписать предыдущий пример так, чтобы тело цикла выполнялось в нем хотя бы один раз:
do {
// какие-то операторы
...
} while( result() );
Помимо широко распространенных конструкций с while и do-while, Java также поддерживает цикл, вводимый оператором for. Хотя принципиально этот тип цикла ничем не отличается от цикла с while, он тем не менее зачастую делает программы более понятными при чтении.
Оператор for
Оператор for - самая сложная из конструкций Java для организации циклов. Этот оператор позволяет присваивать начальное значение переменным, указывать логическое выражение, контролирующее выполнение цикла, а также задавать оператор, который будет выполняться каждый раз в конце тела цикла (чаще всего этот оператор инкрементирует или декрементирует переменную цикла). Цикл for наиболее удобен в тех случаях, когда тело цикла должно быть выполнено определенное количество раз. Вот, например, как можно запрограммировать печать целых чисел от 0 до 4:
for (int i=0; i < 5; i++) {
System.out.println(i);
}
Три выражения, взятые в операторе for в общую пару круглых скобок и разделенные точкой с запятой, вычисляются в разные моменты времени. Первое из этих выражений, называемое инициализацией цикла, выполняется один раз перед первым проходом тела цикла. Второе выражение, контрольное, вычисляется перед началом каждого выполнения тела цикла (в том числе и перед первым его выполнением). Последнее, шаговое выражение вычисляется в конце каждого выполнения тела цикла. Как правило, в нем происходит увеличение или уменьшение некоей переменной, от которой зависит значение контрольного выражения.
В отношении приведенного выше примера стоит сделать несколько замечаний. Во-первых, обратите внимание, что в выражении инициализации объявляется новая переменная типа int. Это совершенно законный и весьма полезный способ использования временных переменных: объявив переменную в выражении инициализации цикла, вы тем самым ограничиваете область ее действия только этим циклом. Кроме того, объявление переменной там же, где она используется, делает программу понятнее для того, кто ее читает, а также облегчает выделение из кода фрагментов для использования в других местах.
Во втором по счету, контрольном, выражении допустимо только нечто, что возвращает результат булевского типа. В отличие от C/C++ вы не можете использовать в этой функции выражение одного из целых типов. Как известно, многие программисты на C любят использовать в таких ситуациях запись типа (i), подразумевая тем самым "true, если i не равно 0, и false, если i равно 0". В Java, однако, такие ухищрения уже не работают; вы должны написать следующее: (i != 0). Таким образом авторы Java пытаются насильственно насаждать хороший стиль программирования.
СОВЕТ Все контрольные выражения циклов должны возвращать значения булевского типа. В отличие от C/C++ целочисленное выражение не может использоваться в качестве контрольного - Java не будет самостоятельно преобразовывать его к булевскому типу.
Последнее из трех выражений - шаговое выражение - вычисляется в конце каждого выполнения тела цикла. Как правило, в этом выражении инкрементируется или декрементируется переменная цикла. То, что эти три выражения в операторе for собраны вместе в заголовке цикла, облегчает написание и отладку циклов, а также делает их смысл более очевидным при чтении - так, в нашем примере можно сразу же сказать, сколько раз будет выполнено тело цикла. В то же время цикл for может использоваться в тех же ситуациях, что и другие типы циклов. Например, вот как можно смоделировать цикл типа while с помощью оператора for:
boolean cont = true;
...
for(; cont == true;) {
// операторы тела цикла
...
// не забудьте предусмотреть изменение значения cont
...
}
Оператор while содержит контрольное выражение, вычисляемое перед первым выполнением тела цикла; если это выражение с самого начала равно false, тело цикла выполняться не будет. Чтобы сымитировать такое поведение в операторе for, достаточно опустить в нем выражение инициализации и шаговое выражение (однако хотя самих выражений и нет, точки с запятой должны оставаться на своих местах).
Операторы break и continue
Язык Java наконец-то окончательно разделался с оператором goto, вредоносность которого была давно очевидна для всех. Чаще всего операторы goto использовались для преждевременного выхода из цикла по какому-нибудь условию; в этой функции они часто делали код неоправданно сложным и способствовали появлению труднообнаружимых ошибок. Однако представьте, что вам позарез нужно запрограммировать цикл, тело которого выполняется десять раз всегда, кроме первого четверга каждого месяца, когда цикл должен выполниться только четыре раза. Как можно в таком случае обойтись без оператора goto для преждевременного прерывания цикла? Необходимо прибегнуть к особым операторам, которые являются в Java прямыми потомками вымершего goto, - операторам break и continue.
С помощью этих операторов можно осуществить преждевременный выход из цикла или из тела метода. Какой именно из двух операторов нужно использовать, зависит от того, куда вы при этом хотите попасть.
Оператор break позволяет передать управление на конец соответствующей конструкции (цикла for, do-while или тела оператора switch). Выполнение цикла прервется вне зависимости от значения контрольного выражения, и управление получит оператор, следующий сразу за оператором цикла. К примеру, оператор break можно использовать для выхода из цикла while, который иначе выполнялся бы до бесконечности:
int i=0;
while(true) {
System.out.println(i);
i++;
if (i > 10) break;
}
Приведенный пример напечатает вам целые числа от нуля до десяти. Без оператора break этот цикл выполнялся бы бесконечно, поскольку его контрольное выражение всегда равно true. Оператор break предусматривает выход из цикла при достижении переменной i значения, большего десяти.
Оператор continue похож на оператор break. Точно так же, как и break, он передает управление на самый конец тела цикла (после последнего его оператора), но, в отличие от оператора break, это не приводит к обязательному прерыванию цикла. Если контрольное выражение, вычисленное после того, как очередной проход тела цикла был насильственно завершен оператором continue, будет равно true, выполнение цикла продолжится. Это бывает полезно для того, чтобы пропустить некоторые из выполнений тела цикла. Так, в следующем примере оператор continue предотвращает ошибку деления на ноль:
for( int i = -10; i < 10; i++) {
if (i == 0) continue;
System.out.println(1/i);
// сюда передается управление
}
Тело цикла, таким образом, не будет выполнено при i == 0. В этом примере следует обратить особое внимание на то, что шаговое выражение, i++, выполняется и для тех проходов тела цикла, которые искусственно пропущены с помощью continue. Если бы это было не так, цикл никогда не сдвинулся бы с мертвой точки. Как и в этом примере, оператор continue часто используется для пропуска части кода, который не имеет смысла или приводит к ошибкам для определенных значений.
И оператор break, и оператор continue могут содержать факультативную часть - метку (label). Как и в других языках, любому оператору в Java может быть приписана метка. Синтаксис таков: имя метки, двоеточие, а затем сам оператор. Например, вот как можно пометить оператор цикла:
loop: for(int i=0; i < 10; i++) {
}
Смысл приписывания метки операторам цикла может быть вам непонятен до тех пор, пока вам не потребуется запрограммировать несколько вложенных друг в друга циклов. Представьте ситуацию: вы находитесь в теле цикла глубокого уровня вложенности, как вдруг происходит ошибка и вам нужно немедленно выскочить на самый верх. Возможно, первая мысль, которая придет вам в голову - "Ах, ну почему же в Java нет оператора безусловного перехода?" Весь фокус заключается в том, что и здесь вполне можно обойтись без оператора goto, воспользовавшись вместо него либо механизмом исключений (о котором ниже), либо операторами break или continue с метками.
По сути, break и continue с метками мало чем отличаются от оператора goto. Возьмем такую вполне реальную ситуацию: вы производите поиск некоего значения в двумерном массиве, например значения 5. Как только вы нашли элемент массива, равный пяти, вам нужно выйти из обоих вложенных друг в друга циклов. Вот как это делается с помощью оператора break с меткой:
int i,j;
int nums[][] = new nums[5][5];
boolean found = false;
loop:
for(i=0; i < 5; i++) {
for(j=0; j < 5; j++) {
if (nums[i][j] == 5) {
found = true;
break loop;
}
}
}
if (!found) System.out.println("Значение не найдено");
else System.out.println("Значение найдено по адресу " + i + " ," + j);
Этот фрагмент кода иллюстрирует, помимо всего прочего, многое из материала данной главы, а задача, которую он решает, относится к довольно распространенным. Если все в этом фрагменте кода вам понятно, то вы можете с чистой совестью сказать, что вполне овладели такими понятиями, как циклы, массивы, операторы и метки. Если вы до сих пор не работали с языком C, вероятно, приводимые примеры покажутся вам излишне запутанными и неочевидными. Однако большинство программистов на C переходят на язык Java без всякого труда.
Оператор switch
В некоторых старых языках программирования нередко можно было видеть длинные цепочки операторов if-else, производящие, например, идентификацию команды, выбранной в меню. При этом после ввода пользователем команды и определения, что же именно было введено, выполнялось некоторое действие. В больших и сложных программах типа текстовых процессоров количество команд могло достигать сотен и тысяч. Чтобы иметь возможность более изящно разбираться с таким обилием возможностей, был придуман оператор switch.
Выражение-переключатель, которое ставится в круглых скобках после ключевого слова switch, может принадлежать к одному из типов char, byte, short или int. Проверка значения этого выражения осуществляется с помощью операторов case, входящих в оператор switch в качестве составных частей. Как только один из операторов case опознал значение выражения-переключателя, управление получает фрагмент кода, следующий за этим оператором case. Вот пример разбора с помощью оператора switch команды, выбранной из меню:
int cmd; // предположим, что в этой переменной хранится номер выбранной команды
switch(cmd) {
case 1 : System.out.println("Команда номер один");
break;
case 2 : System.out.println("Команда номер два");
break;
default : System.out.println("Неизвестная команда");
}
Каждое из значений, для которых вы хотите предусмотреть некие действия, должно указываться в отдельном операторе case. Константа после каждого оператора case должна принадлежать к тому же типу, что и выражение-переключатель в самом операторе switch. Можно также предусмотреть особый оператор default, отмечающий тот код, который будет выполнен, если ни один из операторов case не сработает.
Обратите особое внимание на операторы break в нашем примере. Они позволяют разграничить друг от друга фрагменты кода, относящиеся к разным операторам case. Без оператора break оператор, стоящий после case 1, получив управление и отработав, не прервал бы выполнение тела оператора switch, а передал бы управление дальше, на оператор, помеченный case 2. В некоторых случаях вам может понадобиться именно такое поведение, но в большинстве случаев оно является ошибкой. Оператор case без оператора break применяется почти исключительно в тех случаях, когда нужно приписать одному фрагменту кода несколько операторов case. Поэтому не забывайте расставлять в нужных местах тела оператора switch операторы break.
Массивы являются гибридом объектов и примитивных типов. Они похожи на объекты, но имеют специальное значение для компилятора. Класс Array определен с модификатором final, так что вы не можете расширить его функциональные возможности с помощью своего собственного объекта.
Обычно массивы используются для хранения группы сходных информационных объектов. Все элементы массива во время компиляции должны иметь один и тот же тип. Если массив состоит из элементов примитивных типов, все они должны быть одного типа, а если из переменных ссылочных типов, то все они должны указывать на один и тот же тип.
Подобно объектам массивы требуют использования оператора new для создания экземпляра каждого элемента. В этом отношении массивы Java отличаются от массивов в большинстве языков программирования, в которых все элементы массива уже инициализированы сразу после объявления.
СОВЕТ Массивы в Java имеют важное отличие от массивов в большинстве языков программирования. Чтобы сэкономить свое время в будущем, стоит немного изучить их сейчас. Не надейтесь, что их действие аналогично тому, что делают массивы в языках C или Паскаль. Если вы когда-либо работали с языком SmallTalk, массивы Java должны казаться вам более знакомыми.
Создание массивов
Массивы инициализируются с помощью оператора new. Элементы массива лучше представлять себе в виде отдельных объетов. Массив является как бы контейнером для этих объектов и дает им общую точку доступа.
Простейшим типом массива является одномерный массив переменных примитивного типа, например int. Фрагмент кода для создания и инициализации такого массива следующий:
init nums[] = new int[5];
В этом объявлении массива присутствует большинство концепций, важных для задания массива. Квадратные скобки после идентификатора nums говорят компилятору о том, что nums является массивом. Оператор new задает новый массив и вызывает конструктор для каждого элемента. Тип конструктора - int, и массив может содержать пять элементов. Если вы будете помнить о том, что вы можете использовать конструкторы для создания массивов, у вас не возникнет проблем с многомерными массивами.
У массивов в Java должна быть указана по крайней мере одна размерность. Остальные могут быть определены во время выполнения программы. Создать и инициализировать двумерный массив можно с помощью следующего фрагмента кода:
class tarray {
public static void main(String args[]) {
int numsList[][] = new int[2][];
// вторую размерность мы определим позже
numsList = new int[2][10];
for(int i=0; i < 10; i++) {
numsList[0][1] = 1;
numsList[1][1] = 1;
}
for(int i=0; i < 10; i++) {
System.out.print(numsList[0][1] + " ");
System.out.println(numsList[1][1]);
}
}
}
Здесь создается массив под именем numsList, в котором определена только одна размерность. Другую размерность мы можем определить позже. Важно помнить о вызове конструктора для каждого элемента массива. Это свойство языка можно использовать для создания непрямоугольного массива - например, треугольного:
int[][] createArray(int n);
int[][] nums = new int[n][];
for(i=0; i < n; i++) {
nums[1] = new int[i+1];
}
return nums;
}
Здесь создается треугольный массив, в котором первый элемент массива содержит один элемент, второй - два элемента и последний - n элементов. Такой тип инициализации открывает новые пути применения массивов. Например, мы можем использовать массивы, если нам потребуется связанный список или другая динамическая структура данных.
Инициализация массивов
Массивы могут быть инициализированы во время создания путем перечисления требуемых начальных значений в фигурных скобках {}. При этом можно не указывать размер массива - Java инициализирует массив со стольким количеством элементов, сколько было задано. При этом можно вкладывать задаваемые элементы один в другой, чтобы создать многомерный массив.
В следующем примере приведен простой одномерный массив. Если мы хотим, чтобы он содержал числа от 1 до 5, мы можем инициализировать его следующим образом:
int[] nums = {1,2,3,4,5};
Инициализация двумерного массива требует вкладывания элементов друг в друга. Создадим, к примеру, массив, содержащий в себе пары целых чисел:
int nums[][] = {{1,1},{2,2},{3,3},{4,4},{5,5}};
Как вы можете видеть, инициализация одно- и двумерных массивов очень проста. Многомерные массивы труднее представить себе зрительно, но синтаксис у них тот же самый, просто для инициализации нужно использовать больше уровней вложенности. На практике вы скорее всего придете к использованию циклов для инициализации многоразмерных массивов.
Доступ к массивам
Индексом массива может служить значение типа byte, short, int или char. Невозможно объявить массив, индекс которого принадлежал бы к типу long, boolean, float или double. Если вам потребуется использовать один из этих типов в качестве индекса, вы должны будете прибегнуть к явному преобразованию.
Диапазон изменения индекса массива простирается от нуля до величины, на единицу меньшей размера массива. Длину массива можно выяснить с помощью переменной length, входящей в класс, реализацией которого является любой массив. Вот пример использования этой переменной для получения информации о длине массива:
long sum(int[] list) {
long result=0;
for(int i=0; i < list.length; i++) {
result = result + list[i];
}
return result;
}
Этот фрагмент кода, очевидно, предназначен для подсчета суммы массива целых чисел произвольной длины. Переменная length используется здесь, чтобы выяснить верхнюю границу суммируемого массива.
В языке Java реализована проверка выхода за границы массива (bounds checking). Это значит, что любой доступ к массиву влечет проверку, не выходит ли индекс массива за пределы своего диапазона. Если произошел выход за границы допустимого диапазона, генерируется исключение ArrayIndexOutOfBoundsException.
Массивы - один из самых мощных инструментов языка Java. Однако иногда за удобство и гибкость работы с массивами приходится платить. Дело в том, что некоторые компиляторы оптимизируют массивы так, чтобы все их элементы имели одинаковый размер, в то время как другие компиляторы этого не делают. Как правило, если элементы массива имеют разный размер, доступ к такому массиву значительно замедляется. В таких случаях приходится искать компромисс между скоростью работы и простотой реализации алгоритма.
Java комментарии необходимы для комментирования программы и для составления или оформления документации. Существует специальный синтаксис для оформления документации в виде комментариев и инструмент для выделения этих комментариев в удобную форму. Инструмент называется javadoc. Обрабатывая файл с исходным текстом программы, он выделяет помеченную документацию из комментариев и связывает с именами соответствующих классов или методов. Таким образом, затратив минимум усилий на оформления комментариев, можно получить хорошую документацию к программе.
На выходе javadoc получается HTML файл, который можно просмотреть любым веб-обозревателем. Этот инструмент позволяет создавать и поддерживать файлы с исходным текстом программы и, при необходимости, генерировать сопроводительную документацию. Библиотеки Java обычно документируются именно таким способом, именно поэтому при разработке программ удобно использовать JDK с комментированным для javadoc исходным текстом библиотек вместо JRE, где исходники отсутствуют.
Java имеет три типа комментариев. Первые два типа: //... и /*...*/. Третий тип называется комментарием документации. Такой комментарий начинается с последовательности символов /** и заканчивается последовательностью */. Комментарии документации позволяют добавлять в программу информацию о ней самой. С помощью утилиты javadoc (входящей в состав JDK) эту информацию можно извлекать и помещать в НТМL файл.
Утилита javadoc позволяет вставлять HTML тэги и использовать специальные ярлыки (дескрипторы) документирования. НТМL тэги заголовков не используют, чтобы не нарушать стиль файла, сформированного утилитой.
Дескрипторы javadoc, начинающиеся со знака @, называются автономными и должны помещаться с начала строки комментария (лидирующий символ * игнорируется). Дескрипторы, начинающиеся с фигурной скобки, например {@code}, называются встроенными и могут применяться внутри описания.
Комментарии документации применяют для документирования классов, интерфейсов, полей (переменных), конструкторов и методов. В каждом случае комментарий должен находиться перед документируемым элементом.
Для документирования переменной можно использовать следующие дескрипторы:
@see, @serial, @serialField, {@value}, @deprecated.
Для классов и интерфейсов можно использовать дескрипто ры:
@see, @author, @deprecated, @param, @version.
Методы можно документировать с помощью дескрипторов:
@see, @return, @param, @deprecated, @throws, @serialData, {@inheritDoc}, @ехсерtiоn.
Дескрипторы {@link}, {@docRoot}, {@code}, {@literal}, @since, {@linkplain} могут применяться где угодно.
В Java ключевое слово final имеет слегка разные значения в зависимости от контекста, но в основном, оно определяется так "Это не может быть изменено". Вы можете хотеть запретить изменения по двум причинам: дизайн или эффективность. Поскольку эти две причины слегка различаются, то существует возможность неправильного употребления ключевого слова final.
В следующих секциях обсуждается применение final тремя способами: для данных, для методов и для классов.
Многие языки программирования имеют пути сообщить компилятору, что данный кусочек данных является неизменным, константой. Константа наиболее удобна для применения в следующих двух случаях:
В случае константы во время компиляции компилятор свертывает константу до значения в любых вычислениях, где она используется; при этом, нагрузка при вычислениях во время работы программы может быть значительно снижена. В Java константы такого рода должны быть примитивного типа и объявлены с использованием final. Значение должно быть определено во время определения переменной, как и любой константы.
Поля имеющие модификаторы static и final вообще являются ячейкой для хранения и не могут быть изменены.
При использовании final с объектами, а не с примитивными типами получается несколько не тот эффект. С примитивами, final создает константу значения, а с объектами - ссылку, final создает ссылку - константу. Как только ссылка инициализируется на какой-то объект, она уже не может быть в последствии перенаправлена на другой объект. Однако сам объект может быть модифицирован; Java не предоставляет способа создать объект - константу. (Однако, Вы можете написать свой собственный класс с эффектом константы.) Эти же ограничения накладываются и на массивы, поскольку они тоже объекты.
Ниже представлен пример, демонстрирующий использование полей с модификатором final:
//: c06:FinalData.java
// Эффект полей final.
class Value {
int i = 1;
}
public class FinalData {
// Может быть константой во время компиляции
final int i1 = 9;
static final int VAL_TWO = 99;
// Обычная public константы:
public static final int VAL_THREE = 39;
// Не может быть константой во время компиляции:
final int i4 = (int)(Math.random()*20);
static final int i5 = (int)(Math.random()*20);
Value v1 = new Value();
final Value v2 = new Value();
static final Value v3 = new Value();
// Массивы:
final int[] a = { 1, 2, 3, 4, 5, 6 };
public void print(String id) {
System.out.println(
id + ": " + "i4 = " + i4 +
", i5 = " + i5);
}
public static void main(String[] args) {
FinalData fd1 = new FinalData();
//! fd1.i1++; // Ошибка: значение не может быть изменено
fd1.v2.i++; // Объект не константа!
fd1.v1 = new Value(); // OK -- не final
for(int i = 0; i < fd1.a.length; i++)
fd1.a[i]++; // Объект не константа!
//! fd1.v2 = new Value(); // Ошибка: Нельзя
//! fd1.v3 = new Value(); // изменить ссылку
//! fd1.a = new int[3];
fd1.print("fd1");
System.out.println("Creating new FinalData");
FinalData fd2 = new FinalData();
fd1.print("fd1");
fd2.print("fd2");
}
} ///:~
Поскольку i1 и VAL_TWO являются final примитивами со значениями во время компиляции, то они могут быть использованы в обоих случаях, как константы времени компиляции и не имеют при этом отличий. VAL_THREE определена более типичным путем и Вы можете видеть, как определяются константы: public так, что она может быть использована вне пакета, static т.е. может существовать только одна и final объявляет, что она и есть константа. Заметьте, что примитивы final static с начальными неизменяемыми значениями (константы времени компилирования) называются большими буквами и слова разделены подчеркиванием (такие наименование похожи на константы в C.) Так же заметьте, что i5 не может быть известна во время компиляции, поэтому она названа маленькими буквами.
Просто, если, что-то определено, как final это еще не значит, что его значение известно на стадии компиляции. Это утверждение демонстрируется инициализацией i4 и i5 во время выполнения с использованием случайно генерируемых чисел. Та порция примера так же показывает различие между созданием final с модификатором static и без него. Это различие заметно, только во время инициализации во время выполнения, это происходит из-за того, что значения времени компиляции обращаются в те же самые самим компилятором. (И по видимому, существенно оптимизированными.) Это различие показано в выводе программы после одного запуска:
fd1: i4 = 15, i5 = 9
Creating new FinalData
fd1: i4 = 15, i5 = 9
fd2: i4 = 10, i5 = 9
Заметьте, что значения i4 для fd1 и для fd2 уникальны, но значение для i5 не изменилось после создания второго объекта FinalData. Такое произошло потому, что i5 static и инициализировалась только один раз при загрузке, а не каждый раз, когда создавался новый объект.
Переменные v1 и v4 демонстрируют значение final ссылки. Как Вы можете видеть в методе main( ), только потому, что v2 является final вовсе не означает, что Вы не можете изменить ее значение. Хотя, Вы не можете перенаправить v2 на новый объект, и это потому, что она final. Вот такой смысл вкладывается в понятие final ссылок. Вы так же можете увидеть точно такое же действие на примере массивов, поскольку они являются так же разновидностью ссылок. (Я например не знаю способа, как сделать ссылки массивов самих на себя final.) Таким образом, создание ссылок с типом final менее удобна в использовании, чем создание примитивов с модификатором final.
Java позволяет создавать пустые (чистые) final объекты (blank final), это такие поля данных, которые были объявлены как final но при этом не были инициализированы значением. Во всех случаях, пустая final переменная должна быть инициализирована до ее использования и компилятор обеспечивает это условие. Тем не менее, пустые final поля предоставляют большую гибкость при использовании модификатора final, к примеру, final поле внутри класса может быть разным для каждой копии объекта. Вот пример:
//: c06:BlankFinal.java
// "Пустые" final данные.
class Poppet { }
class BlankFinal {
final int i = 0; // инициализируем final
final int j; // пустой final
final Poppet p; // Ссылка на пустой final
// Пустой final ДОЛЖЕН быть инициализирован
// в конструкторе:
BlankFinal() {
j = 1; // инициализируем чистую final
p = new Poppet();
}
BlankFinal(int x) {
j = x; // Инициализируем чистую final
p = new Poppet();
}
public static void main(String[] args) {
BlankFinal bf = new BlankFinal();
}
} ///:~
Вы принудительно должны осуществить соединение переменной final со значением при ее определении или в конструкторе. При этом гарантировано не будет доступа к переменной, до ее инициализации.
Java позволяет Вам так же создавать и аргументы final определением их таким образом прямо в списке аргументов. Это означает, что внутри метода Вы не сможете изменить этот аргумент или его ссылку:
//: c06:FinalArguments.java
// Использование "final" с аргументами методов.
class Gizmo {
public void spin() {}
}
public class FinalArguments {
void with(final Gizmo g) {
//! g = new Gizmo(); // Неверно -- g - final
}
void without(Gizmo g) {
g = new Gizmo(); // OK -- g не final
g.spin();
}
// void f(final int i) { i++; } // Не может измениться
// Вы можете только читать примитив:
int g(final int i) { return i + 1; }
public static void main(String[] args) {
FinalArguments bf = new FinalArguments();
bf.without(null);
bf.with(null);
}
} ///:~
Заметьте, что Вы все еще можете соединить null ссылку с final аргументом, без реакции со стороны компилера, таким же образом, как и с не final аргументами.
Методы f( ) и g( ) показывают, что случается, когда примитивный аргумент - final: Вы можете прочитать его, но не можете изменить его.
Существует две причины для final методов. Первая - закрытие методов, от возможной модификации при наследовании класса. Такой подход применяется если Вы хотите быть уверенны, что этот метод не будет переопределен в дочерних классах и поведение класса не изменится.
Вторая причина - final методы более эффективны. Если Вы делаете метод с модификатором final, Вы тем самым разрешаете компилятору все вызовы этого метода превратить во внутренние (inline) вызовы. Когда компилятор видит final метод он может(на свое усмотрение) пропустить нормальный метод добавления кода, т.е. обычного исполнения метода (поместить аргументы в стек, перепрыгнуть на код метода и выполнить его, перепрыгнуть обратно на стек аргументов и очистить их, разобраться с возвращаемым значением) и заменить вызов метода на копию актуального кода из тела метода. При этом снижается загрузка машины при выполнении вызова метода. Естественно, если ваш метод велик, тогда ваш код распухнет и вероятно Вы не увидите никаких преимуществ по производительности от использования прямых вызовов, поскольку все повышения производительности при вызове будут съедены временем выполнения кода внутри самого метода. Поэтому Java компилятор способен определять такие ситуации и решать, когда осуществлять компиляцию final метода во внутренние вызовы. Но, все таки не следует слишком уж доверять компилятору и создавать final методы, только, если они действительно небольшие и Вы действительно хотите запретить их изменение при наследовании.
Любой private метод косвенным образом final. Поскольку Вы не можете получить доступ к private методу, Вы не можете переопределить его (даже если компилятор не выдаст сообщения об ошибке при переопределении, Вы все равно не сможете переопределить его, Вы просто создадите новый метод). Вы можете добавить спецификатор final к private методу, но это не добавит ему никаких дополнительных возможностей.
Эта особенность может создать неудобство, поскольку если Вы попытаетесь перекрыть private метод (который косвенно и final) то оно будет работать:
//: c06:FinalOverridingIllusion.java
// Это только выглядит так, как буд-то вы перекрыли
// private или private final метод.
class WithFinals {
// Идентично "private":
private final void f() {
System.out.println("WithFinals.f()");
}
// Так же автоматически "final":
private void g() {
System.out.println("WithFinals.g()");
}
}
class OverridingPrivate extends WithFinals {
private final void f() {
System.out.println("OverridingPrivate.f()");
}
private void g() {
System.out.println("OverridingPrivate.g()");
}
}
class OverridingPrivate2
extends OverridingPrivate {
public final void f() {
System.out.println("OverridingPrivate2.f()");
}
public void g() {
System.out.println("OverridingPrivate2.g()");
}
}
public class FinalOverridingIllusion {
public static void main(String[] args) {
OverridingPrivate2 op2 =
new OverridingPrivate2();
op2.f();
op2.g();
// Вы можете привести к базовому типу:
OverridingPrivate op = op2;
// Но Вы не можете вызвать методы:
//! op.f();
//! op.g();
// Так же здесь:
WithFinals wf = op2;
//! wf.f();
//! wf.g();
}
} ///:~
"Переопределение" может быть использовано только если это что-то является частью интерфейса базового класса. То есть, Вы должны быть способны привести метод к базовому типу объекта и вызвать тот же самый метод (как это делается будет объяснено в следующей главе). Если же метод private, то он не является частью интерфейса базового класса. Он это просто немного кода скрытого внутри класса, и просто так случилось, что он имеет то же имя, но если уж Вы создаете метод с модификатором public, protected или friendly в дочернем классе, то здесь нет связи с методом из базового класса. Поэтому private метод недоступный и эффективный способ скрыть какой-либо код, и при этом он не влияет на организацию кода в котором он был объявлен.
Когда Вы объявляете целый класс final (путем добавления в его определение ключевого слова final), Вы тем самым заявляете, что не хотите наследовать от этого класса или что бы кто-то другой мог наследовать от него. Другими словами, по некоторым причинам в вашем классе не должны делаться какие-либо изменения, или по причинам безопасности не могут быть созданы подклассы. В другом же случае, причиной сделать класс final может послужить эффективность выполнения кода класса, но здесь нужно быть уверенным, что все, что внутри класса уже оптимизировано как можно более максимально.
//: c06:Jurassic.java
// Создание целого final класса.
class SmallBrain {}
final class Dinosaur {
int i = 7;
int j = 1;
SmallBrain x = new SmallBrain();
void f() {}
}
//! класс Further расширяет Dinosaur {}
// Ошибка: Нельзя расширить класс 'Dinosaur'
public class Jurassic {
public static void main(String[] args) {
Dinosaur n = new Dinosaur();
n.f();
n.i = 40;
n.j++;
}
} ///:~
Заметьте, что поля данных могут быть final, а могут и не быть, по вашему выбору. Те же самые правила применимы и к final членам класса вне зависимости определен ли сам класс, как final. Определение класса, как final просто предотвращает дальнейшее от него наследование. Несмотря на это, поскольку он предотвращает наследование всех методов в классе final, то они безоговорочно тоже становятся final, поскольку нет способа для их переопределения. Так что компилятор имеет тоже полезное действие, как если бы Вы определили каждый из методов как final.
Вы можете добавить спецификатор final к методу в final классе, но это уже ничего означать не будет.
Класс - это основной строительный блок Java-программ. Любой класс состоит из данных и методов. Методы, входящие в каждый конкретный класс, как правило, определяют способы изменения и доступа к данным класса. Объединение в одном контейнере как самих данных, так и алгоритмов работы с ними - одно из ключевых свойств объектно-ориентированного программирования, значительно облегчающее повторное использование и обслуживание программного кода.
Конструктор в языке Java - это особый метод, который вызывается с целью создания нового экземпляра объекта. Конструктор некоего класса должен иметь то же имя, что и сам класс, и не должен возвращать никакого значения. Класс может иметь несколько конструкторов, но все они должны различаться между собой по количеству и типам параметров. Имена параметров при этом не учитываются - иными словами, вы не можете объявить два конструктора к одному и тому же классу, имеющие одинаковое количество параметров одинаковых типов, но по-разному именованных. Вот несколько примеров объявлений конструкторов:
class foo {
foo() {...} // конструктор без параметров
foo(int n) {...} // конструктор с одним параметром типа int
foo(String s) {...} // конструктор с одним параметром типа String
}
В этом примере объявление двух конструкторов с одним параметром допустимо, поскольку у каждого из конструкторов этот параметр имеет свой тип. В то же время вы не сможете объявить еще один конструктор как foo(int i), так как, хотя имя параметра и отличается, тип и количество параметров совпадают с одним из уже объявленных конструкторов.
Любой класс может иметь один деструктор - метод, который вызывается в тот момент, когда объект становится доступным для сборщика мусора. Строго говоря, вы не можете точно предсказать момент, в который будет вызван деструктор. В деструктор удобно поместить такие действия, как закрытие файлов, отключение от сети и т. п. В то же время деструктор не должен производить никакого взаимодействия с пользователем или с другими объектами программы, поскольку вы не можете знать, какие из этих объектов будут доступны в момент вызова деструктора.
Деструктор в языке Java должен иметь имя finalize. Он не имеет параметров и не возвращает никакого значения. Так, к приводившемуся выше в качестве примера классу foo можно добавить деструктор следующим образом:
class foo {
finalize() {...} // действия, которые нужно выполнить перед уничтожением объекта
}
В объявлениях класса можно использовать три модификатора - abstract, final или public. Они должны располагаться перед ключевым словом class. Вот, например, как объявляется класс foo с использованием двух из этих модификаторов:
public final class foo {...}
К общедоступному (public) классу можно получить доступ из других пакетов. Если же класс не объявлен как общедоступный, к нему могут обращаться только классы, входящие с ним в один пакет. В каждом пакете можно объявить только один общедоступный класс - вот почему файл с исходным текстом может содержать только один общедоступный класс или интерфейс.
Модификатор final означает, что данный класс нельзя расширять, то есть нельзя строить на его основе новые классы. Некоторые из классов и интерфейсов прикладного программирования Java определены с этим модификатором. Например, классы Array и String определены с модификатором final, поскольку они являются гибридными классами - то есть экземпляры этих классов не являются в полном смысле слова объектами, а часть кода этих классов реализована непосредственно в компиляторе. Обычно объявление класса с модификатором final не имеет большого смысла, так как при этом вы теряете возможность определять подклассы данного класса. Тем самым вы лишаетесь преимуществ объектно-ориентированного подхода, и ни вы сами, ни другие люди не смогут пользоваться написанным вами кодом. Однако модификатор final может быть полезным в некоторых случаях, в которых переносимость и возможность наследования являются нежелательными.
Определяя класс с модификатором abstract, вы тем самым сообщаете компилятору, что один или несколько методов этого класса является абстрактными. Абстрактным методом называется такой, который в момент своего объявления не содержит никакого кода; код может добавляться в этот метод позднее в подклассах данного класса, которые унаследуют этот абстрактный метод. Абстрактный класс не может быть реализован (то есть нельзя создать экземпляр данного класса), но его можно расширять, создавая подклассы и заполняя в них абстрактные методы нужными алгоритмами. Подкласс абстрактного класса обязательно должен либо сам быть объявлен абстрактным, либо реализовать все абстрактные методы. Такой подход удобен в тех случаях, когда на ранних этапах работы ясна структура программы, но еще не выработаны конкретные алгоритмы. Если попытаться объявить целый класс (а не метод) абстрактным, такой класс будет называться интерфейсом (interface). Подробнее об интерфейсах говорится в разделе "Интерфейсы" данной главы, а также в главе 3, "Объектная ориентация в Java".
Отношение наследования реализуется с помощью ключевого слова extends. Любой класс может расширять (или, иными словами, быть наследником) не более одного другого класса. Таким образом, множественное наследование явным образом в языке Java не поддерживается. Тем не менее использование интерфейсов позволяет реализовать некоторые свойства множественного наследования.
У всех объектов в Java есть один общий класс-родитель, который называется Object. Если в спецификации класса не указан класс-родитель, то по умолчанию вновь создаваемый класс становится подклассом класса Object. Для явного указания класса-родителя применяется ключевое слово extends. Так, если мы определили выше класс foo, мы можем создать его подкласс bar следующим образом:
class bar extends foo {...}
Подкласс наследует все методы и переменные своего класса-родителя. Вы можете переопределить или затенить какие-то из этих переменных и методов, использовав в подклассе соответствующий идентификатор с другим значением. Чтобы при этом получить доступ к затененному идентификатору, можно воспользоваться особой переменной super, которая указывает на класс-родитель, ближайший к данному в иерархии классов. Допустим, что в классе foo есть метод под названием test, а в подклассе bar этот метод затенен созданием другого метода с тем же именем. Чтобы получить доступ к исходному методу test, определенному в foo, нужно прибегнуть к следующей записи:
class bar extends foo {
void test() {
super.test(); // вызов метода test, определенного в классе-родителе (foo.test)
...
}
}
СОВЕТ Попытка определить "порочный круг" зависящих друг от друга классов приведет к сообщению об ошибке при компиляции. Иными словами, класс Б не может быть подклассом А, если класс А уже определен как подкласс класса Б.
Класс может являться реализацией одного или нескольких интерфейсов. Интерфейсом называют класс, все методы которого абстрактны. Ключевое слово implements, за которым следует имя интерфейса, должно стоять последним в объявлении класса. Таким образом, полный синтаксис объявления класса таков:
<модификаторы класса> class <имя класса> extends
<имя класса-родителя> implements <имя интерфейса> {...}
В этом объявлении все, кроме ключевого слова class и имени самого определяемого класса, является факультативным. Если класс является реализацией интерфейса, он должен заполнить каким-то кодом методы, определенные в данном интерфейсе. Единственным исключением из этого правила является случай, когда сам определяемый класс является абстрактным; при этом конкретная реализация методов интерфейса может быть переложена на подклассы данного класса.
Допустим, у нас есть интерфейс shapeInterface, который содержит два метода - draw и erase. Тогда мы можем определить класс с именем shape, реализующий этот интерфейс:
class shape implements shapeInterface {
void draw() {...}
void erase() {...}
}
Если вы хотите создать класс, реализующий сразу несколько интерфейсов, то имена этих интерфейсов нужно перечислить после ключевого слова implements через запятую. В таком случае создаваемый класс должен реализовать все методы каждого интерфейса. Допустим, мы имеем два интерфейса, называемые shapeInterface и moveableInterface. В этом случае мы можем определить класс dragDrop, реализующий оба этих интерфейса:
class dragDrop implements shapeInterface, moveableInterface
{...}
Более содержательное обсуждение интерфейсов вы найдете в главе 3, "Объектная ориентация в Java". Синтаксис объявления интерфейсов приведен ниже в этой главе в разделе "Интерфейсы".
Определяя внутри класса переменные, вы можете воспользоваться некоторыми из модификаторов. Присутствие этих модификаторов изменяет такие свойства переменных, как доступность их из других классов, поведение переменных в условиях многопотоковости, а также то, является ли переменная статической или конечной (final). В объявлениях переменных можно указывать следующие модификаторы: public, private, protected, static, final, transient и volatile.
На доступность переменной из других частей программы влияют модификаторы public, protected, private protected и private. Переменная, объявленная с ключевым словом public, доступна как в том пакете, в котором она объявлена, так и в любом другом пакете. Из всех модификаторов данный накладывает наименьшие ограничения на доступность переменной.
Переменная, объявленная с модификатором protected в некоем классе С, доступна всем классам в данном пакете, а также во всех классах, являющихся подклассом класса С. Иными словами, доступа к этой переменной не имеют те классы, которые не входят в данный пакет и не являются подклассами того класса, в котором эта переменная определена.
Если же переменная в классе С объявлена как private protected, то это означает, что к ней могут получить доступ только подклассы класса С. Другим классам, входящим в тот же пакет, эта переменная недоступна. Таким образом, если вам нужно ограничить сферу действия переменной только самим классом и его подклассами, используйте сочетание ключевых слов private protected.
Наконец, модификатор, сильнее всего ограничивающий доступность переменной, - модификатор private - делает переменную невидимой нигде за пределами данного класса. Даже подклассы данного класса не смогут обращаться к переменной, объявленной как private.
Вот пример, в котором используются все четыре модификатора доступа:
class circle {
public String className;
protected int x,y;
private protected float radius;
private int graphicsID;
}
Если переменная объявлена с ключевым словом static, это означает, что данная переменная будет общей для всех реализаций этого класса. Место для такой переменной выделяется во время компиляции, поэтому вам не нужно будет создавать экземпляр класса, чтобы получить доступ к этой переменной. Например, таким образом в классе Math пакета java.lang определена переменная-константа PI. Без какой-либо реализации данного объекта мы можем сразу получить доступ к этой переменной:
System.out.println("PI = " + Math.PI);
Модификатор final говорит о том, что значение данной переменной не может быть изменено. Объявление этой переменной обязательно должно содержать инициализацию - присвоение начального значения, а любая попытка изменить значение переменной в других местах программы приведет к сообщению об ошибке при компиляции. Модификатор final обычно используется в определениях констант. Кроме того, неизменяемые константы обычно имеют модификаторы public и static. Так, в некоем классе foo можно определить константу Answer следующим образом:
class foo {
public static final int Answer = 42;
}
Наконец, модификаторы transient и volatile относятся к той части языка, которая отвечает за многопотоковое исполнение программ. Основная цель этих модификаторов - облегчить компилятору оптимизацию многопотокового кода. Переменная, объявленная с ключевым словом transient, не может принадлежать объекту в резидентном состоянии (persistent state). Ключевое слово transient будет использовано для реализации некоторых функций в будущих версиях языка.
Переменная, объявленная как volatile, - это такая переменная, о которой известно, что она может изменяться асинхронно. Переменные, объявленные с этим ключевым словом, будут записываться на свое место в памяти после каждого использования и вновь загружаться по мере необходимости. Ключевые слова transient и volatile зарезервированы для использования в будущем, хотя в программах их можно употреблять уже сейчас. (Подробнее о переменных, объявленных с ключевым словом volatile, вы узнаете в главе 11, "Многопотоковость".)
При объявлении метода могут использоваться модификаторы, перечисленные в табл. 4-13. Из них модификаторы public protected и private действуют точно так же, как и при объявлении переменных, и употребляются для ограничения доступа к методам.
Таблица 4-13. Модификаторы методов
public |
protected |
private |
static |
abstract |
final |
native |
synchronized |
Модификатор static позволяет сделать метод доступным даже в том случае, когда класс, к которому он принадлежит, не реализован. Методы, объявленные статическими, неявным образом используют также модификатор final - иными словами, переопределить статический метод невозможно. В пределах статического метода вы можете обращаться только к тем членам данного класса, которые также являются статическими.
Абстрактный метод, определенный с модификатором abstract, - это такой метод, который будет реализован не в экземпляре данного класса, а лишь в каком-то из его подклассов. Если хотя бы один из методов класса является абстрактным, этот класс также становится абстрактным и уже не может быть реализован. Если все методы класса являются абстрактными, то такой класс, вероятно, имеет смысл объявить как интерфейс.
Метод, определенный с модификатором final, не может быть переопределен в подклассе данного класса. По сути, тем же свойством обладает и метод, объявленный с модификатором private, - он также не может быть переопределен. Оптимизирующий компилятор, возможно, будет производить встраивание такого метода для повышения скорости работы программы - это значит, что во все места, где данный метод вызывается, компилятор вместо вызова будет копировать сам код метода. При этом за счет увеличения объема программы иногда удается получить заметный выигрыш в скорости. Многие компиляторы C/C++ также пользуются таким методом оптимизации.
Ключевое слово synchronized применяется к тем методам, которые требуют, чтобы монитор данного класса был заперт прежде, чем можно будет выполнять данный метод. Подробнее о синхронизации и многопотоковости мы поговорим в главе 11, "Многопотоковость".
Чтобы написать код, который работает с различными типами параметров с одним и тем же именем, можно совместить новый метод с существующим. Обычно применяется совмещение конструкторов класса, что позволяет принимать параметры, содержащие различную информацию для инициализации. Совмещать можно почти любые методы, в том числе конструкторы и обычные методы. Однако вы не можете совмещать деструктор класса, поскольку он не получает никаких параметров и вы не сможете проверить, когда или как этот метод вызывается.
Чтобы совместить методы, нужно создать новый метод, используя то же самое имя и тип возвращаемого значения, но другие параметры. Каждый совмещенный метод должен быть уникальным, и уникальность эта определяется количеством параметров и их типами, но не их именами. Следующий фрагмент кода приведет к появлению ошибки:
class foo {
foo(int i) {...} // не будет скомпилирован
foo(int j) {...} // то же самое количество и тип параметров
}
В приведенном выше фрагменте кода делается попытка создать два метода с одним параметром типа int. Вы не можете совместить два метода, имеющих одинаковое количество параметров одинаковых типов.
Вот одно важное замечание насчет совмещения: вам необходимо знать, какой тип передается в метод. Сравните два метода, объявленных ниже:
class foo {
foo(int i) {}
foo(byte j) {}
}
Этот код скомпилируется, но обратите особое внимание на то, что вы определили. Если вы передадите в этот метод переменную типа short или long, компилятор выдаст ошибку. Java не может выполнять преобразование типов при вызове совмещенных методов. Как уже упоминалось выше, отсутствие явного преобразования типов является важным отличием языка Java от C/C++.
Класс
Класс есть ключевое понятие в объектно-ориентированном программировании, под которое и заточена Java. Класс описывает содержание и поведение некой совокупности данных и действий над этими данными.
К примеру, если мы моделируем прямоугольную комнату классом Комната, то данными могут быть: длина, ширина и высота, двери, электророзетки, мебель. Заметим, что на уровне класса мы ещё не знаем, о которой комнате идет речь, но точно знаем, что это не ящик (который тоже имеет длину, высоту и ширину), а именно комната. Действиями могут быть: вычисление объема, помещение и изъятие мебели, открытие дверей. Чтобы вычислить объем комнаты или наклеить обои, нам не нужны ее размеры, о своих размерах каждая конкретная комната знает сама.
Object - это экземпляр класса. В нашем примере - это может быть какая-то конкретная комната с конкретными размерами, причем количество комнат не ограничено. Предположим у нас есть два экземпляра комнат: спальня и кабинет. Теперь мы можем совершенно не зная с какой комнатой имеем дело узнать ее объем, т.к. вычисление объема - это свойство которое работает для любой комнаты.
Определение класса на языке Java с помощью оператора class:
class MyClass {
String name = "Example";
// "Конструктор"
public MyClass(String name) {
this.name = name;
}
// "Метод"
public String getName() {
return name;
}
}
Создание экземпляра класса:
MyClass my = new MyClass("Example 2");
В Java (как и в C++) используются статические методы (англ. staticmethod), которые задаются при помощи ключевого словаstatic. Статические поля имеют тот же смысл, что и в C++: каждое такое поле является собственностью класса, поэтому для доступа к статическим полям не требуется создавать экземпляры соответствующего класса.
Например, математические функции, реализованные в классе Math, представляют собой, как раз, статические методы данного класса. Поэтому можно писать
double x = Math.sin(1);
вместо
Math m = new Math();
double x = m.sin(1);
Поскольку статические методы существуют независимо от объектов (экземпляров класса), они не имеют доступа к обычным (нестатическим) полям и методам данного класса. В частности, при реализации статического метода недопустимо использовать идентификатор this.
Особенности статических методов:
Java-технология позволяет использовать метод finalize() (финализировать), чтобы произвести необходимую очистку перед тем, как сборщик мусора извлекает объект из памяти. Этот метод вызывается для объекта сборщиком мусора, когда сборщик мусора вычисляет, что ссылок к объекту больше нет. Это описано в классе Object, а значит, это наследуется всеми классами. Подкласс отменяет метод finalize(), чтобы освободиться от системных ресурсов или для ещё одной очистки:
protected void finalize() throws Throwable |
Если незарегистрированное исключение генерируется методом finalize(), то исключение игнорируется и финализация этого объекта прекращается.
Метод finalize() будет активизирован только один раз за время существования объекта.
Возможно использование метода finalize() любого объекта, чтобы защитить его от утилизации. Но в этом случае сборщик мусора уже не активирует finalize() для этого объекта.
Метод finalize() всегда будет активизирован один раз перед тем, как объект будет удалён сборщиком мусора. Однако, возможно, что метод finalize() не будет активизирован для данного объекта за всё время его существования, так как он может не подлежать утилизации.
Java package(пакет Java)— механизм, позволяющий организовать Java классы в пространства имен. Пакет (package) — это некий контейнер, который используется для того, чтобы изолировать имена классов.
Java пакеты могут содержаться в сжатом виде в JAR файлах. Обычно в пакеты объединяют классы одной и той же категории, либо предоставляющие сходную функциональность.
Оператор import
После оператора package, но до любого определения классов в исходном Java-файле, может присутствовать список операторов import. Пакеты являются хорошим механизмом для отделения классов друг от друга, поэтому все встроенные в Java классы хранятся в пакетах. Общая форма оператора import такова:
import пакет1 [.пакет2].(имякласса|*);
Здесь пакет1 — имя пакета верхнего уровня, пакет2 — это необязательное имя пакета, вложенного в первый пакет и отделенное точкой. И, наконец, после указания пути в иерархии пакетов, указывается либо имя класса, либо метасимвол звездочка. Звездочка означает, что, если Java-транслятору потребуется какой-либо класс, для которого пакет не указан явно, он должен просмотреть все содержимое пакета со звездочкой вместо имени класса. В приведенном ниже фрагменте кода показаны обе формы использования оператора import :
import java.util.Date
import java.io.*;
ЗАМЕЧАНИЕ
Но использовать без нужды форму записи оператора import с использованием звездочки не рекомендуется, т.к. этоможет значительно увеличить время трансляции кода (на скорость работы и размер программы это не влияет).
Все встроенные в Java классы, которые входят в комплект поставки, хранятся в пакете с именем java. Базовые функции языка хранятся во вложенном пакете java.lang. Весь этот пакет автоматически импортируется транслятором во все программы. Это эквивалентно размещению в начале каждой программы оператора
import java.lang.*;
Если в двух пакетах, подключаемых с помощью формы оператора import со звездочкой, есть классы с одинаковыми именами, однако вы их не используете, транслятор не отреагирует. А вот при попытке использовать такой класс, вы сразу получите сообщение об ошибке, и вам придется переписать операторы import, чтобы явно указать, класс какого пакета вы имеете ввиду.
class MyDate extends Java.util.Date { }
Ограничение доступа
Java предоставляет несколько уровней защиты, обеспечивающих возможность тонкой настройки области видимости данных и методов. Из-за наличия пакетов Java должна уметь работать еще с четырьмя категориями видимости между элементами классов :
• Подклассы в том же пакете.
• Не подклассы в том же пакете.
• Подклассы в различных пакетах.
• Классы, которые не являются подклассами и не входят в тот же пакет.
В языке Java имеется три уровня доступа, определяемых ключевыми словами: private (закрытый), public (открытый) и protected (защищенный), которые употребляются в различных комбинациях. Содержимое ячеек таблицы определяет доступность переменной с данной комбинацией модификаторов (столбец) из указанного места (строка).
private |
модификатор отсутствует |
private protected |
protected |
public |
|
тот же класс |
да |
да |
Да |
да |
да |
подкласс в том же пакете |
нет |
да |
Да |
да |
да |
независимый класс в том же пакете |
нет |
да |
Нет |
да |
да |
подкласс в другом пакете |
нет |
нет |
Да |
да |
да |
независимый класс в другом пакете |
нет |
нет |
Нет |
нет |
да |
Разработчики должны что-то предпринять, чтобы избежать возможности для двух пакетов иметь одно имя, выбирая уникальные имена пакетадля пакетов. Это позволяет пакетам быть легко и автоматически устанавливаемыми и каталогизируемыми. Этот раздел определяет стандартное соглашение, не предписанное компилятором Явы, для создания таких уникальных имен пакета. Система Явы стремится обеспечить автоматическое преобразование локальных и случайных имен пакета в уникальные имена, описанные здесь.
Если уникальные имена пакетов не используются, то конфликты имен пакетов могут возникать далеко от места создания любого из находящихся в противоречии пакетов. Это может создать неразрешимую ситуацию для пользователя или программиста. Класс ClassLoader (§20.14) из стандартного окружения виртуальной машины Явы может быть использован, чтобы изолировать пакеты с одним и тем же именем друг от друга в тех случаях, где пакеты вынуждены взаимодействовать, но не совсем понятным способом.
Когда вы создаете уникальное имя пакета, то сначала вы пишите имя домена в Internet, которому вы принадлежите (или имя организации, которая имеет к нему принадлежит ), например, Sun.COM. Затем вы переставляете компонент за компонентом, чтобы получить в этом примереCOM.Sun, и используете его как префикс для ваших имен пакета, используя соглашение, разработанное внутри вашей организации.
Такое соглашение могло бы определять, что некоторый компонент имени каталога может означать отделение, проект, машину, или имя входа в систему. Некоторые примеры:
COM.Sun.sunsoft.DOE
COM.Sun.java.jag.scrabble
COM.Apple.quicktime.v2
EDU.cmu.cs.bovik.cheese
GOV.whitehouse.socks.mousefinder
Первый компонент уникального имени пакета всегда пишется заглавными буквами ASCII и должен быть одним из имен верхнего уровня, в настоящее время COM, EDU, GOV, МIL, NET, ORG, или одного из двухбуквенных английских кодов, идентифицирующих страны как определено в ISO Standard 3166, 1981. Для подробной информации, обратитесь к документам, хранящимся вftp://rs.internic.net/rfc, например, rfc920.txt и rfc1032.txt.
Имя пакета не обязательно означает, где пакет сохранен в Internet; например, пакет, именованный EDU.cmu.cs.bovik.cheese не обязательно доступен из адреса Internet cmu.EDU или из cs.cmu.EDU или из bovik.cs.cmu.EDU. Соглашение в Яве для создания уникальных имен пакета - просто способ добавления к существующему, широко известного уникального имени вместо того, чтобы создавать отдельную регистрацию для имени пакета в Яве.
Если вам нужно получить новое имя домена в Internet, вы можете взять анкету из ftp://ftp.internic.net и отправить предложенные полные формы по электронной почте в domreg@internic.net. Чтобы выяснять, каковы в настоящее время зарегистрированные имена областей, вы можете подсоединиться к rs.internic.net и использовать средство whois.
Спецификаторы доступа в Java
Спецификаторы доступа Java public, protected и private располагаются перед каждым определением каждого члена в Вашем классе, независимо от того, метод это или просто поле. Каждый спецификатор доступа определяет доступ только для одного конкретного определения. В этом - явное различие с языком C++, в котором спецификатор доступа определяет доступ для всех последующих определений, пока не встретится другой спецификатор доступа.
Так или иначе, у всего имеется какой-то тип доступа. Далее Вы узнаете все о различных типах доступа, начиная с типа доступа по умолчанию.
Дружественный доступ “Friendly”
А что, если Вы вообще не определяете спецификатор доступа, как это было сделано во всех примерах до настоящей главы? Доступ по умолчанию не имеет ключевого слова, но обычно называется дружественным - “friendly.” Это значит, что все другие классы в том же пакете имеют доступ к дружественным членам, но для классов за пределами этого пакета, члены являются приватными (private). Т.к. файл модуля компиляции может принадлежать только одному пакету, все классы внутри этого единичного модуля компиляции автоматически являются дружественными друг другу. Таким образом, говорят, что дружественные элементы имеют доступ на уровне пакета.
Дружественный доступ позволяет Вам объединять связанные классы в пакете, так, что они могут легко общаться друг с другом. Когда Вы располагаете классы вместе в одном пакете, (определив таким образом совместный доступ для дружественных членов), Вы “владеете” кодом в этом пакете. Во многих языках, Вам волей-неволей приходится организовывать определения в файлах, но Java Вас заставляет создавать их в разумной форме. К тому же, Вы, возможно, захотите исключить классы, которые не должны иметь доступ к классам в том же пакете.
Класс управляет тем, какой код имеет доступ к его членам. И нет никакого магического способа “прорваться внутрь.” Код из другого пакета не может появиться и сказать, “Привет, Я друг Боба!” и затем посмотреть все защищенные, дружественные и приватные члены Боба. Единственный путь получить доступ, это:
Сделать этот член публичным. И кто угодно, откуда угодно сможет получить к нему доступ.
Сделайте это член дружественным, удалив все спецификаторы доступа, и расположите классы в одном пакете.
Как Вы увидите в Главе 6, когда наследование определено, унаследованный класс получает доступ к защищенным членам, а также к публичным членам (но не приватным). Этот класс может получить доступ к дружественным членам, только если эти два класса находятся в одном пакете. Но Вам не стоит беспокоиться об этом сейчас.
Предоствавьте методы “accessor/mutator” (также известные как “get/set” методы), которые читают и изменяют значение какого-то поля класса. Это самый цивилизованный подход в терминах ООП, и это основной подход в JavaBeans, как Вы увидите в Главе 13.
public: интерфейсный доступ
Если Вы используете ключевое слово public, это значит, что объявление, следующее сразу за этим словом, доступно всем, и, конечно, клиентскому программисту, который использует эту библиотеку. Предположим, что Вы создаете пакет dessert, содержащий следующий модуль компиляции:
//: c05:dessert:Cookie.java
// Создаем библиотеку.
package c05.dessert;
public class Cookie {
public Cookie() {
System.out.println("Cookie constructor");
}
void bite() { System.out.println("bite"); }
} ///:~
Запомните, Cookie.java должен располагаться в каталоге c05\dessert (с05 означает пятую главу этой книги), который должен быть доступен по одному из путей в CLASSPATH. Не надейтесь, что Java всегда просматривает текущий каталог, как один из начальных каталогов для поиска классов. Если Вы не добавите путь "." в переменную среды CLASSPATH, Java не будет этого делать.
Теперь, если Вы создадите программу, использующую Cookie:
//: c05:Dinner.java
// Использует библиотеку.
import c05.dessert.*;
public class Dinner {
public Dinner() {
System.out.println("Dinner constructor");
}
public static void main(String[] args) {
Cookie x = new Cookie();
//! x.bite(); // Недоступно
}
} ///:~
Вы сможете создать объект Cookie, т.к. его конструктор и сам класс являются публичными. (Далее Вы больше узнаете о концепции публичных классов.) Однако, метод bite( ) недоступен внутри Dinner.java т.к. bite( ) остается дружественным только внутри пакета dessert.
Пакет по умолчанию
Вы, возможно, удивитесь, когда узнаете, что следующий код компилируется, хотя Вам может показаться, что он нарушает правила языка Java:
//: c05:Cake.java
// Получает дочтуп к классу
// в другом модуле комиляции
class Cake {
public static void main(String[] args) {
Pie x = new Pie();
x.f();
}
} ///:~
Другой файл в том же каталоге содержит следующее:
//: c05:Pie.java
// Другой класс.
class Pie {
void f() { System.out.println("Pie.f()"); }
} ///:~
Вначале Вы можете посчитать эти файлы абсолютно чужими, и все же Cake может создать объект Pie и вызвать его метод f( )! (Конечно, Вам нужно, чтобы CLASSPATH содержал ".", иначе файлы не будут компилироваться.) Вы можете подумать, что и класс Pie и его метод f( ) являются дружественными и недоступны объекту Cake. То, что они дружественны - это верно! А причина, по которой они доступны в Cake.java в том, что они находятся в одном и том же каталоге и не имеют конкретного имени пакета. Java считает эти файлы частью “пакета по умолчанию” для этого каталога, и поэтому, дружественными всем остальным файлам в этом каталоге.
private: Вы не можете коснуться этого!
Ключевое слово private означает, что никто не имеет доступа к этому члену, за исключением класса, в котором он находится. Другие классы в том же пакете не смогут получить доступ к приватным членам. С другой стороны, очень часто пакет создается группой людей, работающих вместе и private позволит Вам свободно изменять члены класса, не беспокоясь о том, что это может нарушить работу другого класса в том же пакете.
Дружественного доступа по умолчанию часто достаточно для скрытия реализации; запомните: “дружественный” член недоступен пользователю пакета. Это здорово, т.к. доступ по умолчанию - то, что Вы обычно используете (и это именно то, что Вы получаете, когда вообще не ставите идентификаторы доступа). Таким образом, Вы можете подумать, что доступ к членам должен быть публичным для клиентского программиста, и в результате, Вам не нужно использовать ключевое слово private, т.к. Вы можете прожить без него. Однако, использование private очень важно, особенно в многопоточных программах. (Как Вы увидите в Главе 14.)
Вот пример использования private:
//: c05:IceCream.java
// Демонстрирует ключевое слово "private".
class Sundae {
private Sundae() {}
static Sundae makeASundae() {
return new Sundae();
}
}
public class IceCream {
public static void main(String[] args) {
//! Sundae x = new Sundae();
Sundae x = Sundae.makeASundae();
}
} ///:~
Этот пример показывает использование private: Вам может потребоваться контроль создания объекта и предотвращение прямого доступа к какому-нибудь конструктору (или всем конструкторам). В примере выше, Вы не можете создать объект Sundae с помощью его конструктора; для этого Вы должны вызвать метод makeASundae( )[33].
Любой метод, который Вы считаете вспомогательным для этого класса можно сделать приватным, что будет гарантировать, что Вы не использовали случайно этот метод в другом месте того же пакета, и в результате не можете изменить либо удалить его. Создание метода приватным, гарантирует именно такой результат.
Это верно и для приватных полей внутри класса. Если Вы не собираетесь раскрывать реализацию (что бывает реже, чем Вы думаете), Вам следует сделать все поля приватными. Однако, если ссылка на объект является приватной внутри класса, это не значит, что другой объект не может иметь публичную ссылку на тот же объект. (Смотрите приложение A об алиасах.)
protected: “тип дружественного доступа”
Спецификатор доступа protected требует дополнительных усилий для понимания. Но Вы должны знать, что Вам не требуется понимать этот раздел, чтобы продолжать дальнейшее чтение разделов о наследовании (Глава 6). Но для завершенности, здесь представлено краткое описание и примеры использования ключевого слова protected.
Ключевое слово protected разрешает концепцию названную наследование, которое берет существующий класс и добавляет в него новые члены не затрагивая исходного (базового класса). Вы также можете изменить поведение существующих методов класса. Для наследования от существующего класса Вы говорите, что новый класс расширяет (extends) существующий класс:
class Foo extends Bar {
Дальнейшее определение класса выглядит также.
Если Вы создаете новый пакет и наследуете класс из другого пакета, то единственные члены, к которым Вы имеете доступ, это публичные члены в исходном пакете. (Конечно, если наследование происходит в том же самом пакете, Вы имеете нормальный пакетный доступ для всех “дружественных” членов.) Но иногда, создатель базового класса хочет разрешить доступ к конкретному члену только для наследуемого класса, но не всему миру в целом. Именно это делает protected. Если Вы рассмотрите снова файл Cookie.java, нижеследующий класс не может получить доступ к “дружественному” члену:
//: c05:ChocolateChip.java
// Нет доступа к члену
// другого класса.
import c05.dessert.*;
public class ChocolateChip extends Cookie {
public ChocolateChip() {
System.out.println(
"ChocolateChip constructor");
}
public static void main(String[] args) {
ChocolateChip x = new ChocolateChip();
//! x.bite(); // Нет доступа к bite
}
} ///:~
Одна из интересных особенностей наследования заключается в том, что если метод bite( ) существует в классе Cookie, то он также существует в любом наследуемом от Cookie классе. Но, т.к. bite( ) является “дружественным” в другом пакете, он недоступен нам в этом. Конечно, Вы можете сделать его публичным public, но тогда каждый будет иметь к нему доступ, и может быть, Вы не хотите этого. Если мы изменим класс Cookie, как показано ниже:
public class Cookie {
public Cookie() {
System.out.println("Cookie constructor");
}
protected void bite() {
System.out.println("bite");
}
}
то метод bite( ) будет иметь “дружественный” доступ внутри пакета dessert, а также будет доступен всем наследникам класса Cookie. Однако, он - не публичный.
Интерфейс и реализация
Контроль доступа часто называют скрытием реализации. Завертывание методов и данных в классах в комбинации со скрытием реализации называется часто инкапсуляцией[34]. Результат - это тип данных с определенными характеристиками и поведением.
Контроль доступа накладывает ограничения по двум важным причинам. Первая - необходимость определения того, что клиентский программист может использовать, а что нет. Вы можете построить Ваш внутренний механизм в структуре класса, не беспокоясь о том, что клинетские программисты случайно воспримут внутреннюю реализацию как часть интерфейса, которой они должны использовать.
Отсюда следует вторая причина, это разделение описания и реализации. Если эта структура используется в нескольких программах, но клиентские программисты не могут ничего общения с публичными членами, то Вы можете менять как угодно все, что не является публичным (e.g., “дружественным,” защищенным, либо приватным), без модификаций клиентского кода.
Мы живем в мире объектно-ориентированного программирования, где class обычно описывает “класс объектов,” как Вы можете описать класс рыб или класс птиц. Любой объект, принадлежащий этому классу разделит эти характеристики и поведение. Класс - это описание того, как все объекты этого типа будут выглядеть и действовать.
В оригинальном объектно-ориентированном языке, Simula-67, ключевое слово class использовалось для описания нового типа данных. То же самое ключевое слово используется в большинстве объектно-ориентированных языков. Это основной момент целого языка: создание нового типа данных, который является чем-то большим, чем просто контейнером содержащим данные и методы.
Класс - основная концепция ООП в Java. Это одно из ключевых слов, которое не будет выделено жирным шрифтом в этой книге —чтобы не было путаницы со словом повторяемым также часто, как и “класс.”
Для упрощения, Вы можете выбрать стиль создания классов, в котором сначала располагаются публичные члены, затем защищенные, дружественные и, наконец, частные. Выгода в том что пользователь этого класса сможет, просматривая файл сначала, увидеть сразу то, что важно для него (публичные члены, т.к. к ним может быть получен доступ за пределами файла), и прекратить просмотр при достижении непубличных членов, которые являются частью внутренней реализации:
public class X {
public void pub1( ) { /* . . . */ }
public void pub2( ) { /* . . . */ }
public void pub3( ) { /* . . . */ }
private void priv1( ) { /* . . . */ }
private void priv2( ) { /* . . . */ }
private void priv3( ) { /* . . . */ }
private int i;
// . . .
}
Это всего лишь частично упростит чтение, т.к. описание и реализация все еще находятся вместе. То есть, Вы еще видите исходный код —реализацию—, поскольку она находится здесь же, в классе. К тому же, документация из комментариев, поддерживаемая утилитой javadoc (описанной в Главе 2) преуменьшает важность чтения кода клиентским программистом. Отображение интерфейса для пользователя класса это, на самом деле, занятие браузера классов, инструмента, чья работа состоит в том, чтобы просмотреть все доступные классы и показать Вам, что Вы можете делать с ними (т.е. показать все доступные члены), в удобной форме. К тому времени, как Вы прочитаете это, такие браузеры должны быть частью любой хорошей среды разработки Java.
Доступ класса
В Java, спецификаторы доступа могут также использоваться для определения того, какие классы внутри библиотеки будут доступны пользователям библиотеки. Если Вы хотите сделать класс доступным клиентскому программисту, Вы располагаете ключевое слово public где-нибудь перед открывающей фигурной скобкой тела класса. Это определяет, может ли клиентский программист создать объект этого класса.
Для контроля доступа к классу, спецификатор должен располагаться перед ключевым словом class. Итак, Вы можете написать:
public class Widget {
Если имя Вашей библиотеки mylib любой клиентский программист может получить доступ к Widget с помощью
import mylib.Widget;
либо
import mylib.*;
Однако, существует несколько дополнительных ограничений:
Может существовать только один публичный класс в одном модуле компиляции (файле). Идея состоит в том, что один модуль компиляции имеет один публичный интерфейс, представленный этим публичным классом. Он может иметь так много поддерживающих “дружественных” классов, сколько Вам необходимо. Если у Вас больше одного публичного класса в модуле компиляции, компилятор выдаст сообщение об ошибке.
Имя публичного класса должно полностью совпадать, с именем файла, содержащего соответствующий модуль компиляции, включая регистры символов. Так, например, для класса Widget, имя файла должно быть Widget.java, но никак не widget.java или WIDGET.java. Итак, Вы получите ошибку компиляции, если Вы с этим не согласны.
Возможно, но не типично, что у Вас будет модуль компиляции вообще без публичного класса. В этом случае, Вы можете называть файл как хотите.
А что, если у Вас есть такой класс внутри библиотеки mylib, который Вы используете для выполнения задач представленных классом Widget или каким-то другим публичным классом в mylib? Вы не хотите создавать документацию для клиентского программиста, и думаете, что когда-нибудь позже Вы захотите все изменить, либо вообще удалить класс, заменяя его другим. Чтобы иметь такую возможность, Вам нужно убедиться, что ни один клиентский программист не зависит от Ваших деталей реализации, скрытых внутри mylib. Для достижения этого, Вы удаляете ключевое слово public из класса, в этом случае он становится дружественным. (Этот класс может быть использован только внутри этого пакета.)
Обратите внимание, что класс не может быть private (это сделало бы его никому не доступным кроме самого этого класса), или protected[35]. Итак, у Вас есть только выбор из двух вариантов: “дружественный” или публичный. Если Вы не хотите, чтобы кто-то другой имел доступ к классу, Вы можете сделать все конструкторы приватными, этим запрещая любому кроме Вас, создание объекта этого класса внутри статического члена класса.[36]. Вот пример:
//: c05:Lunch.java
// Демонстрирует спецификаторы доступа к классу.
// Делает класс приватным
// с помощью приватных конструкторов:
class Soup {
private Soup() {}
// (1) Позволяет создание с помощью статического метода:
public static Soup makeSoup() {
return new Soup();
}
// (2) Создание статического объекта
// возвращается ссылка на запрос.
// (шаблон "Singleton"):
private static Soup ps1 = new Soup();
public static Soup access() {
return ps1;
}
public void f() {}
}
class Sandwich { // Использует Lunch
void f() { new Lunch(); }
}
// В файле только один публичный класс:
public class Lunch {
void test() {
// Вы не можете сделать это! Приватный контруктор:
//! Soup priv1 = new Soup();
Soup priv2 = Soup.makeSoup();
Sandwich f1 = new Sandwich();
Soup.access().f();
}
} ///:~
До сих пор, большинство методов возвращали либо void либо примитивный тип, и описание:
public static Soup access() {
return ps1;
}
может вначале привести в замешательство. Слово перед именем метода (access) говорит о том, что возвращает метод. Пока чаще всего был тип void, и это означало, что метод не возвращает ничего. Но Вы можете возвратить также ссылку на объект, что и происходит здесь. Этот метод возвращает ссылку на объект класса Soup.
Класс Soup показывает как предотвратить прямое создание класса, сделав все конструкторы приватными. Запомните, что если Вы не сознаете явно ни одного конструктора, конструктор по умолчанию (конструктор без аргументов) сам будет создан для Вас. Если Вы напишите конструктор по умолчанию, он не будет создаваться автоматически. Если Вы сделаете его приватным, то никто не сможет создать объект этого класса. Но сейчас, как кто-нибудь сможет использовать этот класс? Пример выше показывает два варианта. Первый, с помощью статического метода создается объект типа Soup и возвращается ссылка на него. Это может быть полезно, если Вы хотите выполнить несколько дополнительных операций с классом Soup, перед тем как его возвратить, либо если Вы хотите хранить количество создаваемых объектов типа Soup (возможно для контроля их популяции).
Второй вариант использует так называемый шаблон разработки, который описан в книге Thinking in Patterns with Java, доступной на с www.BruceEckel.com. Этот специфический шаблон называется “singleton” потому что он позволяет создавать только один объект. Объект класса Soup создается как статический приватный член класса Soup, и существует один и только один объект, и Вы не можете получить его никаким другим способом, кроме как с помощью публичного метода access( ).
Как ранее было упомянуто, если Вы вообще не ставите идентификатор доступа для класса, он становится “дружественным.” Это означает, что объект этого класса может быть создан в любом другом классе того же пакета, но не за его пределами. (Запомните, все файлы в одном каталоге не имеющие явного выражения package, принадлежат пакету по умолчанию для этого каталога.) Однако, однако, если статический член этого класса - публичный, то клиентский программист сможет получить доступ к этому статическому члену, даже если он не сможет создать объект этого класса.
Комментарий-Документация
Одна из вдумчивых частей языка Java в том, что его разработчики не предполагали, что написание кода будет единственно важным действием — они также подумали о его документации. Возможно, что наибольшая проблема с документированием кода - это поддержка этой документации. Если документация и код разделены, вы получаете трудности при изменении документации всякий раз, когда вы будете менять код. Решение выглядит легким: свяжите код с документацией. Наилегчайший путь для этого - поместить все в один файл. Однако для завершения картины вам нужен специальный синтаксис для пометки специальной документации и инструмент для выделения этих комментариев и помещения их в удобную форму. Это то, что делает Java.
Инструмент, для выделения этих комментариев, называется javadoc. Он использует ту же технологию, что и Java компилятор для поиска специальных ярлыков комментариев, помещенных в вашу программу. Он не только выделяет информацию, помеченную этими ярлыками, а так же помещает имя класса или имя метода, присоединяя его к комментарию. Этим способом вы можете получить при минимальной работе для генерации хорошей документации программы.
На выходе javadoc получается HTML файл, который вы можете просмотреть вашим Web броузером. Этот инструмент позволяет вам создавать и поддерживать единственный исходный файл и автоматически генерирует полезную документацию. Поскольку с javadoc мы имеем стандарт для создания документации и это достаточно легко, поэтому мы можем ожидать или даже требовать документации от Java библиотек.
Синтаксис
Все команды javadoc встречаются только внутри комментариев /**. Комментарий заканчивается */, как обычно. Есть два основных способа использовать javadoc: вставление HTML или использование “ярлыков документации”. Ярлыки документации являются командами, которые начинаются с ‘@’, которая помещается с начала строки комментария. (Однако лидирующая ‘*’ игнорируется.)
Есть три “типа” комментариев документации, которые соответствуют элементам, предшествующий комментарию: класс, переменная или метод. Таким образом, компоненты класса появляются прямо перед определением класса; компонент переменная появляется прямо перед определением переменной, а компонент метода появляется прямо перед определением метода. Как простой пример:
/** Компонент - класс */
public class docTest {
/** Компонент - переменная */
public int i;
/** Компонент - метод */
public void f() {}
}
Обратите внимание, что javadoc будет обрабатывать компоненты документации только для public и protected членов. Компоненты для private и “дружественных” членов (смотрите Главу 5) игнорируются, и вы не увидите их в выводе. (Однако вы можете использовать флаг -private для включения private членов наряду с остальными.) Это имеет смысл, так как только public и protected члены доступны извне файла, которые просматривают программисты-клиенты. Однако все комментарии для class включаются в выходной файл.
Вывод для приведенного выше кода - это HTML файл, который имеет тот же стандартный формат, как и вся остальная документация по Java, так что пользователи будут чувствовать себя комфортно с этим форматом и смогут легко ориентироваться в ваших классах. Цена за это - ввод приведенного выше кода, пропуск через javadoc и просмотр результирующего HTML файла.
Вставка HTML
Javadoc пропускает HTML команды для генерации HTML документа. Это позволяет вам использовать HTML; однако главный мотив состоит в позволении вам форматировать код, например так:
/**
* <pre>
* System.out.println(new Date());
* </pre>
*/
Вы также можете использовать HTML, как вы это делаете в других Web документах для форматирования обычного текста вашего документа:
/**
* Вы можете <em>даже</em> вставить список:
* <ol>
* <li> Первый элемент
* <li> Второй элемент
* <li> Третий элемент
* </ol>
*/
Обратите внимание, что внутри комментариев-документации звездочки в начале строки выбрасываются javadoc вместе с начальными пробелами. Javadoc переформатирует все так, что документ принимает внешний вид стандартного. Не используйте заголовки, такие как <h1> или <hr> в качестве встраиваемого HTML, потому что javadoc вставляет свои собственные заголовки, и вы можете запутаться в них.
Все компоненты документации — класс, переменная и метод — могут поддерживать вставку HTML.
@see: ссылка на другой класс
Все три типа компонентов документации (класс, переменная и метод) могут содержать ярлык @see, который позволяет ссылаться на документацию другого класса. Javadoc генерирует HTML с ярлыком @see, как гиперссылку на другой документ. Форма:
@see classname
@see fully-qualified-classname
@see fully-qualified-classname#method-name
Каждая вставка добавляет гиперссылку, входящую в раздел “See Also”, при генерации документации. Javadoc не проверяет гиперссылки, которые вы даете, чтобы убедится в их правильности.
Ярлыки документации класса
Наряду со встроенным HTML и ссылками @see, документация класса может включать ярлыки для информации о версии и имени автора. Документация класса также может быть использована для интерфейса (смотрите Главу 8).
@version
Вот форма:
@version version-information
в которой version-information - это любая важная для вас информация., которую вы хотите включить. Когда вносится флаг -version в командную строку javadoc, информация о номере версии специально будет включена в генерируемый HTML документ.
@author
Вот форма:
@author author-information
в которой author-information это, предположительно, ваше имя, но она так же может включать ваш электронный адрес или любую другую подходящую информацию. Когда флаг -author вносится в командную строку javadoc, информация об авторе специально включается в генерируемый HTML документ.
Вы можете иметь несколько ярлыков авторства для всего списка авторов, но они будут помещены последовательно. Вся информация об авторах будет собрана вместе в один параграф генерируемого HTML.
@since
Этот флаг позволяет вам указывать версию того кода, с которого началось использование определенной особенности. Вы увидите, что он появится в HTML документации Java для указания какая версия JDK используется.
Ярлыки документации переменных
Документация переменных может включать только встроенный HTML код и ссылки @see.
Ярлыки документации методов
Так же как и встроенная документация и ссылки @see, методы допускают ярлыки документации для параметров, возвращаемых значений и исключений.
@param
Эта форма:
@param parameter-name description
в которой parameter-name - это идентификатор в списке параметров, а description - текст, который может продолжаться на последующих строках. Описание считается законченным, когда обнаруживается новый ярлык документации. Вы можете иметь любое число таких ярлыков, предположительно, по одному для каждого параметра.
@return
Эта форма:
@return description
в которой description дает вам смысл возвращаемого значения. Описание продолжается на последующих строках.
@throws
Исключения будут продемонстрированы в Главе 10, но если коротко: это объекты, которые могут быть “выброшены” из метода, если метод окончится неудачей. Хотя только один объект исключение может появиться при вызове метода, обычно метод может производить любое число исключений различных типов, все они требуют описания. Форма ярлыка исключения следующая:
@throws fully-qualified-class-name description
в которой fully-qualified-class-name дает вам уникальное имя класса исключения, который где-нибудь определен, а description (которое может продолжаться на последующих линиях) говорит вам, почему этот тип исключения может возникнуть при вызове метода.
@deprecated
Это используется для ярлыка особенностей, которые были заменены улучшенными особенностями. Ярлык deprecated советует вам больше не использовать эту определенную особенность, так как когда нибудь в будущем она будет удалена. Метод, помеченный как @deprecated заставляет компилятор выдавать предупреждение, если он используется.
Пример документации
Здесь снова приведена первая Java программа, на этот раз с добавленными комментариями-документацией:
//: c02:HelloDate.java
import java.util.*;
/** Первая программа - пример Thinking in Java.
* Отображает строку и сегодняшнюю дату.
* @author Bruce Eckel
* @author www.BruceEckel.com
* @version 2.0
*/
public class HelloDate {
/** Единственная точка входа для класса и приложения
* @param args массив строк аргументов
* @return Возвращаемого значения нет
* @exception Исключения не выбрасываются
*/
public static void main(String[] args) {
System.out.println("Hello, it's: ");
System.out.println(new Date());
}
} ///:~
Первая строка файла использует мою собственную технику помещения ‘:’ как специальный маркер для строки комментария исходного имени файла. Эта строка содержит информацию о пути к файлу (в этом случае c02 указывает Главу 2), за которой следует имя файла [24]. Последняя строка также завершается комментарием, который означает конец исходного кода, который позволяет автоматически выбирать его из текста этой книги и проверять компилятором.
Классы и объекты, основные определения.
Основным понятием объектно-ориентированного программирования является класс.
Класс это лишь описание методов и свойств. Класс не создается и не используется в программе, т.е. “класс” – это синоним типа.
Объект - это переменная, которая имеет свойства и методы, описанные в классе, от которого он создается. Вы можете создать несколько объектов от одного класса. Проще говоря объект – это экземпляр класса.
Свойство – это та же самая переменная, только внутри объекта.
Метод – это та же самая функция, только внутри объекта.
Иерархия наследования (или иерархия категорий) представляет собой особый тип объединения сущностей, которые разделяют общие характеристики. Например, в организации работают служащие, занятые полный рабочий день (постоянные служащие), и совместители. Из их общих свойств можно сформировать обобщенную сущность (родовой предок) Сотрудник, чтобы представить информацию, общую для всех типов служащих. Специфическая для каждого типа информация может быть расположена в категориальных сущностях (потомках) Постоянный сотрудник и Совместитель.
Обычно иерархию наследования создают, когда несколько сущностей имеют общие по смыслу атрибуты, либо когда сущности имеют общие по смыслу связи (например, если бы Постоянный сотрудник и Совместитель имели сходную по смыслу связь "работает в" с сущностью Организация), либо когда это диктуется бизнес-правилами.
Полиморфизм описывает шаблон в ООП, в котором классы имеют различную функциональность при использовании общего интерфейса.
Прелесть полиморфизма заключается в том, что можно работать в коде с различными классами, и при этом не нужно знать, что за класс используется, потому что они имеют один и тот же интерфейс.
Аналогия полиморфизма в реальном мире - кнопка. Каждый знает как использовать кнопку - нужно просто нажать на нее. Но то, что "делает" кнопка в действительности, зависит от ее соединений и контекста использования. Если кто-то говорит, что нужно нажать кнопку, то уже известно, что нужно сделать, чтобы решить задачу.Одной из наиболее притягательных возможностей языка Java является возможность повторного использования кода. Но что действительно "революционно", так это наличие возможности выполнять не только простое копирование и изменение этого кода.
Такой подход использован в процедурных языках программирования, наподобие C, но он работает не очень хорошо. Как и все в Java, решение с повторным использованием кода вертится вокруг классов. Вы повторно используете код, создавая новый класс, но вместо того, что бы создавать его с нуля Вы используете уже существующие классы, которые кто-то уже создал и отладил.
Уловка в том, что бы использовать классы без копания в их исходном коде. В этой главе вы увидите два способа достижения этого. Первый - почти прямой: Вы просто создаете объекты ваших уже существующих классов внутри нового класса. Это называется "композиция" , потому, что новый класс создается из объектов уже существующих классов. Вы просто повторно используете функциональность кода, но не его самого.
Это исключительно, но синтаксис и поведение идентичны для обоих способов, для композиции и наследования (обусловлено тем, что оба пути создают новые типы из существующих типов). В этой главе Вы узнаете об обоих этих механизмах повторного использования.
До сих пор, композиция достаточно часто использовалась, Вы просто помещали ссылку на объект внутрь нового класса. Для примера, представьте себе, что Вы хотите получить объект, который хранит различные объекты типа String, пару примитивных типов и объект другого класса. Для не примитивных объектов Вы помещаете ссылки внутри вашего класса, но примитивные типы Вы определяете напрямую:
//: c06:SprinklerSystem.java
// Композиция для повторного использования кода.
class WaterSource {
private String s;
WaterSource() {
System.out.println("WaterSource()");
s = new String("Constructed");
}
public String toString() { return s; }
}
public class SprinklerSystem {
private String valve1, valve2, valve3, valve4;
WaterSource source;
int i;
float f;
void print() {
System.out.println("valve1 = " + valve1);
System.out.println("valve2 = " + valve2);
System.out.println("valve3 = " + valve3);
System.out.println("valve4 = " + valve4);
System.out.println("i = " + i);
System.out.println("f = " + f);
System.out.println("source = " + source);
}
public static void main(String[] args) {
SprinklerSystem x = new SprinklerSystem();
x.print();
}
} ///:~
Один из методов определенных в WaterSource особенный - toString( ). Вы узнаете позже, что все не примитивные объекты имеют метод toString( ) и он вызывается в особых ситуациях, когда компилятор хочет получить String, но эти объекты не являются таковыми. Так в выражении:
System.out.println("source = " + source);
компилятор видит Вашу попытку добавить объект String ("source = ") к WaterSource. И при этом для компилятора нет никакой разницы, поскольку Вы можете только добавить строку (String) к другой строке (String), при этом он "скажет": "Я преобразую source в String вызвав метод toString( )!" После выполнения этой операции компилятор объединит эти две строки и передаст результат в виде опять же строки в System.out.println( ). В любое время, когда вы захотите получить доступ к такой линии поведения с классом, Вам нужно только написать в нем метод toString( ) .
На первый взгляд, вы можете позволить Java принять на себя заботу об безопасности, потому, что компилятор автоматически создаст объекты для каждой ссылки, как в предыдущем коде. Например, вызов конструктора по умолчанию для WaterSource при инициализации source. Вывод печатаемых данных на самом же деле такой:
valve1 = null
valve2 = null
valve3 = null
valve4 = null
i = 0
f = 0.0
source = null
Примитивные типы-поля класса автоматически инициализируются в нулевое значение, как и было описано в главе 2. Но ссылки на объекты инициализируются в null и если Вы попытаетесь вызвать любой из этих методов, то Вы получите исключение. В действительности достаточно хорошо (и удобно) то, что Вы можете распечатать их без обработки исключения.
Этот пример дает понять, что компилятор только просто создает объект по умолчанию для каждой ссылки, потому, что в противном случае система может в отдельных случаях подвергнуться перегрузке. Если же Вы желаете инициализировать полностью эти ссылки, Вы можете сделать это такими способами:
При этом может быть уменьшена перегрузка системы в ситуациях, когда объектам нет необходимости быть созданным все время работы программы.
Все три подхода представлены ниже:
//: c06:Bath.java
// Инициализация конструктора с композицией.
class Soap {
private String s;
Soap() {
System.out.println("Soap()");
s = new String("Constructed");
}
public String toString() { return s; }
}
public class Bath {
private String
// Инициализация в точке определения:
s1 = new String("Happy"),
s2 = "Happy",
s3, s4;
Soap castille;
int i;
float toy;
Bath() {
System.out.println("Inside Bath()");
s3 = new String("Joy");
i = 47;
toy = 3.14f;
castille = new Soap();
}
void print() {
// Отложенная (ленивая) инициализация:
if(s4 == null)
s4 = new String("Joy");
System.out.println("s1 = " + s1);
System.out.println("s2 = " + s2);
System.out.println("s3 = " + s3);
System.out.println("s4 = " + s4);
System.out.println("i = " + i);
System.out.println("toy = " + toy);
System.out.println("castille = " + castille);
}
public static void main(String[] args) {
Bath b = new Bath();
b.print();
}
} ///:~
Заметьте, что в конструкторе Bath оператор выполняется до того, как произойдет инициализация. Если вы не проинициализируете объект в точке определения, то нет никакой гарантии, что Вы выполните инициализацию до того, как вы пошлете сообщение объекту и неизбежно получите исключение.
Ниже приведен вывод программы:
Inside Bath()
Soap()
s1 = Happy
s2 = Happy
s3 = Joy
s4 = Joy
i = 47
toy = 3.14
castille = Constructed
Когда вызывается print( ) он заполняется из s4 потому, что все поля были правильно инициализированы до того времени, когда они были использованы.
Наследование является неотъемлемой частью Java, впрочем, как и других ОО языков программирования. Это очевидно - Вы всегда осуществляете операцию наследования, когда создаете класс, даже если ваш класс не является наследником какого либо другого, потому, что Вы неявно наследуете стандартный корневой класс Java Object.
Синтаксис наследования похож на композицию, но процедура выполнения заметно отличается. Когда Вы наследуете, Вы "говорите": "Этот класс такой же, как тот старый класс!" Вы излагаете эту фразу в коде давая классу имя, как обычно, но до того, как начнете работать с телом класса, добавляете ключевое слово extends следующее до имени базового класса. Когда вы сделаете это, вы автоматически получите все поля данных и методы базового класса. Вот пример:
//: c06:Detergent.java
// Свойства и синтаксис наследования.
class Cleanser {
private String s = new String("Cleanser");
public void append(String a) { s += a; }
public void dilute() { append(" dilute()"); }
public void apply() { append(" apply()"); }
public void scrub() { append(" scrub()"); }
public void print() { System.out.println(s); }
public static void main(String[] args) {
Cleanser x = new Cleanser();
x.dilute(); x.apply(); x.scrub();
x.print();
}
}
public class Detergent extends Cleanser {
// Изменяем метод:
public void scrub() {
append(" Detergent.scrub()");
super.scrub(); // Вызываем метод базового класса
}
// Все методы наследования:
public void foam() { append(" foam()"); }
// Проверяем новый класс:
public static void main(String[] args) {
Detergent x = new Detergent();
x.dilute();
x.apply();
x.scrub();
x.foam();
x.print();
System.out.println("Testing base class:");
Cleanser.main(args);
}
} ///:~
Этот пример показывает несколько возможностей. Сперва в методе Cleanser append( ) , String-и конкатенируются с s при помощи оператора "+=", это один из операторов (с плюсом впереди), который перегружается Java для работы с типом String.
Во-вторых, оба Cleanser и Detergent содержат метод main( ). Вы можете создать main( ) для каждого из ваших классов и часто рекомендуется писать такой код для тестирования каждого из классов. Если же у Вас имеется множество классов в программе, то выполнится только метод main( ) того класса, который был вызван из командной стоки. Так что в этом случае, когда вы вызовите java Detergent, будет вызван метод Detergent.main( ) . Но так же вы можете вызвать java Cleanser для выполнения Cleanser.main( ), несмотря даже на то, что класс Cleanser не public . Эта техника помещения метода main( ) в каждый класс позволяет легко проверять каждый из классов программы по отдельности. И Вам нет необходимости удалять main( ) когда вы закончили проверки, Вы можете оставить его для будущих проверок.
Здесь Вы можете видеть, что Detergent.main( ) явно вызывает Cleanser.main( ) , передавая ему те же самые аргументы из командной строки(тем не менее, Вы могли были передать ему любой , массив элементов типа String).
Важно то, что все методы в Cleanser - public. Помните, если Вы оставите любой из спецификаторов доступа в состоянии по умолчанию, т.е. он будет friendly, то доступ к нему могут получить только члены этого же пакета. Поэтому в этом пакете все могут использовать эти методы, если у них нет спецификатора доступа. Detergent с эти проблем не имеет, к примеру. Но в любом случае, если класс из другого пакета попытается наследовать Cleanser он получит доступ только к членам со спецификатором public. Так что если Вы планируете использовать наследование, то в качестве главного правила делайте все поля private и все методы public. (protected так же могут получить доступ к наследуемым классам, но Вы узнаете об этом позже.) Естественно в частных случаях Вы должны делать поправки на эти самые частные случаи, но все равно это полезная линия поведения.
Замете, что Cleanser имеет набор методов из родительского интерфейса: append( ), dilute( ), apply( ), scrub( ), и print( ). Из-за того, что Detergent произошел от Ceanser (при помощи ключевого слова extends ) он автоматически получил все те методы, что есть в его интерфейсе, даже не смотря на то, что вы не видите их определенных в Detergent. Вы можете подумать о наследовании, а уже только затем о повторном использовании интерфейса.
Как видно в scrub( ) , возможно создать метод, который определяется в базовом классе, а затем уже его модифицировать. В таком случае, Вы можете захотеть вызвать метод внутри базового класса этот новый модифицированный метод. Но внутри scrub( ) вы не можете просто вызвать scrub( ), поскольку эта операция вызовет рекурсивный вызов, а это не то, что Вы хотите. Для разрешения этой проблемы в Java используется ключевое слово super , которое ссылается на superclass, который в свою очередь является классом, от которого произошел текущий класс. Поэтому выражение super.scrub( ) вызывает метод базового класса scrub( ).
При наследовании вы не ограничены в использовании методов базового класса. Вы можете так же добавлять новые методы в новый класс. Это сделать очень просто, нужно просто определить их. Метод foam( ) тому демонстрация.
В Detergent.main( ) вы можете увидеть, что у объекта Detergent Вы можете вызвать все методы, которые доступны в Cleanser так же, как и в Detergent (в том числе и foam( )).
Абстрактные базовые классы и интерфейсы
Часто при разработке вы хотите, чтобы базовый класс представлял только интерфейс для наследуемых классов. Это значит, что вы не хотите, чтобы кто-то реально создавал объект базового класса, а только выполнял обратное преобразование к нему, чтобы использовать интерфейс. Это достигается при создании абстрактного класса, используя ключевое слово abstract. Если кто-либо попробует создать объект абстрактного класса, компилятор предотвратит это. Это инструмент для навязывания определенного дизайна.
Вы также можете использовать ключевое слово abstract для описания методов, которые не будут реализованы сразу — как напоминание “это интерфейсная функция для всех типов, наследуемых от этого класса, но в этом месте она не имеет реализации”. Абстрактный метод может быть создан только внутри абстрактного класса. При наследовании такой метод должен быть реализован или наследуемый класс также станет абстрактным. Создание абстрактных методов позволяет вам помещать методы в интерфейс и не заботиться о возможности создания бессмысленного кода для тела этого метода.
Интерфейс похож на абстрактный класс, за исключением того, что все методы интерфейса неявно определены абстрактными.
Если в состав интерфейса входит переменная, то она обязательно должна быть объявлена как static final (т.е. быть константой).
Ключевое слово interface дает концепцию абстрактного класса одним шагом, предотвращая будущее определения функций. Интерфейс - очень удобный и часто используемый инструмент, который обеспечивает отличное разделение интерфейса и реализации. В дополнение вы можете комбинировать много интерфейсов вместе, если хотите, в то время как наследование от нескольких обычных или абстрактных классов не возможно.
Класс object не является ничьим наследником, от него начинается иерархия любых классов Java. В частности, все массивы — прямые наследники класса object .
Поскольку такой класс может содержать только общие свойства всех классов, в него включено лишь несколько самых общих методов, например, метод equals() , сравнивающий данный объект на равенство с объектом, заданным в аргументе, и возвращающий логическое значение. Его можно использовать так:
Object objl = new Dog(), obj 2 = new Cat();
if (obj1.equals(obj2)) ...
Метод equals() же сравнивает содержимое объектов в их текущем состоянии, фактически он реализован в классе object как тождество: объект равен только самому себе. Поэтому его часто переопределяют в подклассах, более того, правильно спроектированные, "хорошо воспитанные", классы должны переопределить методы класса object , если их не устраивает стандартная реализация.
Второй метод класса object , который следует переопределять в подклассах, — метод tostring () . Это метод без параметров, который пытается содержимое объекта преобразовать в строку символов и возвращает объект класса string . К этому методу исполняющая система Java обращается каждый раз, когда требуется представить объект в виде строки, например, в методе printing .
JCF - унифицированная архитектура для представления и манипулирования коллекциями
•Уменьшает усилия по программированию
• Уменьшает усилия по изучению и использованию нового API
• Увеличивает скорость и надежность программы
• Допускает переносимость среди связанных API
Состоит из трех частей:? Интерфейсы ? Реализации ? Алгоритмы
Интерфейсы – абстрактные типы данных представляющие коллекции. Назначение коллекций:
? позволить манипулирование коллекциями независимо от их деталей их представления
? предоставить точки расширения для добавления новых типов коллекций и их реализаций
• Реализации – конкретная реализация интерфейса коллекции
• Алгоритмы – методы, выполняющие полезные вычисления над объектами, которые реализуют интерфейс коллекций. Например, поиск и сортировку.
? Предоставляют повторно используемую функциональность посредством полиморфизма – один алгоритм для разных реализаций
Этот универсальный интерфейс для изменения коллекций и прохода по элементам коллекций
• Проверки принадлежности элемента к коллекции
• Добавления элемента к коллекции
• Удаления элемента из коллекции
Структура библиотеки коллекций
• Карта отображает ключи на значения • Добавление/удаление пары ключ-значение
• Взять значение для данного ключа • Проверить наличие элемента в карте
• Можно рассматривать карту как:
? Множество ключей
? Множество пар ключ-значение
? Коллекцию значений
• Итератор предоставляет удобный способ перебора элементов коллекции
•ListIterator – добавляет методы представляющие последовательность элементов этой коллекции
• Методы add и remove позволяют изменять коллекцию во время ее прохода
• Итераторы для сортированных коллекций учитывают порядок заданный при сортировке. Шаблон кода итератора:Collection c;
Iterator i =c.iterator();
while (i.hasNext()) { Object o = i.next(); // process this object }
Для хранения большого количества однотипных данных могут использоваться массивы, но они не всегда являются идеальным решением. Во-первых, длина массива задается заранее и в случае, если количество элементов заранее неизвестно, придется либо выделять память «с запасом», либо предпринимать сложные действия по переопределению массива. Во-вторых, элементы массива имеют жестко заданное размещение в его ячейках, поэтому, например, удаление элемента из массива не является простой операцией.
В программировании давно и эффективно используются такие структуры данных как стек, очередь, список, множество и т.д., объединенные общим названием коллекция. Коллекция — это группа элементов с операциями добавления, извлечения и поиска элемента. Механизм работы операций существенно различается в зависимости от типа коллекции. Например, элементы стека упорядочены в последовательность, добавление нового элемента может происходить только в конец этой последовательности, и получить можно только элемент, находящийся в конце (то есть, добавленный последним). Очередь, напротив, позволяет получить лишь первый элемент (элементы добавляются в один конец последовательности, а «забираются» с другого). Другие коллекции (например, список) позволяют получить элемент из любого места последовательности, а множество вообще не упорядочивает элементы и позволяет (помимо добавления и удаления) только узнать, содержится ли в нем данный элемент.
Язык Java предоставляет библиотеку стандартных коллекций, которые собраны в пакете java.util, поэтому нет необходимости программировать их самостоятельно.
При работе с коллекциями главное избегать ошибки начинающих — пользоваться наиболее универсальной коллекцией вместо той, которая необходима для решения задачи — например, списком вместо стека. Если логика работы программы такова, что данные должны храниться в стеке (появляться и обрабатываться в обратной последовательности), следует использовать именно стек. В этом случае вы не сможете нарушить логику обработки данных, обратившись напрямую к середине последовательности, а значит, шанс появления трудно обнаруживаемых ошибок резко уменьшается.
Чтобы выбрать коллекцию, которая лучше всего подходит условию задачи, необходимо знать особенности каждой из них. Эти знания являются обязательными для любого программиста, поскольку без применения тех или иных коллекций редко обходится любая современная задача. Некоторые сведения вы сможете почерпнуть из дальнейшего изложения.
LinkedList — двунаправленный список, реализующий интерфейсы List и Queue.
Класс ArrayList — аналог класса Vector. Он представляет собой список и может использоваться в тех же ситуациях. Основное отличие в том, что он не синхронизирован и одновременная работа нескольких параллельных процессов с объектом этого класса не рекомендуется. В обычных же ситуациях он работает быстрее. *
HashSet - несортируемый, неупорядоченный набор (Set). Он использует хешкод вставляемого объекта, что еще эффективнее вашей собственной реализации hashCode(). Следует использовать данный класс, когда Вы хотите, чтобы коллекция не содержала дубликатов и Вы не переживаете о порядке упорядочивания при прохождении по набору.
TreeSet - одна из двух сортируемых коллекций ( второй - TreeMap). Она использует древовидную структуру и гарантирует, что все элементы будут расположены в порядке возрастания в природном порядке. По желанию, Вы можете сконструировать TreeSet с конструктором, который позволяет Вам присвоить коллекции свои собственные правила о том, каким должен быть порядок с использованием Comparable или Comparator.
LinkedHashSet - упорядоченная версия HashSet, которая обслуживает дважды связанный список и его элементы. Следует использовать данный класс вместо HashSet, когда Вам важен порядок итерации. Когда вы проходите через HashSet - порядок непредсказуем, в то время как LinkedHashSet позволяет Вам проходить через элементы в том порядке, в котором они были вставлены.
К механизму обработки исключений в Java имеют отношение 5 клю-чевых слов: - try, catch, throw, throws и finally. Схема работы этого механизма следующая. Вы пытаетесь (try) выполнить блок кода, и если при этом возникает ошибка, система возбуждает (throw) исключение, ко-торое в зависимости от его типа вы можете перехватить (catch) или пере-дать умалчиваемому (finally) обработчику.
Ниже приведена общая форма блока обработки исключений.
try {
// блок кода }
catch (ТипИсключения1 е) {
// обработчик исключений типа ТипИсключения1 }
catch (ТипИсключения2 е) {
// обработчик исключений типа ТипИсключения2
throw(e) // повторное возбуждение исключения }
finally {
}
В вершине иерархии исключений стоит класс Throwable. Каждый из типов исключений является подклассом класса Throwable. Два непосредственных наследника класса Throwable делят иерархию подклассов исключений на две различные ветви. Один из них - класс Ехception - используется для описания исключительных ситуации, кото-рые должны перехватываться программным кодом пользователя. Другая ветвь дерева подклассов Throwable - класс Error, который предназначен для описания исклю-чительных ситуаций, которые при обычных условиях не должны перехватываться в пользовательской программе.
Объекты-исключения автоматически создаются исполняющей средой Java в результате возникновения определенных исключительных состо-яний. Например, очередная наша программа содержит выражение, при вычислении которого возникает деление на нуль.
class Exc0 {
public static void main(string args[]) {
int d = 0;
int a = 42 / d;
}
}
Вот вывод, полученный при запуске нашего примера.
С:\> java Exc0
java.lang.ArithmeticException: / by zero
at Exc0.main(Exc0.java:4)
Обратите внимание на тот факт что типом возбужденного исклю-чения был не Exception и не Throwable. Это подкласс класса Exception, а именно: ArithmeticException, поясняющий, какая ошибка возникла при выполнении программы. Вот другая версия того же класса, в кото-рой возникает та же исключительная ситуация, но на этот раз не в про-граммном коде метода main.
class Exc1 {
static void subroutine() {
int d = 0;
int a = 10 / d;
}
public static void main(String args[]) {
Exc1.subroutine();
}
}
Вывод этой программы показывает, как обработчик исключений ис-полняющей системы Java выводит содержимое всего стека вызовов.
С:\> java Exc1
java.lang.ArithmeticException: / by zero
at Exc1.subroutine(Exc1.java:4)
at Exc1.main(Exc1.java:7)
Для задания блока программного кода, который требуется защитить от исключений, исполь-зуется ключевое слово try. Сразу же после try-блока помещается блок catch, задающий тип исключения которое вы хотите обрабатывать.
class Exc2 {
public static void main(String args[]) {
try {
int d = 0;
int a = 42 / d;
}
catch (ArithmeticException e) {
System.out.println("division by zero");
}
}
}
Целью большинства хорошо сконструированных catch-разделов долж-на быть обработка возникшей исключительной ситуации и приведение переменных программы в некоторое разумное состояние - такое, чтобы программу можно было продолжить так, будто никакой ошибки и не было (в нашем примере выводится предупреждение - division by zero).
Операторы try можно вкладывать друг в друга аналогично тому, как можно создавать вложенные области видимости переменных. Если у оператора try низкого уровня нет раздела catch, соответствующего возбужденному исключению, стек будет развернут на одну ступень выше, и в поисках подходящего обработчика будут прове-рены разделы catch внешнего оператора try. Вот пример, в котором два оператора try вложены друг в друга посредством вызова метода.
class MultiNest {
static void procedure() {
try {
int c[] = { 1 };
c[42] = 99;
}
catch(ArrayIndexOutOfBoundsException e) {
System.out.println("array index oob: " + e);
}
}
public static void main(String args[]) {
try {
int a = args.length();
System.out.println("a = " + a);
int b = 42 / a;
procedure();
}
catch (ArithmeticException e) {
System.out.println("div by 0: " + e);
}
}
}
Оператор throw используется для возбуждения исключения <вруч-ную>. Для того, чтобы сделать это, нужно иметь объект подкласса клас-са Throwable, который можно либо получить как параметр оператора catch, либо создать с помощью оператора new. Ниже приведена общая форма оператора throw.
При достижении этого оператора нормальное выполнение кода немед-ленно прекращается, так что следующий за ним оператор не выполня-ется. Ближайший окружающий блок try проверяется на наличие соот-ветствующего возбужденному исключению обработчика catch. Если такой отыщется, управление передается ему. Если нет, проверяется следующий из вложенных операторов try, и так до тех пор пока либо не будет най-ден подходящий раздел catch, либо обработчик исключений исполняю-щей системы Java не остановит программу, выведя при этом состояние стека вызовов. Ниже приведен пример, в котором сначала создается объект-исключение, затем оператор throw возбуждает исключительную ситуацию, после чего то же исключение возбуждается повторно - на этот раз уже кодом перехватившего его в первый раз раздела catch.
class ThrowDemo {
static void demoproc() {
try {
throw new NullPointerException("demo");
}
catch (NullPointerException e) {
System.out.println("caught inside demoproc");
throw e;
}
}
public static void main(String args[]) {
try {
demoproc();
}
catch(NulPointerException e) {
System.out.println("recaught: " + e);
}
}
}
В этом примере обработка исключения проводится в два приема. Метод main создает контекст для исключения и вызывает demoproc. Метод demoproc также устанавливает контекст для обработки исключе-ния, создает новый объект класса NullPointerException и с помощью опе-ратора throw возбуждает это исключение. Исключение перехватывается в следующей строке внутри метода demoproc, причем объект-исключение доступен коду обработчика через параметр e. Код обработчика выводит сообщение о том, что возбуждено исключение, а затем снова возбуждает его с помощью оператора throw, в результате чего оно передается обра-ботчику исключений в методе main. Ниже приведен результат, получен-ный при запуске этого примера.
С:\> java ThrowDemo
caught inside demoproc
recaught: java.lang.NullPointerException: demo
Если метод способен возбуждать исключения, которые он сам не об-рабатывает, он должен объявить о таком поведении, чтобы вызывающие методы могли защитить себя от этих исключений. Для задания списка исключений, которые могут возбуждаться методом, используется ключе-вое слово throws. Если метод в явном виде (т.е. с помощью оператора throw) возбуждает исключе-ние соответствующего класса, тип класса исключений должен быть ука-зан в операторе throws в объявлении этого метода. С учетом этого наш прежний синтаксис определения метода должен быть расширен следую-щим образом:
тип имя_метода(список аргументов) throws список_исключений {}
Ниже приведен пример программы, в которой метод procedure пыта-ется возбудить исключение, не обеспечивая ни программного кода для его перехвата, ни объявления этого исключения в заголовке метода. Такой программный код не будет оттранслирован.
class ThrowsDemo1 {
static void procedure() {
System.out.println("inside procedure");
throw new IllegalAccessException("demo");
}
public static void main(String args[]) {
procedure();
}
}
Для того, чтобы мы смогли оттранслировать этот пример, нам при-дется сообщить транслятору, что procedure может возбуждать исключе-ния типа IllegalAccessException и в методе main добавить код для обработки этого типа исключений :
class ThrowsDemo {
static void procedure() throws IllegalAccessException {
System.out.println(" inside procedure");
throw new IllegalAccessException("demo");
}
public static void main(String args[]) {
try {
procedure();
}
catch (IllegalAccessException e) {
System.out.println("caught " + e);
}
}
}
Ниже приведен результат выполнения этой программы.
С:\> java ThrowsDemo
inside procedure
caught java.lang.IllegalAccessException: demo
Иногда требуется гарантировать, что определенный участок кода будет выпол-няться независимо от того, какие исключения были возбуждены и пере-хвачены. Для создания такого участка кода используется ключевое слово finally. Даже в тех случаях, когда в методе нет соответствующего воз-бужденному исключению раздела catch, блок finally будет выполнен до того, как управление перейдет к операторам, следующим за разделом try. У каждого раздела try должен быть по крайней мере или один раз-дел catch или блок finally. Блок finally очень удобен для закрытия файлов и освобождения любых других ресурсов, захваченных для времен-ного использования в начале выполнения метода. Ниже приведен пример класса с двумя методами, завершение которых происходит по разным причинам, но в обоих перед выходом выполняется код раздела finally.
class FinallyDemo {
static void procA() {
try {
System.out.println("inside procA");
throw new RuntimeException("demo");
}
finally {
System.out.println("procA's finally");
}
}
static void procB() {
try {
System.out.println("inside procB");
return;
}
finally {
System.out.println("procB's finally");
}
}
public static void main(String args[]) {
try {
procA();
}
catch (Exception e) {}
procB();
}
}
В этом примере в методе procA из-за возбуждения исключения про-исходит преждевременный выход из блока try, но по пути <наружу> вы-полняется раздел finally. Другой метод procB завершает работу выпол-нением стоящего в try-блоке оператора return, но и при этом перед выходом из метода выполняется программный код блока finally. Ниже приведен результат, полученный при выполнении этой программы.
С:\> java FinallyDemo
inside procA
procA's finally
inside procB
procB's finally
Только подклассы класса Throwable могут быть возбуждены или пере-хвачены. Простые типы - int, char и т.п., а также классы, не являю-щиеся подклассами Throwable, например, String и Object, использоваться в качестве исключений не могут. Наиболее общий путь для использова-ния исключений - создание своих собственных подклассов класса Ex-ception. Ниже приведена программа, в которой объявлен новый подкласс класса Exception.
class MyException extends Exception {
private int detail;
MyException(int a) {
detail = a:
}
public String toString() {
return "MyException["+detail+"]";
}
}
class ExceptionDemo {
static void compute(int a) throws MyException {
System.out.println("called computer+a+").");
if (a > 10) throw new MyException(a);
System.out.println("normal exit.");
}
public static void main(String args[]) {
try {
compute(1);
compute(20);
}
catch (MyException e) {
System.out.println("caught" + e);
}
}
}
Этот пример довольно сложен. В нем сделано объявление подкласса MyException класса Exception. У этого подкласса есть специальный кон-структор, который записывает в переменную объекта целочисленное значение, и совмещенный метод toString, выводящий значение, хранящееся в объекте-исключении. Класс ExceptionDemo определяет метод compute, который возбуждает исключение типа MyExcepton. Простая логика метода compute возбуждает исключение в том случае, когда значение пара-ветра метода больше 10. Метод main в защищенном блоке вызывает метод compute сначала с допустимым значением, а затем - с недопус-тимым (больше 10), что позволяет продемонстрировать работу при обоих путях выполнения кода. Ниже приведен результат выполнения програм-мы.
С:\> java ExceptionDemo
called compute(1).
normal exit.
called compute(20).
caught MyException[20]
Обработка исключений предоставляет исключительно мощный меха-низм для управления сложными программами. Try, throw, catch дают вам простой и ясный путь для встраивания обработки ошибок и прочих нештатных ситуаций в программную логи-ку. Если вы научитесь должным об-разом использовать рассмотренные в данной главе механизмы, это при-даст вашим классам профессиональный вид, и любые будущие пользователи вашего программного кода, несомненно, оценят это.
Иерархия исключений
1. вышестоящий слой ничего не должен знать о нижестоящем слое кроме того, что нужно знать для использования интерфейса, посредством которого они взаимодействуют. Исключение должно давать характер проблемы в сущностях именно того слоя, который получает исклюение. То есть скажем, ClassNotFoundException должен обрабатываться на самом нижнем уровне, и не должен, скажем , попадать в какой-нить фасадный слой.
2. Для удобства отладки при возникновении ERROR-состояния в генерируемое исключение передавать предыдущее исключение, чтобы можно было восстановить весь stacktrace.
3. На счет иерархии .. все зависит от предметной области, но ИМХО — не вижу смысла разрастать эту иерархию, подобно иерархии классов объектов.. Достаточно , считаю , того, что они будут унаследованы от Exception. Все равно каждый слой будет использовать свои исключения. Но это , опять же, мое мнение, вполне естественно, что кто-то с этим не согласится.
Создание собственных исключений
Прежде всего, нужно четко определить ситуации, в которых будет возникать ваше собственное исключение, и подумать, не станет ли его перехват невольно перехватывать также и другие, не учтенные вами исключения.
Потом надо выбрать суперкласс создаваемого класса-исключения. Им может быть класс Exception или один из его многочисленных подклассов.
После этого можно написать класс-исключение. Его имя, по соглашению, должно завершаться словом Exception. Как правило, этот класс состоит только из двух конструкторов и переопределения методов tostringo и getMessageO.
Рассмотрим простой пример. Пусть метод handle(int cipher) обрабатывает арабские цифры 0—9, которые передаются ему в аргументе cipher типа int.
Мы хотим выбросить исключение, если аргумент cipher выходит за диапазон 0—9.
Прежде всего, убедимся, что такого исключения нет в иерархии классов Exception. Ко всему прочему, не отслеживается и более общая ситуация попадания целого числа в какой-то диапазон. Поэтому будем расширять наш класс. Назовем его cipherException, прямо от класса Exception. Определим класс cipherException, как показано в листинге 16.6, и используем его в классе ExceptDemo. На рис. 16.5 продемонстрирован вывод этой программы.
Регулярные выражения (Regular Expressions) позволяют сопоставлять текст с указанным шаблоном, а также выполнять замену текста. Эти операции осуществляются с помощью универсальных символов, которые специальным образом интерпретируются.
Регулярные выражения используются в большом количестве языков программирования.
В Java тоже есть пакет, который позволяет работать с ними - java.util.regex.
Пакет состоит всего из трех классов: Matcher, Pattern, PatternSyntaxException.
Pattern - скомпилированное представление регулярного выражения.
Matcher - движок, который производит операцию сравнения (match).
PatternSyntaxException - указывает на синтаксическую ошибку в выражении.
Последовательность вызова методов при работе с regexp:
Pattern p = Pattern.compile(“a*b”);
Matcher m = p.matcher(“aaab”);
boolean b = m.matches();
Как видно из примера, регулярное выражение сперва должно быть откомпилировано. Результирующий объект может быть использован для создания объекта Matcher на основе java.lang.CharSequence (String). Matcher в свою очередь вызывает метод matches().
Например:
(“[a-zA-Z]{1}[a-zA-Z\\d\\u002E\\u005F]+@([a-zA-Z]+\\u002E){1,2}((net)|(com)|(org))”);
Последовательность вида [a-zA-Z] указывает на множество. {n} говорит о том, что некоторый символ должен встретится n раз, а {n,m} - от n до m раз. Символ \d указывает на множество цифр. “\u002E” и “\u005F” - это символы точки и подчеркивания соответсвенно. Знак плюс после некоторой последовательности говорит о том, что она должна встретится один или более раз. “|” - представление логического “или”.
Регулярные выражения (Regular Expressions) позволяют сопоставлять текст с указанным шаблоном, а также выполнять замену текста. Эти операции осуществляются с помощью универсальных символов, которые специальным образом интерпретируются.
«.»-один любой символ
«?»-допускается один экземпляр
«*»-допускается любое количество экземпляров
«+»-требуется один, допускается много
«^»-начало строки
Базой данных является представленная в объективной форме совокупность самостоятельных материалов, систематизированных таким образом, чтобы эти материалы могли быть найдены и обработаны с помощью электронной вычислительной машины (ЭВМ) .
База данных — совокупность данных, хранимых в соответствии со схемой данных.
Назначение: хранение данных, а главное, внутренних зависимостей данных(отношений) в форме удобной для последующей обработки этих данных.
Java DataBase Connectivity (JDBC) представляет собой набор интерфейсов и классов, написанных на языке программирования Java для доступа к базам данных из написанных на языке java программ.
Используя JDBC 3.0 API можно получить доступ практически к любым источникам данных например к текстовым файлам, таблицам Excel или реляционным базам данных.
Технология JDBC также является базовой основой на основе которой могут строится альтернативные интерфейсы.
JDBC 3.0 API включает два пакета: java.sql и javax.sql который содержит дополнительные возможности серверного уровня.
Оба пакета включены в состав Java Platform Standard Edition (Java SE) 6.
XML— рекомендованный Консорциумом Всемирной паутины язык разметки, фактически представляющий собой свод общих синтаксических правил. XML — текстовый формат, предназначенный для хранения структурированных данных, для обмена информацией между программами, а также для создания на его основе более специализированных языков разметки (например, XHTML).
Java-разработчики используют XML для конфигурирования в качестве хранилища данных, а еще чаще - как формат для обмена данными. Он стал основой для Web-сервисов и SOAP, а следовательно, и для конструктивных шаблонов современной сервис-ориентированной архитектуры (Service-Oriented Architecture - SOA). Но этим возможности XML не исчерпываются. В Ajax (Asynchronous JavaScript + XML) он представлен буквой X и играет ключевую роль в создании более функционально насыщенных, чем когда-либо, интерфейсов, присущих современным Web-приложениям.
XStream – простая библиотека для сериализации объектов в .xml и обратно.
Сегодня XML может использоваться в любых приложениях, которым нужна структурированная информация - от сложных геоинформационных систем, с гигантскими объемами передаваемой информации до обычных "однокомпьютерных" программ, использующих этот язык для описания служебной информации. XML-документ представляет собой обычный текстовый файл, в котором при помощи специальных маркеров создаются элементы данных, последовательность и вложенность которых определяет структуру документа и его содержание. Основным достоинством XML документов является то, что при относительно простом способе создания и обработки (обычный текст может редактироваться любым тестовым процессором и обрабатываться стандартными XML анализаторами), они позволяют создавать структурированную информацию, которую хорошо "понимают" компьютеры.
DOM (Document Object Model) — это способ представления содержимого XML в виде дерева. Такой подход наиболее естественно согласуется с объектно-ориентированной парадигмой. У этого подхода есть недостатки. Во-первых, все дерево нужно хранить в памяти, что может быть проблемой для больших XML файлов (к тому же, зачастую построенная при помощи DOM структура в памяти занимает места раза в 4 больше, чем сам файл). Во-вторых, даже для получения небольшой части содержимого, нужно прочитать весь файл и построить все дерево, что может быть очень долго (например, если файл передается по сети). Перечисленные проблемы накладывают ограничения на ситуации, в которых целесообразно применять DOM. Рассмотрим теперь другой подход, который называется SAX.
SAX (Simple API for XML) — это способ последовательного чтения XML файла. SAX-парсер читает содержимое файла и просто сообщает о встреченных элементах XML-разметки (или ошибках). Такая модель может быть осуществлена посредством функций обратного вызова (callback function) или полиморфизма. Преимуществом SAX является то, что его реализация требует минимум памяти. Но у него есть и недостатки, например, нельзя возвращаться назад или сразу перейти вперед.
Подведем итоги. Существует два вида XML парсеров, DOM и SAX. DOM представляет собой дерево и удобен, если нужно выполнять быструю навигацию по документу. Использует объектную модель, так что применяется преимущественно в объектно-ориентированных языках (например, C++, Java и т.д.) SAX, в отличие от DOM, требует независящего от размера файла объема памяти, но не позволяет произвольно перемещаться по файлу. Может быть реализован при помощи функций обратного вызова, что позволяет его использовать в процедурных языках программирования (таких, как C).
.property – ‘то обычный текстовый файл, данные в котором хранятся в виде key = value.
В Java есть готовый класс для чтения/записи таких файлов (java.util.Properties), но с ним есть некоторые проблемы. Во первых для чтения невозможно задать кодировку файла, а это означает проблемы с русскими буквами. Во вторых стандартная функция записи сохраняет данные в порядке следования хэш-значений ключей, что значит - как ей больше понравится. Но это тоже легко разрешимо - достаточно написать свою читалку/писалку.
Лог - это что то наподобие подробного отчёта. дневника. Туда программа записывает какое действие и во сколько она выполнила. Или записывает событие, например ошибки. Обычно содержит время, и текст, того или оиного события. На тех же самых серверах операционаая система ведёт лог подключившихся пользователей, ошибок, сбоев, действий пользователей. Каждая программа ведёт свой лог, и записывает туда важные данные.
Log4j – это инструмент для журналирования с открытым исходным кодом, разработанный под эгидой глобального проекта Jakarta Apache. Он представляет собой набор API с помощью которых, разработчики могут вставлять в свой код выражения, которые выводят некоторую информацию (отладочную, информационную, сообщения об ошибках и т.д.), и конфигурировать этот вывод с помощью внешний конфигурационных файлов.
appender — это "пункт назначения" для логгера, т.е. то место, куда пишутся логи.
Logger можно представлять себе, как компонент некого приложения, который будет принимать и выполнять ваши запросы на запись каких-либо событий в регистрационный журнал, представленный файлом, базой данных, консолью и т.д. (определяется appender’ом). Каждый класс вашего приложения может иметь свой собственный logger или же быть привязанным к общему logger’у. Log4j предусматривает корневой logger, от которого будут наследоваться все создаваемые вами объекты этого типа. Это значит, что если у вас не предусмотрен для каждого объекта свой logger, то вы всегда сможете воспользоваться корневым, с помощью метода Logger.getRootLogger(), однако так поступать не рекомендуется.
Layout — это настройка appender-а, с помощью которой можно задать формат вывода данных.
FATAL - произошла фатальная ошибка - у этого сообщения наивысший приоритет
ERROR - в программе произошла ошибка
WARN - предупреждение в программе что-то не так
INFO - информация.
DEBUG - детальная информация для отладки
TRACE - наиболее полная информация. трассировка выполнения программы. Наиболее низкий приоритет.
1.Именование пакетов, классов, переменных, констант и методов
Пакеты: ru.emms.core.service
Классы и интерфейсы: Line, MessageLog
Переменные: line, audioSystem, messageLog
Константы: MAX_ITERATIONS, COLOR_RED
Методы: getName(), computeTotalWidth()
2. Язык кодирования - английский!
3.Комментарии
// комментарий
// а это уже многострочный комментарий
// the end
/**
* это пример javadoc комментария
*
* @param если есть параметры
*/
4. Порядок объявления классов и интерфейсов
Packet name
Import libraries
Class/Interface documentation.
Class or interface statement.
Constants
Variables
Constructors.
Methods (no specific order).
5. Порядок формирования сигнатуры метода
<access> static abstract synchronized final native
// NOT: static public double square(double a);
public static double square(double a);
6. Использование переменных
Переменные должны объявляться как можно ближе к месту использования
Не допускайте повторного использования переменной в другом контексте
Поля класса должны объявляться с модификатором видимости private
Старайтесь давать названиям осмысленные названия, но не делайте их слишком длинными
7. Упрощайте конструкции
boolean isFinished = (elementNo < 0) || (elementNo > maxElement); boolean isRepeatedEntry = elementNo == lastElement;
if (isFinished || isRepeatedEntry) {
:
}
// NOT:
if ((elementNo < 0) || (elementNo > maxElement)||
elementNo == lastElement) {
:
}