Буферизированный ввод из файла
Для открытия файла для ввода символов вы используете FileInputReader с объектом String или File в качестве имени файла. Для быстрой работы вы можете захотеть, чтобы файл был буферизированный, поэтому вы передаете результирующую ссылку в конструктор BufferedReader. BufferedReader также обеспечивает метод readLine( ), так что это ваш конечный объект и интерфейс, из которого вы читаете. Когда вы достигаете конца файла, readLine( ) возвращает null, что используется для окончания цикла while.
String s2 использует для аккумулирования всего содержимого файла (включая символы новой строки, которые должны добавляться, поскольку readLine( ) отбрасывает их). s2 далее используется в следующих частях этой программы. В конце вызывается close( ) для закрытия файла. Технически, close( ) будет вызвано при запуске finalize( ), а это произойдет (не зависимо от того произойдет или нет сборка мусора) при выходе из программы. Однако это было реализовано неустойчиво, поэтому безопасным подходом является явный вызов close( ) для файлов.
Раздел 1b показывает, как вы можете использовать System.in для чтения консольного ввода. System.in является DataInputStream и для BufferedReader необходим аргумент Reader, так что InputStreamReader вовлекается для выполнения перевода.
Тренировка
Это первый шаг в некоторых формах образования. Помните о вложениях компании в код и попробуйте не бросать все в беспорядке через шесть или девять месяцев, пока каждый будет ломать голову как работают интерфейсы. Возьмите маленькую группу для идеологической обработки, лучше всего составленную из людей, которые любопытны, хорошо работают вместе и могут функционировать как собственная сеть поддержки, пока они учат Java.
Альтернативный подход, который иногда предпочтительней, это обучение всех уровней компании одновременно, включая обзорные курсы для стратегических управляющих, так же как и курсы дизайна и программирования для строителей проекта. Это особенно хорошо для маленьких компаний, делающих фундаментальный сдвиг на пути создания вещей, или для отделения большой компании. Однако, поскольку стоимость высока, некоторые могут выбрать начало с проекта тренировочного уровня, выполнить проект под руководством (возможно с приглашенным специалистом), а затем команда проекта станет учителями для всей остальной компании.
: Введение в объекты
Развитие компьютерной революции идет из машины. Поэтому, развитие наших языков программирования также склоняется на сторону машины.
Но компьютеры не настолько машины, они понимаются как расширенные инструменты (“бациллы для ума”, как сказал Стив Добс (Steve Jobs)) и различные виды средств выражения. В результате инструмент, выглядит меньше всего как машина, а больше похож на часть нашего ума, а также на другие формы выражения, как письмо, рисование, скульптура, анимация и создание фильмов. Объектно-ориентированное программирование (ООП) - это часть движения в этом направлении использования компьютеров, как средства выражения.
Эта глава расскажет вам основные концепции ООП, включая обзор методов разработки. Эта глава и эта книга предполагает, что вы имеете опыт в процедурных языках программирования, но не обязательно в C. Если вы считает, что вам нужна большая подготовка в программировании и в синтаксисе C прежде, чем браться за эту книгу, вы должны поработать с книгой "Думаем на C: Основы для C++ и Java", которая есть на CD ROM, сопровождающим эту книгу, а также доступной на www.BruceEckel.com.
Эта глава является предпосылкой и дополнительным материалом. Многие люди не чувствуют себя комфортно в объектно-ориентированном программировании без первоначального понимания всей картины. Поэтому, существует много концепций, которые приведены здесь, чтобы дать вам целую картину ООП. Однако многие другие люди не понимают всей картины, пока они сначала не увидят некоторую механику; такие люди могут увязнуть и потеряться без некоторого кода, полученного своими руками. Если вы относитесь к этой последней группе и сначала хотите узнать спецификацию языка, можете свободно пропустить эту главу — пропуск с этого места не скажется на написании программ или на изучении языка. Однако со временем вы можете захотеть вернуться, чтобы пополнить свои знания, чтобы вы могли понимать, почему объекты так важны и как работать с ними.
Проекты низкого риска
Проекты низкого риска всегда первые и позволяют ошибаться. Как только вы приобретете определенный опыт, вы можете либо выбрать другие проекты с членами первой команды, или использовать членов команды как штат ООП технической поддержки. Этот первый проект первое время может работать не правильно, так что это не должно иметь критического значения для компании. Он должен быть простым, самозаконченным и поучительным; это значит, что он должен вызывать создание классов, которые будут многозначительны для других программистов компании, когда они получат их, чтобы включится в изучение Java.
: Все есть объекты
Хотя он основывается на C++, Java более “чистый” объектно-ориентированный язык.
И C++ и Java гибридные языки, но разработчики Java почувствовали, что гибридизация не так важна, как в случае C++. Гибридные языки позволяют различные стили программирования; причина гибридизации C++ в обратной совместимости с языком C. Поэтому C++ является расширением языка C, он включает много нежелательных особенностей этого языка, которые могут сделать некоторые аспекты C++ чрезмерно запутанными.
Язык Java предполагает, что вы хотите заниматься только объектно-ориентированным программированием. Это значит, что прежде чем вы сможете начать, вы должны продвинуть свой разум в объектно-ориентированный мир (если вы еще не там). Польза от этого начального достижения - это способность программировать на языке, который проще для изучения и использования, чем многие другие ООП языки. В этой главе вы увидите основные компоненты Java программы, и мы выучим, что все в Java - это объекты, даже Java программа.
Ввод из памяти
Эта секция берет String s2, которая теперь включает все содержимое файла, и использует его для создания StringReader. Затем используется read( ) для чтения каждого символа, один символ за обращение, который посылается на консоль. Обратите, что read( ) возвращает следующий байт как int, поэтому он должен быть приведен к типу char для правильной печати.
Форматированный ввод из памяти
Для чтения “форматированных” данных вы используете DataInputStream, который является байт-ориентированным классом ввода/вывода (а не символьно-ориентированным). Таким образом, вы должны использовать все классы InputStream, а не классы Reader. Конечно, вы можете читать все, что угодно (также как и файл) байтами, используя классы InputStream, но здесь используется String. Для преобразования String в массив байт, который является подходящим для ByteArrayInputStream, String имеет метод getBytes( ), чтобы сделать эту работу. В этой точке вы имеете соответствующий InputStream для управления DataInputStream.
Если вы читаете символы из DataInputStream по одному байту, используя readByte( ), любое байтовое значение является допустимым результатом, так что возвращаемое значение не может использоваться для обнаружения конца ввода. Вместо этого вы можете использовать метод available( ) для нахождения как много символов доступно. Вот пример, который показывает, как читать файл по одному байту:
//: c11:TestEOF.java
// Проверка на конец файла
// при чтении по одному байту.
import java.io.*;
public class TestEOF { // Выбрасывается исключение на консоль:
public static void main(String[] args) throws IOException { DataInputStream in = new DataInputStream( new BufferedInputStream( new FileInputStream("TestEof.java"))); while(in.available() != 0) System.out.print((char)in.readByte()); } } ///:~
Обратите внимание, что available( ) работает по разному в зависимости от сорта носителя, из которого вы читаете; буквально - “это число байт, которые могут быть прочитаны без блокировки”. Для файлов это означает весь файл, но для другого вида потоков это может не быть правдой, так что используйте его осторожно.
Вы также можете определить конец ввода в таком случае, как здесь, при поимке исключения. Однако использование исключений для управления выполнением программы, рассматривается как злоупотребление этой особенностью.
Модель успеха
Поищите примеры хорошего объектно-ориентированного дизайна, прежде чем начнете набивать шишки. Есть хорошая вероятность, что кто-нибудь уже решил вашу проблему, а если они не решили ее точно, вы, вероятно, можете применить то, что вы выучили об абстракции для модификации существующего дизайна в соответствии с вашими требованиями. Это общая концепция дизайна шаблонов, описанная в Thinking in Patterns with Java, имеющаяся на www.BruceEckel.com.
: Управление течением программы
Подобно мыслящему существу, программа должна управлять собственным миром и делать выбор при выполнении.
В Java вы манипулируете объектами и данными, используя операторы, и вы делаете выбор с помощью выражений, контролирующих выполнение. Java был наследован из C++, так что большинство этих выражений будут знакомы программистам, работающим на C и C++. Java также имеет несколько дополнительных усовершенствований и упрощений.
Если вы почувствуете, что вам немного трудно двигаться вперед в этой главе, просмотрите мультимедиа CD ROM прилагающийся к этой книге: Thinking in C: Foundations for Java and C++. Он содержит аудио лекции, слайды, упражнения и решения, специально подобранные так, чтобы быстро ввести вас в синтаксис C, необходимый для изучения Java.
: Инициализация и очистка
В процессе компьютерной революции, “не безопасное” программирование стало главной виной его удорожания.
Двумя основными проблемами безопасности являются инициализация и очистка. Многие ошибки в C возникали тогда, когда программист забывал инициализировать переменную. Это особенно верно для библиотек, когда пользователь не знает о том, как инициализировать компонент библиотеки или о том, что он должен это сделать. Очистка - это особая проблема, потому что легче забыть об элементе, когда вы уже закончили работать с ним, так как он больше не притягивает ваше внимание. Таким образом, ресурсы, используемые элементом, остаются, и вы можете легко прийти к завершению программы из-за нехватки ресурсов (чаще всего, это память).
В C++ введена концепция конструктора - это специальный метод, вызывающийся автоматически при создании объекта. Java позаимствовала конструктор и добавила сборщик мусора, который автоматически освобождает ресурсы памяти, когда они более не используются. Эта глава исследует проблемы инициализации и очистки, и то, как они решаются в Java.
Использование библиотек существующих классов
Первичная экономическая мотивация для перехода к ООП - это простота в использовании существующего кода в форме библиотек классов (обычно, Стандартные Библиотеки Java, которые описываются в этой книге). Короткий цикл разработки приложения в результате которого вы можете создать и использовать не библиотечные объекты. Однако некоторые начинающие программисты не понимают этого, не зная существующих библиотек классов, или, очаровываясь языком, хотят написать классы, которые уже существуют. Ваш успех в ООП и Java будет оптимизирован, если вы раньше в процессе перехода сделаете усилие по поиску и повторному использованию кода других людей.
Вывод в файл
Этот пример также показывает, как писать данные в файл. Сначала создается FileWriter для соединения с файлом. Фактически, вы всегда будете буферизировать вывод, обернув его с помощью BufferedWriter (попробуйте удалить эту обертку, чтобы посмотреть влияние на производительность — буферизация позволяет значительно увеличить производительность операций ввода/вывода). Затем, для форматирование объект включен в PrintWriter. Файл данных, созданный этим способом, читаем, как обычный текстовый файл.
Когда строки записываются в файл, добавляются номера строк. Обратите внимание, что LineNumberInputStream не используется, потому что он слабый и вам не нужен. Как показано здесь, достаточно просто хранить свою историю номеров строк.
Когда входной поток исчерпан, readLine( ) возвращает null. Вы увидите явный вызов close( ) для out1, в противном случае, если вы не вызовите close( ) для всех своих выходных файлов, вы можете обнаружить, что данные из буферов не вытолкнуты, поэтому файлы не завершенные.
Не переписывайте существующий в Java код
Не лучший выход взять существующий функционирующий код и переписать его на Java. (Если вы должны включить его в объекты, вы можете связаться с кодом C или C++, используя Java Native Interface, описанный в приложении B.) Есть определенные выгоды, особенно, если код планируют использовать еще. Шанс, что вы не увидите впечатляющее увеличение производительности, как вы надеялись в нескольких первых проектах, за исключением того, что проект новый. Java и ООП сияют лучше, когда переводят проект из концепции в реальность.
: Скрытие реализации
Основным обсуждением в объектно-ориентированном программировании является “отделение вещей, которые меняются, от тех, которые не меняются.”
Это очень важно для библиотек. Пользователь (клиентский программист) этой библиотеки должен полагаться на ту часть библиотеки, которую он использует, и знать, что ему не нужно будет снова переписывать код, как только выйдет новая версия этой библиотеки. С другой стороны, создатель библиотеки должен иметь свободу модификаций и расширений, и быть уверенным, что эти изменения не повлияют на работу кода клиентского программиста.
Все это может быть достигнуто с помощью соглашений. Например, программист библиотеки должен оставлять существующие методы при модификации класса в библиотеке, т.к. это может нарушить работу кода клиентского программиста. Обратная ситуация гораздо сложнее. В случае с членами данных, как создатель библиотеки узнает, какие члены данных используются клиентским программистом? Это также верно для методов, которые являются частью класса и не используются напрямую клиентским программистом. А что, если создатель библиотеки хочет удалить старую реализацию и поместить новую? Изменение любого из этих методов может нарушить работу кода клиентского программиста. Получается, что программист находится в весьма затруднительном положении и не может ничего изменить.
Для решения этой проблемы, Java предоставляет спецификаторы доступа для того, чтобы создатель библиотеки мог сказать что доступно клиентскому программисту, а что нет. Уровни контроля доступа от “полного” до “минимального” определяются с помощью ключевых слов: публичный - public, защищенный - protected, дружественный - “friendly” (не имеет ключевого слова) и приватный - private. Из предыдущего параграфа Вы можете посчитать, что как разработчик библиотеки, Вы будете хранить все, что возможно как “private”, и раскрывать только те методы, которые Вы хотите предоставить клиентскому программисту. Это абсолютно верно, хотя это бывает трудно понимать людям, программирующим на других языках (особенно на C), которые имеют доступ ко всему, без ограничений. К концу этой главы Вы поймете, насколько большое значение имеет контроль доступа в Java.
Однако, концепция библиотеки компонент и контроля доступа к ним это еще не все. Существует вопрос - как хранить вместе связанные компоненты в модуле библиотеки. В Java это реализуется с помощью ключевого слова package (пакет), и спецификаторы доступа действуют в зависимости от того, находится ли класс в том же пакете или нет. Итак, в начале этой главы Вы узнаете, как размещать компоненты библиотеки в пакетах. А затем, Вы сможете понять значение спецификаторов доступа.
Сохранение и возврат
PrintWriter форматирует данные так, чтобы их читали люди. Однако для вывода данных в виде, чтобы они могли быть возвращены в другой поток, используйте DataOutputStream для записи данных, а DataInputStream для обратного получения данных. Конечно, эти потоки могут быть всем, что угодно, но здесь используется файл, буферизируемый и для чтения, и для записи. DataOutputStream и DataInputStream являются байт-ориентированными и поэтому требуют потоков InputStream и OutputStream.
Если вы используете DataOutputStream для записи данных, то Java гарантирует, что вы можете безошибочно повторно задействовать данные, используя DataInputStream — не зависимо от различий платформ для записи и чтения данных. Это невероятно ценно, как могут подтвердить те, кто потратил время, заботясь о платформозависимых путях движения данными. Эти проблемы снимаются, если вы имеете Java на обеих платформах. [58]
Обратите внимание, что строки символов записываются с использованием как writeChars( ), так и writeBytes( ). Когда вы запустите программу, вы обнаружите, что выводит 16-битные символы Unicode. Когда вы читаете строки, используя readLine( ), вы увидите, что есть пространство между символами, потому что каждый дополнительный байт вставляется из-за Unicode. Так как нет дополнительного метода “readChars” для DataInputStream, вы вынуждены вытягивать символы по одному с помощью readChar( ). Так что для ASCII легче написать символы байтами, за которым следует новая строка, а затем использовать readLine( ) для чтения байтов, как обычной ASCII cтроки.
writeDouble( ) сохраняет числа типа double в потоке, а дополнительный метод readDouble( ) получает их обратно (есть аналогичные методы для чтения и записи остальных типов). Но для корректной работы с любым читающим методом вы должны знать точное положение элемента данных в потоке, чтобы было одинаково возможно читать хранимое double, как простую последовательность байт, или как char, и т.п. Таким образом, вы должны либо иметь фиксированный формат для данных в файле, или в файле должна хранится дополнительная информация, которую вы обработаете для определения местоположения данных.
Чтение и запись файлов произвольного доступа
Как было замечено ранее, RandomAccessFile почти полностью изолирован от оставшейся иерархии ввода/вывода, и подтвержден тот факт, что он реализует интерфейсы DataInput и DataOutput. Поэтому вы не можете комбинировать его с любыми другими аспектами подклассов InputStream и OutputStream. Даже при том, что имело бы смысл трактовать ByteArrayInputStream, как элемент произвольного доступа, вы можете использовать RandomAccessFile только для открытия файла. Вы должны иметь в виду, что RandomAccessFile буферизирован должным образом, так что вам не нужно заботится об этом.
Одну из настроек вы имеете во втором конструкторе аргумента: вы можете открыть RandomAccessFile для чтения (“r”) или для чтения и записи (“rw”).
Использование RandomAccessFile аналогично использования комбинации DataInputStream и DataOutputStream (потому что он реализует эквивалентные интерфейсы). Кроме того, вы можете видеть, что seek( ) используется для перемещения в файле и изменения одного значения на другое.
: Повторное использование классов.
Одной из наиболее притягательных возможностей языка Java является возможность повторного использования кода. Но что действительно "революционно", так это наличие возможности выполнять не только простое копирование и изменение этого кода.
Такой подход использован в процедурных языках программирования, наподобие C, но он работает не очень хорошо. Как и все в Java, решение с повторным использованием кода вертится вокруг классов. Вы повторно используете код, создавая новый класс, но вместо того, что бы создавать его с нуля Вы используете уже существующие классы, которые кто-то уже создал и отладил.
Уловка в том, что бы использовать классы без копания в их исходном коде. В этой главе вы увидите два способа достижения этого. Первый - почти прямой: Вы просто создаете объекты ваших уже существующих классов внутри нового класса. Это называется "композиция" , потому, что новый класс создается из объектов уже существующих классов. Вы просто повторно используете функциональность кода, но не его самого.
Второй подход более искусный. Суть его в том, что создается новый класс с типом существующего класса. Вы буквально берете оболочку (интерфейс) существующего класса и добавляете свой код к нему без модификации существующего класса. Этот магический акт называется "наследование", и компилятор языка при этом выполняет большую часть работы. Наследование является одним из краеугольных камней объектно-ориентированного программирования и имеет более широкий смысл, который будет раскрыт в главе 7.
Это исключительно, но синтаксис и поведение идентичны для обоих способов, для композиции и наследования (обусловлено тем, что оба пути создают новые типы из существующих типов). В этой главе Вы узнаете об обоих этих механизмах повторного использования.
: Полиморфизм
Полиморфизм - третья неотъемлемая часть объектно-ориентированного программирования, после абстракции и наследования соответственно.
Полиморфизм предоставляет другое измерение разделения интерфейса и реализации, т.е. отделяет что от как. Полиморфизм позволяет повысить возможности по организации кода и читабельность исходных текстов, так же, как создание расширяемых программ, которые могут расти не только во время их создания, но и после, когда к ним нужно добавить новую требуемую возможность.
Инкапсуляция создает новые инкапсулированные типы данных, комбинируя их характеристики и типы поведения. Имплементация скрывает интерфейс от дальнейшей имплементации делая некоторые элементы private. Этот вид механической организации может быть нов для кого-то, кто имеет большие познания в процедурном программировании. Но полиморфизм работает с разделением типов. В предыдущей главе Вы увидели, что наследование обращается с объектом с его собственным типом или с базовым типом. Эта особенность критична, поскольку при этом могут поддерживаться многие типы (дочерних от одного и того же базового типа), которые обрабатываются, как если бы они были одного типа и один и тот же код работает одинаково с этими различными типами. Вызов полиморфного метода поддерживает один тип для выражения его отличия от другого, такого же типа, и это из-за того, что они произошли от одного базового типа. Данное различие выражено через различие в поведении методов, которые Вы можете вызвать через базовый класс.
В этой главе, Вы узнаете о полиморфизме (так же называемом динамически связыванием или поздним связыванием или связыванием во время выполнения), начиная от основ с простыми примерами с последующим раскрытием всего поведения полиморфизма в программах.
: Хранение Ваших объектов
Это очень простая программа имеет только ограниченное число объектов с известным временем жизни.
В общем случае ваши программы будут всегда создавать новые объекты, основываясь на таких критериях, которые будут известны только во время выполнения программы. Вы не можете знать до запуска программы количество или даже точный тип необходимых объектов. Для решения общих проблем программирования вы должны быть способны создать любое число объектов в любое время, в любом месте. Так что вы на самом деле не можете создать поименованные ссылки, чтобы держать их для каждого вашего объекта:
MyObject myReference;
так как вы никогда не будете знать, сколько таких ссылок вам на самом деле необходимо.
Для решения этой насущной проблемы Java имеет несколько способов хранения объектов (или скорее, ссылок на объекты). Встроенным типом является массив, который обсуждался уже ранее. Также библиотека утилит Java имеет разумный набор контейнерный классов (также известных как классы сборки, поэтому библиотека Java 2 использует имя Collection для указания определенного набора библиотеки, я буду использовать более обобщающий термин “контейнер”). Контейнеры обеспечивают удовлетворительные способы хранения и манипуляции вашими объектами.
Обработка ошибок с помощью исключений
Основная философия Java в том, что “плохо сформированный код не будет работать”.
Идеальное время для поимки ошибки - это время компиляции, прежде чем вы попробуете даже запустить программу. Однако не все ошибки могут быть определены во время компиляции. Оставшиеся проблемы должны быть обработаны во время выполнения, с помощью некоторого правила, которая позволяет источнику ошибки передавать соответствующую информацию приемщику, который будет знать, как правильно обрабатывать затруднение.
В C и других ранних языках могло быть несколько таких правил, и они обычно устанавливались соглашениями, а не являлись частью языка программирования. Обычно вы возвращали специальное значение или устанавливали флаг, а приемщику предлагалось взглянуть на это значение или на флаг и определить, было ли что-нибудь неправильно. Однако, по прошествии лет, было обнаружено, что программисты, использующие библиотеки, имеют тенденцию думать о себе, как о непогрешимых, например: “Да, ошибки могут случаться с другими, но не в моем коде”. Так что, не удивительно, что они не проверяют состояние ошибки (а иногда состояние ошибки бывает слишком глупым, чтобы проверять [51]). Если вы всякий раз проверяли состояние ошибки при вызове метода, ваш код мог превратиться нечитаемый ночной кошмар. Поскольку программисты все еще могли уговорить систему в этих языках, они были стойки к принятию правды: Этот подход обработки ошибок имел большие ограничения при создании больших, устойчивых, легких в уходе программ.
Решением является упор на причинную натуру обработки ошибок и усиление правил. Это действительно имеет долгую историю, так как реализация обработки исключений возвращает нас к операционным системам 1960-х и даже к бейсиковому “on error goto” (переход по ошибке). Но исключения C++ основывались на Ada, а Java напрямую базируется на C++ (хотя он больше похож на Object Pascal).
Слово “исключение” используется в смысле “Я беру исключение из этого”. В том месте, где возникает проблема, вы можете не знать, что делать с ней, но вы знаете, что вы не можете просто весело продолжать; вы должны остановиться и кто-то, где-то должен определить, что делать. Но у вас нет достаточно информации в текущем контексте для устранения проблемы. Так что вы передаете проблему в более высокий контекст, где кто-то будет достаточно квалифицированным, чтобы принять правильное решение (как в цепочке команд).
Другая, более значимая выгода исключений в том, что они очищают код обработки ошибок. Вместо проверки всех возможных ошибок и выполнения этого в различных местах вашей программы, вам более нет необходимости проверять место вызова метода (так как исключение гарантирует, что кто-то поймает его). И вам необходимо обработать проблему только в одном месте, называемом обработчик исключения. Это сохранит ваш код и разделит код, описывающий то, что вы хотите сделать, от кода, который выполняется, если что-то случается не так. В общем, чтение, запись и отладка кода становится яснее при использовании исключений, чем при использовании старого способа обработки ошибок.
Так как обработка исключений навязывается компилятором Java, то есть так много примеров, которые могут быть написаны в этой книге без изучения обработки исключений. Эта глава вводит вас в код, который вам необходим для правильной обработки исключений, и способы, которыми вы можете генерировать свои собственные исключения, если ваш метод испытывает затруднения.
Система ввода/вывода в Java
Создание хорошей системы ввода/вывода (I/O) является одной из наиболее сложных задач для разработчиков языка.
Доказательством этому служит наличие множества различных подходов. Сложность задачи видится в охвате всех возможностей. Не только различный исходный код и виды ввода/вывода, с которыми вы можете общаться (файлы, консоль, сетевые соединения), но так же вам необходимо общаться с ними большим числом способов (последовательный, в случайном порядке, буферный, бинарный, посимвольный, построчный, пословный и т.п.).
Разработчики библиотеки Java атаковали эту проблему путем создания множества классов. Фактически, существует так много классов для системы ввода/вывода в Java, что это может сначала испугать (по иронии, дизайн ввода/вывода Java I/O на самом деле предотвращает взрыв классов). Также произошли значительные изменения в библиотеке ввода/вывода после версии Java 1.0, когда изначально byte-ориентированная библиотека была пополнена char-ориентированными, основанными на Unicode I/O классами. Как результат, есть некоторое количество классов, которые необходимо изучить прежде, чем вы поймете достаточно хорошо картину ввода/вывода Java и ее правильно использовать. Кроме того, достаточно важно понимать историю эволюции библиотеки ввода/вывода, даже если вашей первой реакцией было: “не надоедайте мне историей, просто покажите мне, как использовать это!” Проблема в том, что без исторической перспективы вы постоянно будете смущаться некоторыми классами, определяя, когда вы должны, а когда не должны использовать их.
Эта глава даст вам введение в различные классы ввод/вывода стандартной библиотеки Java и расскажет о том, как их использовать.
Идентификация типа времени выполнения
Идея механизма идентификации типа времени выполнения (RTTI - run-time type identification) кажется довольно простой вначале: он позволяет Вам определить точный тип объекта только по ссылке на базовый тип.
Однако, необходимость RTTI раскрывает огромное количество интересных (и зачастую запутанных) решений ОО дизайна, и, ставит фундаментальные вопросы - как Вам следует строить свои программы.
Эта глава показывает пути, которые Java предоставляет для получения информации об объекте и классах во время выполнения. Она дает две формы: “традиционный” механизм RTTI, который предполагает, что все типы у Вас доступны во время компиляции и выполнения, а также механизм “рефлексии”, который позволяет Вам получить информацию о классе исключительно во время выполнения. Вначале будет описан “традиционный” механизм, а затем будет обсуждение рефлексии.
Создание окон и Апплеты
Фундаментальный принцип дизайна - “делать простые вещи легкими, а трудные - возможными”.[61]
Основной целью дизайна библиотеки графического интерфейса пользователя (GUI) в Java 1.0 было позволить программисту построить GUI, который хорошо выглядит на всех платформах. Эта цель не была достигнута. Вместо этого Абстрактный Оконный Инструментарий Java 1.0 ( Аbstract Window Toolkit - AWT) вводил GUI, который выглядел достаточно заурядно на всех платформах. Кроме того, он был ограничен: вы могли использовать только четыре шрифта и вы не могли получить доступ к любому более сложному и тонкому GUI элементу, имеющемуся в вашей операционной системе. Модель программирования Java 1.0 AWT также была слабая и не объектно-ориентированная. Студент на одном из моих семинаров (который был в Sun во время создания Java) объяснил почему: начальная версия AWT была концептуализирована, разработана и реализована за месяц. Конечно - это чудо продуктивности, а также является предметом объяснения, почему дизайн так важен.
Ситуация улучшилась с появлением модели событий с Java 1.1 AWT, которая стала намного понятней, использовала объектно-ориентированный подход, наряду с добавлением JavaBeans, имела модель компонентного программирования, которая ориентируется на легкое создание среды визуального программирования. Java 2 завершила переход от старого Java 1.0 AWT, тщательно заменяя все, начиная с Фундаментальных Классов Java (Java Foundation Classes - JFC), часть GUI, которая теперь называется “Swing”. Теперь есть множество легких в использовании и понимании JavaBeans, которые могут быть перетянуты и брошены (наряду с программированием в ручную) для создания GUI, которым вы можете (наконец) быть удовлетворены. Правила “третьей ревизии” программной индустрии (продукт не считается хорошим до третьей ревизии) выглядит истинным для языков программирования.
Эта глава не охватывает ничего наиболее современного - библиотеку Java 2 Swing, а разумно считает, что Swing - это финальная стадия GUI библиотеки для Java. Если по некоторым причинам вам необходимо использовать изначальную “старую” библиотеку AWT (потому что вы поддерживаете старый код или у вас есть ограничения со стороны броузера), вы можете найти это описание в первой редакции этой книги, доступной на www.BruceEckel.com (также включенной в CD-ROM, прилагаемый к этой книге).
Далее в этой главе вы увидите, как отличаются вещи, когда вы хотите создать апплет и когда вы хотите создать обычное приложение с использование Swing, и как создать программу, являющуюся и приложением и апплетом, так чтобы она могла запускаться в броузере или из командной строки. Почти все GUI примеры в этой книге могут быть исполнены либо как апплет, либо как приложение.
Пожалуйста, запомните, что это не полный список всех компонентов Swing или всех методов для описанных классов. То, что вы увидите здесь, будет простым. Библиотека Swing обширна, и цель этой главы только ввести вас, познакомив с сутью и прелестью концепции. Если вам нужно больше, то, вероятно, Swing даст вам то, что вы хотите, если вы захотите заняться исследованием.
Здесь я принимаю во внимание, что вы имеете закаченную и установленную (бесплатную) документацию по библиотеке Java в формате HTML, имеющуюся на java.sun.com и буду рассматривать классы javax.swing этой документации, чтобы увидеть все детали и методы библиотеки Swing. Из-за простоты дизайна Swing здесь вы найдете достаточно информации для решения вашей проблемы. Есть много (более толстых) книг, посвященных исключительно Swing, и вы можете перейти к ним, если вам необходима большая глубина охвата, или если вы хотите изменить родное поведение Swing.
Когда вы выучите Swing, вы обнаружите:
Swing - наиболее лучшая модель программирования, по сравнению с теми, которые вы, вероятно, видели в других языках и средах разработки. JavaBeans (которая будет введена ближе к концу книги) - это рабочее пространство для работы библиотеки.
“Построители GUI” (среды визуального программирования) строго следят за аспектами полновесной среды Java разработки. JavaBeans и Swing позволяют построителю писать код для вас при помещении компонентов на форму, используя графические инструменты. Это не только многократно ускоряет разработку во время построения GUI, это позволяет увеличить экспериментирование и, таким образом, позволяет пробовать больше дизайнов и, в конце концов, прийти к какому-то одному лучшему.
Простота и хорошо спроектированная природа Swing означает, что если даже вы используете построитель GUI, а не пишете код руками, результирующий код все равно будет оставаться понятным — это решает большую проблему построителей GUI, которые легко генерируют не читаемый код.
Swing содержит все компоненты, которые вы ожидаете увидеть в современном интерфейсе пользователя, все, начиная от кнопок, содержащих рисунки, заканчивая деревьями и таблицами. Это большая библиотека, но она разработана так, чтобы иметь определенную сложность для имеющихся под рукой задач — если что-то просто, вы не пишите много кода, но если вы пытаетесь создать более сложную вещь, ваш код, вероятно, становится более сложным. Это значит легкость в подходе, но вы получите мощь, если она вам нужна.
Все, что вы захотите от Swing, может быть названо “ортогональностью использования”. То есть, как только вы схватите главные идеи библиотеки, вы можете применять их везде. Главным образом, из-за стандартного соглашения об именах, большую часть времени, что я писал эти примеры, я мог догадаться об именах методов, и был прав без дополнительного поиска. Это, конечно, отличительный признак хорошего дизайна библиотеки. Кроме того, вы, как правило, можете включать компоненты в другие компоненты, и вещи будут работать правильно.
Для скорости все компоненты являются “легковесными”, и Swing целиком написана на Java для портативности.
Клавиатура используется автоматически — вы можете запускать Swing приложения без использования мыши, и это не требует дополнительного программирования. Поддержка скроллинга не требует усилий — вы просто оборачиваете ваш компонент с помощью JScrollPane, когда вы добавляете его в вашу форму. Такие особенности, как инструмент подсказок, обычно требует одну строку кода для использования.
Swing также поддерживает радикальные особенности, называемые “настраиваемы look and feel”, который означает, что UI может динамически меняться в соответствии с ожиданием пользователя для разных платформ и разных операционных систем. Даже возможно (хотя трудно) выдумать ваш собственный вид.
Множественные нити процессов
Объекты позволяют разбить программу на независимые секции. Часто также необходимо превратить программу в несколько независимо выполняющихся подзадач.
Каждая такая подзадача называетсяпроцесс (другие способы перевода: нити или потоки, чтобы избежать путаницы с потоками (stream), в данной главе thread будет переводиться как процесс - Прим.перев. ), а ваша программа выполняется так, как если бы каждый процесс был запущен сам по себе на отдельном процессоре. Некоторые нижележащие механизмы действительно разделяют время процессора для вашей задачи, но в основном вам нет необходимости думать об этом, в результате чего программирование с множественными процессами становится простой задачей.
Процесс - автономно выполняемая программа, запущенная в своем собственном адресном пространстве. Многозадачная операционная система способна запускать более одного процесса (программы) одновременно, это выглядит так, как будто каждая выполняется сама по себе, за счет периодической передачи кванта времени процессора для каждой задачи. Процесс есть простой последовательный поток управления в процессоре. Следовательно, один процессор может выполнять несколько конкурирующих процессов.
Применение множества процессов разнообразно, но в основном у вас есть часть программы привязанная к определенному событию или ресурсу и позволяющая выполнять их независимо от основной программы. Хороший пример этому кнопка "Выход" - не хотелось бы опрашивать ее состояние в каждом куске кода программы, но, в то же время, она должна реагировать на нажатие так, как будто регулярно проверяется. Фактически одна из наиболее привлекательных причин использования множества процессов в создании быстрореагирующего пользовательского интерфейса.
Распределенные вычисления
Исторически, программирование для нескольких машин было трудоемким, сложным и полным ошибок.
Программист должен знать множество деталей о сети, а иногда, также об аппаратном обеспечении. Вам нужно было разбираться с различными “слоями” сетевых протоколов, и, кроме того, существовало множество функций в сетевых библиотеках для соединения, распаковки, упаковки блоков информации; передачи этих блоков туда и обратно; и подтверждения связи. Это была тяжелая задача.
Однако, основная идея распределенного программирования не так сложна, и легко резюмируется в библиотеках Java. Вы хотите:
Получать информацию с одной машины и переместить на другую машину, либо наоборот. Это выполняется с помощью простейшего сетевого программирования. Подключаться к базе данных, находящейся в сети. Это реализуется с помощью библиотеки Java DataBase Connectivity (JDBC), которая абстрагируется от связанных с платформой деталей SQL (the structured query language - структурированный язык запросов - используемый в большинстве транзакций баз данных). Предоставлять услуги через Web сервер. Достигается с помощью Java сервлетов и Java Server Pages (JSPs). Выполнять прозрачный запуск методов объектов Java, которые расположены на удаленных машинах, как будто они располагаются на локальных машинах. Выполняется с помощью Remote Method Invocation (RMI). Использовать код, написанный на других языках, работающий в других архитектурах. Это выполняется с помощью Common Object Request Broker Architecture (CORBA), технологии, которая напрямую поддерживается языком Java. Изолировать бизнес правила от соединения, особенно, соединения с базами данных, включающего обработку транзакций и безопасность. Это выполняется с помощью Enterprise JavaBeans (EJBs). EJBs на самом деле не является распределенной архитектурой, но конечные приложения обычно используются в сетевых клиент-серверных системах. Простое, динамическое добавление и удаление устройств из сетевого представления локальной системы. Это выполняется с помощью Jini из Java.
Для каждого пункта будет дано небольшое введение в этой главе. Обратите внимание, что каждая тема довольно объемная и сама по себе может занимать отдельную книгу, так что эта глава предназначена для ознакомления с этими разделами. Это, конечно, не сделает Вас экспертами по данным вопросам (хотя, Вы можете довольно далеко пройти с помощью информации представленной здесь по сетевому программированию, сервлетам и JSPs).
Абсолютное позиционирование
Также возможно установить абсолютное позиционирование графической компоненты таким способом:
Установить null вместо менеджера компоновки для вашего Container: setLayout(null). Вызвать setBounds( ) или reshape( ) (в зависимости от версии языка) для каждого компонента, передавая прямоугольник границы в координатах пикселей. Вы можете выполнить это в конструкторе или в paint( ), в зависимости от того, чего вы хотите добиться.
Некоторые построители GUI широко используют этот подход, но это обычно не лучший способ генерации кода. Более полезные построители GUI используют вместо него GridBagLayout.
Абстрагирование распределенной системы
Jini пробует поднять уровень абстракции для программирования распределенных систем с уровня сетевого протокола до уровня интерфейсов объектов. При быстром распространении встроенных устройств, подсоединенных к сети, многие части распределенной систмы могут поставляться различными производителями. Jini делает необязательным для поставщиков приходить к соглашению на уровне сетевого протокола, который позволяет устройствам взаимодействовать. Вместо этого производители должны принять интерфейсы Java, через которые их устройство смоет взаимодействовать. Процесс обнаружения, присоединения и поиска обеспечивается инфраструктурой Jini времени выполнения, позволяющей устройствам находить друг друга в сети. Как только они найдут друг друга, устройсвта могут общаться друг с другом посредствам Java интерфейсов.
Абстрактные базовые классы и интерфейсы
Часто при разработке вы хотите, чтобы базовый класс представлял только интерфейс для наследуемых классов. Это значит, что вы не хотите, чтобы кто-то реально создавал объект базового класса, а только выполнял обратное преобразование к нему, чтобы использовать интерфейс. Это достигается при создании абстрактного класса, используя ключевое слово abstract. Если кто-либо попробует создать объект абстрактного класса, компилятор предотвратит это. Это инструмент для навязывания определенного дизайна.
Вы также можете использовать ключевое слово abstract для описания методов, которые не будут реализованы сразу — как напоминание “это интерфейсная функция для всех типов, наследуемых от этого класса, но в этом месте она не имеет реализации”. Абстрактный метод может быть создан только внутри абстрактного класса. При наследовании такой метод должен быть реализован или наследуемый класс также станет абстрактным. Создание абстрактных методов позволяет вам помещать методы в интерфейс и не заботиться о возможности создания бессмысленного кода для тела этого метода.
Ключевое слово interface дает концепцию абстрактного класса одним шагом, предотвращая будущее определения функций. Интерфейс - очень удобный и часто используемый инструмент, который обеспечивает отличное разделение интерфейса и реализации. В дополнение вы можете комбинировать много интерфейсов вместе, если хотите, в то время как наследование от нескольких обычных или абстрактных классов не возможно.
Абстрактные методы и классы
Во всех примерах с инструментами, методы базового класса Instrument всегда поддельны, фиктивны. В этих методах всегда при вызове происходило что-то неправильное. Это происходит из-за того, что цель Instrument - создание общего интерфейса для всех дочерних классов.
Единственная причина для создания этого общего интерфейса, то, что он должен быть реализован по разному для каждого отдельного подтипа. Он создает основную форму, так что Вы можете сказать, что общего во всех дочерних классах. Другой путь сказать то же самое, это вызов Instrument абстрактного базового класса
(или просто абстрактного класса). Вы создаете абстрактный класс, когда Вы хотите управлять набором классов, через этот общий интерфейс. Все методы дочерних классов, совпадающие с объявлением сигнатуры базового класса, используют динамическое связывание. (Однако как было написано в предыдущей секции, если имя метода совпадает с именем метода базового класса, а аргументы различны, то это означает перегрузку, что обычно не то, что требуется.)
Если у Вас есть абстрактный класс типа Instrument, объекты этого класса всегда ничего не значат. Это означает, что Instrument является только интерфейсом, а не частным случаем реализации, так что создание объектов Instrument бессмысленно, и Вы вероятно хотели бы оградить пользователей от этой возможности. Это можно осуществить путем внедрения во все методы Instrument вывода сообщения об ошибке, но при этом осуществляется задержка вывода информации при работе программы и требует изнуряющего тестирования пользовательской части. Но всегда все таки лучше ловить проблемы на стадии компиляции.
Java предоставляет механизм для этого, называемый вызов абстрактного метода[37]. Такой метод является не законченным; он имеет только объявление и не имеет тела метода. Ниже приведен синтаксис объявления абстрактного метода:
abstract void f();
Класс, содержащий абстрактные методы, называется абстрактным классом. Если класс содержит один или больше абстрактных методов, этот класс должен быть определен как abstract. (В противном случае компилятор выдаст сообщение об ошибке.)
Если объявлен абстрактный класс, то что компилятор сделает, если кто-то попытается создать объект от этого класса? Поскольку компилятор не может безопасно создать объект абстрактного класса, то Вы получите сообщение об ошибке. Таким образом компилятор заботится о чистоте абстрактного класса и вам нет необходимости беспокоиться об этом.
Если Вы наследуете от абстрактного класса и Вы хотите создать объект нового типа, то Вы должны предоставить определения всех абстрактных методов базового класса. Если же Вы этого не сделаете (а Вы можете решить не делать этого), то дочерний класса будет так же абстрактным и компилятор насильно установит модификатор abstract для этого класса.
Вообще возможно создать abstract класс без включения в него каких либо abstract методов. Такой способ удобно употреблять когда у вас есть класс в котором все равно есть ли в нем какие либо abstract методы, и тем самым Вы предотвратите наследование от этого класса.
Класс Instrument может быть с легкостью превращен в abstract класс. Только некоторые из методов будут abstract, поскольку создание абстрактного метода не требует от вас определение всех методов abstract. Здесь показано, на что это похоже:
Ниже пример с оркестром, модифицированный для использования abstract классов и методов:
//: c07:music4:Music4.java
// Абстрактные методы и классы.
import java.util.*;
abstract class Instrument { int i; // хранилище зарезервировано для всех
public abstract void play(); public String what() { return "Instrument"; } public abstract void adjust(); }
class Wind extends Instrument { public void play() { System.out.println("Wind.play()"); } public String what() { return "Wind"; } public void adjust() {} }
class Percussion extends Instrument { public void play() { System.out.println("Percussion.play()"); } public String what() { return "Percussion"; } public void adjust() {} }
class Stringed extends Instrument { public void play() { System.out.println("Stringed.play()"); } public String what() { return "Stringed"; } public void adjust() {} }
class Brass extends Wind { public void play() { System.out.println("Brass.play()"); } public void adjust() { System.out.println("Brass.adjust()"); } }
class Woodwind extends Wind { public void play() { System.out.println("Woodwind.play()"); } public String what() { return "Woodwind"; } }
public class Music4 { // Не беспокойтесь от типах, поскольку новые типы добавляемые
// в систему, не мешают ей работать правильно:
static void tune(Instrument i) { // ...
i.play(); } static void tuneAll(Instrument[] e) { for(int i = 0; i < e.length; i++) tune(e[i]); } public static void main(String[] args) { Instrument[] orchestra = new Instrument[5]; int i = 0; // Приведение к базовому типу во время добавления в массив:
orchestra[i++] = new Wind(); orchestra[i++] = new Percussion(); orchestra[i++] = new Stringed(); orchestra[i++] = new Brass(); orchestra[i++] = new Woodwind(); tuneAll(orchestra); } } ///:~
Вы можете видеть, что здесь не произошло реального изменения базового класса.
Так поступать очень удобно, создавая abstract классы и методы, потому что создается понятное и для пользователя и для компилятора намеренье об его использования.
ActiveX
Для некоторых проблем соперником Java является Microsoft ActiveX, хотя он имеет полностью другой подход. ActiveX изначально было только решением для Windows, хотя сейчас это становится кросс-платформенной разработкой независимого консорциума. Действительно, ActiveX говорит: “если ваша программа подключается к окружению, то она может быть перенесена на Web страницу и работать под управлением броузера, который поддерживает ActiveX”. (IE напрямую поддерживает ActiveX, а Netscape использует подключаемые модули.) Таким образом, ActiveX не принуждает вас к специальному языку. Если, например, если вы имеете опыт программирования в Windows на таких языках как C++, Visual Basic или Delphi от Borland, вы можете создать компонент ActiveX, почти без изменений вашего знания языка. ActiveX также обеспечивает путь для использования правильного кода на вашей Web странице.
Активация процесса указания имен
Теперь м ыимеем серверное и клиентское приложения, готовые к взаимодействию. Вы видели, что оба они нуждаются в службе указания имен для связывания и разрешения указателя на строковый объект. Вы должны запустить процесс указания имен до запуска сервера и клиента. В JavaIDL служба указания имен является Java приложением, которое поставляется в пакете с продуктом, но для других продуктов это может быть не так. Служба указания имен JavaIDL запускается внутри экземпляра JVM и слушает по умолчанию сетевой порт 900.
Активация сервера и клиента
Теперь вы готовы запустить ваше серверное и клиентское приложение (в этом порядке, так как наш сервер временный). Если вы все установили правильно, то, что вы получите - это единственная строка вывода в клиентской консоли, сообщающая вам текущее время. Конечно это не очень волнующе само по себе, но вы должны принять во внимание одну вещь: даже если они располагаются на одной и той де машине, клиентское и серверное приложения запускаются внутри разных виртуальных машин и они могут общаться через лежащий в основе интегрирующий уровень, ORB и Сервис Указания Имен.
Этот простой пример предназначен ля работы без сети, но ORB обычно конфигурируется для независимости от местоположения. Когда сервер и клиент находятся на разных машинах, ORB может разрешать удаленные строковые ссылки, используя компонент, известный как Implementation Repository. Хотя Implementation Repository является частью CORBA, для него нет спецификации, так что он различен у разных производителей.
Как вы можете видеть, о CORBA есть много больше информации, чем было рассмотрено тут, но вы должны получить основную идею. Если вы хотите получить более подробную информацию относительно CORBA, начните с Web страницы OMG, на www.omg.org. Там вы найдете документацию, белые страницы, работы и ссылки на другие исходные тексты и продукты CORBA.
Альтернатива Externalizable
Если вы не достаточно сильны в реализации интерфейса Externalizable, существует другой подход. Вы можете реализовать интерфейс Serializable и добавить (обратите внимание, я сказал “добавить”, а не “перекрыть” или “реализовать”) методы, называемые writeObject( ) и readObject( ), которые будут автоматически вызваны, когда объект будет, соответственно, сериализоваться и десериализоваться. То есть, если вы обеспечите эти два метода, они будут использоваться взамен сериализации по умолчанию.
Методы должны иметь следующие точные сигнатуры:
private void writeObject(ObjectOutputStream stream) throws IOException;
private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException
С точки зрения дизайна, это мистические вещи. Прежде всего, вы можете подумать так, потому что эти методы не являются частью базового класса или интерфейса Serializable, следовательно, они не будут определены в своем собственном интерфейсе. Но обратите внимание, что они объявлены как private, что означает, что они будут вызываться только другим членом этого класса. Однако на самом деле вы не вызываете их из других членов этого класса, а вместо этого методы writeObject( ) и readObject( ), принадлежащие объекту ObjectOutputStream и ObjectInputStream, вызывают методы writeObject( ) и readObject( ) вашего объекта. (Обратите внимание на мою невероятную сдержанность, из-за которой я не пускаюсь в пространные обличительные речи по поводу использования одних и тех же имен методов здесь. Я просто скажу: путаница.) Вы можете быть удивлены, как объекты ObjectOutputStream и ObjectInputStream получают доступ к private методам вашего класса. Мы можем только иметь в виду, что эта часть составляет магию сериализации.
В любом случае, все, что определено в интерфейсе, автоматически становится public, поэтому, если writeObject( ) и readObject( ) должны быть private, то они не могут быть частью интерфейса. Так как вы должны следовать точным сигнатурам, получаемый эффект тот же самые, как если бы вы реализовали interface.
Может показаться, что когда вы вызываете ObjectOutputStream.writeObject( ), объект с интерфейсом Serializable, который вы передаете, опрашивается (используя рефлексию, не имеет значения) на предмет реализации своего собственного writeObject( ). Если это так, то нормальный процесс сериализации пропускается, и вызывается writeObject( ). Аналогичная ситуация наблюдается и для readObject( ).
Есть еще один поворот. Внутри вашего writeObject( ) вы можете выбрать выполнение стандартного действия writeObject( ), вызвав defaultWriteObject( ). Точно так же, внутри readObject( ) вы можете вызвать defaultReadObject( ). Вот пример, который демонстрирует, как вы можете управлять хранением и восстановлением объектов с интерфейсом Serializable:
//: c11:SerialCtl.java
// Управление сериализацией, путем добавления
// собственных методов writeObject() и readObject().
import java.io.*;
public class SerialCtl implements Serializable { String a; transient String b; public SerialCtl(String aa, String bb) { a = "Not Transient: " + aa; b = "Transient: " + bb; } public String toString() { return a + "\n" + b; } private void writeObject(ObjectOutputStream stream) throws IOException { stream.defaultWriteObject(); stream.writeObject(b); } private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException { stream.defaultReadObject(); b = (String)stream.readObject(); } public static void main(String[] args) throws IOException, ClassNotFoundException { SerialCtl sc = new SerialCtl("Test1", "Test2"); System.out.println("Before:\n" + sc); ByteArrayOutputStream buf = new ByteArrayOutputStream(); ObjectOutputStream o = new ObjectOutputStream(buf); o.writeObject(sc); // Теперь получим это назад:
ObjectInputStream in = new ObjectInputStream( new ByteArrayInputStream( buf.toByteArray())); SerialCtl sc2 = (SerialCtl)in.readObject(); System.out.println("After:\n" + sc2); } } ///:~
В этом примере есть одно обычное поле String, а другое имеет модификатор transient, для обеспечения возможности сохранения не transient поля с помощью метода defaultWriteObject( ), а transient поля сохраняются и восстанавливаются явно. Поля инициализируются внутри конструктора, а не в точке определения, чтобы удостоверится, что они не инициализируются каким-либо автоматическим механизмом во время десериализации.
Если вы будете использовать стандартный механизм записи не transient частей вашего объекта, вы должны вызвать defaultWriteObject( ), как первое действие writeObject( ) и defaultReadObject( ), как первое действие readObject( ). Это странный вызов методов. Он может показать, например, что вы вызываете defaultWriteObject( ) для ObjectOutputStream и не передаете ему аргументов, но все же как-то происходит включение и узнавание ссылки на ваш объект и способа записи всех не transient частей. Мираж.
Для хранения и восстановления transient объектов используется более знакомый код. И еще, подумайте о том, что происходит тут. В main( ) создается объект SerialCtl, а затем он сериализуется в ObjectOutputStream. (Обратите внимание, что в этом случае используется буфер вместо файла — это все тот же ObjectOutputStream.) Сериализация происходит в строке:
o.writeObject(sc);
Метод writeObject( ) должен проверить sc на предмет существования собственного метода writeObject( ). (Не с помощью проверки интерфейса — здесь нет его — или типа класса, а реальной охотой за методом, используя рефлексию.) Если метод существует, он используется. Аналогичный подход используется для readObject( ). Возможно это чисто практический способ, которым можно решить проблему, но он, несомненно, странен.
Анализ и дизайн
Объектно-ориентированное программирование - это новый и отличный путь думать о программировании. Большинство людей имеют проблемы при первом знании о том, как подступить к ООП проекту. Так как вы знаете, что все можно представить объектом, и так как вы учитесь думать в более объектно-ориентированном стиле, вы можете начать создавать “хороший” дизайн, который использует все преимущества, которые предлагает ООП.
Метод (часто называемый методологией) - является набором обработок и процедур, используемых для разбиения сложной проблемы программирования. Многие методы ООП уже сформулированы еще на расцвете объектно-ориентированного программирования. Этот раздел даст вам почувствовать, что вы пытаетесь достигнуть, когда используете метод.
Особенно в ООП методология - поле для многих экспериментов, что особенно важно для понимания, что метод - это попытка решить проблему, прежде чем вы выберите решение. Это особенно верно в Java, где язык программирования предназначен для снижения сложности (по сравнению с С) возникающей в выражениях программ. Это, фактически, может смягчить необходимость во всех-более-сложных методологиях. Вместо этого простых методологий может хватить в Java для большинства типов проблем, которые вы можете встретить, используя простые методологии с процедурными языками.
Так же важно понять, что “методология” часто очень важна и обещает очень много. Что бы вы ни делали при разработке и написании программы - это метод. Это может быть ваш собственный метод и вы можете не осознавать этого, но это процесс, которым вы следуете, создаете. Если это эффективный процесс, для него необходимо только небольшая настройка при работе с Java. Если вы не удовлетворены вашей производительностью и путем, которым идет ваша программа, вы можете захотеть выбрать формальный метод или выбрать часть из большого количества формальных методов.
Пока вы следуете процессу разработки, наиболее важная проблема при этом: не потеряться. Это легко сделать. Большинство из методов анализа и дизайна предназначены для решения больших проблем. Помните, что большинство проектов выходят за рамки категории, так что вы можете обычно иметь удовлетворительный анализ и дизайн с относительно малым числом рекомендаций метода. [8]. Но некоторый сорт процессов, не имеет значения чем ограничено, будет заставлять вас на вашем пути поступать гораздо лучше, чем просто начинать кодировать.
Легко попасть в ловушку и впасть в аналитический паралич, где вы почувствуете, что вы не можете двигаться вперед, потому что вы не сможете обнаружить каждую малую деталь в текущем состоянии. Помните, не имеет значения, сколько анализов вы выполнили, есть некоторые вещи, относительно системы, которые не откроют себя во время дизайна, а многие вещи не откроют себя, пока вы не начнете программировать или даже пока программа не будет запущена. По этому, важно быстро перейти от анализа и дизайна к реализации теста предполагаемой системы.
Это места заслуживает особого внимания. Исходя из истории процедурных языков, похвально то, что команда продвигается осторожно и каждую минуту разбирает и понимает детали, прежде чем перейдет к дизайну и реализации. Конечно, когда создаете DBMS, необходимо понять требования потребителя. Но DBMS - это классическая проблема, которая хорошо поставлена и хорошо понята; в большинстве таких программ структура базы данных является проблемой для решения. Класс проблем программирования, обсуждаемый в этой главе - из “неопределенного (wild-card)” (мой термин) разнообразия, в котором решение - это не просто переформирование хорошо известного решения, а привлечение одного или нескольких “неопределенных (wild-card) факторов” — элементов, для которых нет хорошо известных предыдущих решений, и для которых необходимо исследование [9]. Попытка полного анализа неизвестной проблемы до перехода к дизайну и реализации приводит к параличу анализа, так как вы не имеете достаточно информации для решения проблем такого рода в фазе анализа. Решение таких проблем требует циклического приближения, а это источник рискованного поведения (которое дает понимание, так как вы пробуете выполнить что-то новое и потенциально вознаграждает и повышает знания). Это выглядит как риск, получаемый при “броске” к предварительной реализации, но это может снизить в неопознанном проекте, потому что вы рано выясняете, является ли обычный подход к проблеме жизнеспособным. Разработка продукта - риск в управлении.
Часто получается, что вы “строите одно, чтобы выбросить”. В ООП вы можете выбросить часть, потому что код инкапсулируется в класс, во время первого прохода вы неизбежно создадите определенный полезный дизайн класса и разработаете полезные идеи относительно разрабатываемой системы, которые нет необходимости выбрасывать. Таким образом, первый проход цикла по проблеме не только даст важную информацию для следующего прохода анализа, дизайна и реализации, он также создаст основу кода.
Говорят, что если вы смотрите на методологию, создающую огромное число деталей и предлагающую много шагов и документов, все еще тяжело понять, когда остановиться. Держите в уме, что вы пробуете узнать:
Что такое объекты? (Как вы разделите ваш проект на составные части?) Что есть его интерфейсы? (Какие сообщения вам необходимо посылать каждому объекту?)
Если вы не приобрели ничего, кроме объектов и их интерфейсов, вы можете писать программу. По разным причинам вам может быть необходимо более подробное описание и документация, чем эта, но вы не можете избежать неприятностей.
Процесс может быть разбит на пять частей, и Фаза 0 является обязательной начальной ступенью для использования определенной структуры.
Анализ и проектировка
Extreme Programming Explained, автор Kent Beck (Addison-Wesley, 2000). Я влюблен в эту книгу. Да, я тяготею к радикальному решению вещей связанных с улучшением процесса разработки программ и эта книга при более внимательном взгляде мне кажется очень удачной. Единственная книга произведшая на меня такое же впечатление была PeopleWare (описана ниже), в ней рассказывается в основном об окружении и "разделении" умственных способностей. Extreme Programming Explained говорит о программировании и размещает все места по соответствующим им полочкам. О книге можно еще добавить, картинки в ней нормальные, но лучше будет, если Вы не будете их разглядывать, а будете постигать суть. (Да будет вам известно, что эта книга не имеет штампа допуска UML на своей обложке.) Я все такие предпочитаю работать над своим развитием именно с такими книгами, небольшие книги, маленькие главы, легкость прочтения, удовольствие от осмысления.
UML Distilled, 2nd Edition, автор Martin Fowler (Addison-Wesley, 2000). Когда Вы впервые открываете для себя UML, он выглядит устрашающе, поскольку содержит множество диаграмм и деталей. Соответственно Fowler, считая их несущественными просто выкинул их и оставил только суть. Для большинства проектов, вам достаточно знать всего лишь несколько инструментов диаграмм и Fowler оставил их, с хорошим дизайном, качественными объяснениями, так что не стоит по этому поводу слишком уж беспокоиться. Приятная, тонкая, читабельная книга, первая книга, если вам нужно изучить и главное понять UML.
UML Toolkit, авторы Hans-Erik Eriksson и Magnus Penker, (John Wiley & Sons, 1997). Разъясняют саму сущность UML и то, как его использовать, но кроме этого содержит уроки по Java. Сопроводительный CD ROM содержит коды Java и урезанную версию Rational Rose. Ну что же - превосходное введение в UML и в то, как построить реальную систему.
The Unified Software Development Process, авторы Ivar Jacobsen, Grady Booch и James Rumbaugh (Addison-Wesley, 1999). У меня были все основания для нелюбви к этой книге. Она выглядела, как сборник скучных учебных текстов. Но я был приятно удивлен, только карманные издания книги содержат разъяснения, которые выглядят, как если бы их основы не были бы понятны самим авторам. Основная часть книги не только "прозрачна" и ясна, но и может доставить удовольствие. Но лучше всего, процесс создания практических заданий. Однако, это не Extreme Programming (и не содержит так де ее ясности о тестировании), но она так же и часть UML неумолимой силы, даже если Вы не можете принять XP. Большинство людей залезают на борт "UML это хорошо" (в зависимости от их уровня знания о нем) и наверное вам он то же подойдет. Я думаю, эта книга должна быть главной книгой о UML, а если Вы прочтете ее после книга UML Distilled, то тогда Вы получите больше деталей.
До того, как Вы выберете любой метод, не плохо было бы оценить возможную "прибыль" на будущее от тех, кто не пытался продать один. Легко принять метод без действительного понятия, что вам действительно нужно для вас. Другие принимают решения на основании непреодолимых причин. Но все равно, люди имеют странный небольшой изгиб в голове: Если они верят в то, что что-то может решить их проблемы, они обязательно испробуют это. (Это называется экперементаторством, и это хорошо.) Но если их надежды не оправдались, то они удваивают свои усилия и начинают громко рекламировать крутую вещь, которую они только что обнаружили. (Это - протест, и не хорошо.) Такое обычно происходит при попытке самому разобраться в происходящем, а это не очень хорошо (ну может быть, если Вы просто болеете у себя дома).
Это не просто предположение, которое отрицает все методы, но Вы должны быть во все оружии (прямо вооружены до зубов) мозговыми "средствами", что бы найти в себе силы оставаться в режиме эксперементаторства (Оно не работает, но давайте попробуем что ни будь другое) и не находиться в режиме отрицания (Нет, это на самом деле не проблема, все чудесно и замечательно работает, нам не нужны перемены). Я думаю, что книги описанные ниже, предоставят вам эти самые мозговые "средства", и позволят вам выбирать оптимальный метод выполнения.
Software Creativity, автор Robert Glass (Prentice-Hall, 1995). Это лучшая книга, которую, я когда-либо видел, в ней рассуждается о перспективах использования различных метод. Эта книга - сборник коротких эссе и заметок, которые автор написал собственноручно и на собственном опыте (P.J. Plauger был его помощником), отражающие долгие годы обдумывания и изучения предмета. Они занимательны и достаточно важны; они не заставят вас долго блуждать по оглавлению и не наскучат. Но они так же и не пустой дым; в них содержится сотни ссылок на другие письменные работы и учения. Все программисты и менеджеры должны прочитать эту книгу до того, как они собираются погрузиться в болото методологии.
Software Runaways: Monumental Software Disasters, автор Robert Glass (Prentice-Hall, 1997). Важной стороной это книги является то, что в ней выносятся на первый план те вещи, о которых мы обычно не хотим разговаривать: сколько проектов не просто провалилось, а провалилось с треском. Я знаю, что многие из нас все еще думают: "Это не может случиться со мной" (или "Это не может произойти опять"), а я думаю, что такие мысли не делают нам чести. Сохраняя в памяти все те плохие вещи, которые всегда приводили нас к неправильному действию, мы будем находиться в более выгодной позиции, что бы сделать их все таки правильно.
Peopleware, 2nd Edition, авторы Tom Demarco и Timothy Lister (Dorset House, 1999). В силу того, что авторы имели опыт в разработке программного обеспечения, то эта книга в основном о проектах и командах. А фокус на людях и их потребностях, а не на технологиях и их потребностях. Они рассуждают о создании некой среды, где бы люди были счастливы и продуктивны, а не о правилах, которым эти люди должны следовать и стать в конце концов придатками машин. Это последнее замечание, я думаю, вызовет наибольшее одобрение у программистов, когда метод XYZ будет принят, а они будут тихо сидеть и вспоминать, что же они сделали.
Complexity, автор M. Mitchell Waldrop (Simon & Schuster, 1992). Это не книга, это хроники работы группы ученых различных дисциплин, собравшихся в Санта Фе, в Мексике. Они обсуждали настоящие проблемы, которые их дисциплины не могут разрешить (биржевой рынок в экономике, начальное формирование в биологии, почему люди делают то, что делают, в социологии и т.д.). Такое смешение физики, экономики, химии, математики, социологии и других наук создает прецедент многонаучного поиска пути решения какой либо задачи. Но наиболее важно, то, что использовались совершенно другие пути осмысления этих сверх комплексных проблем: прочь от математического детерминизма и иллюзии, что Вы можете написать эквивалент предиката определяющего все поведения, а вместо этого в первую очередь к наблюдению и исследованию с целью создания шаблонов и последующие попытки съэмулировать при помощи этого шаблона все возможные значения предмета. (Эта книга в частности посвящена решению алгоритмов генетики.) Эта разновидность мышления, а я надеюсь на это, очень удобна для поиска путей для решения все более и более комплексных программных проектов.
Анонимные внутренние классы
Это пример идеален для того, чтобы быть переписанным с использованием анонимных внутренних классов (описанных в Главе 8). В качестве первой пробы, создадим метод filter( ), который возвращает ссылку на FilenameFilter:
//: c11:DirList2.java
// Использование анонимных внутренних классов.
import java.io.*; import java.util.*; import com.bruceeckel.util.*;
public class DirList2 { public static FilenameFilter filter(final String afn) { // Создание анонимного внутреннего класса:
return new FilenameFilter() { String fn = afn; public boolean accept(File dir, String n) { // Получаем информацию о пути:
String f = new File(n).getName(); return f.indexOf(fn) != -1; } }; // Конец анонимного внутреннего класса
} public static void main(String[] args) { File path = new File("."); String[] list; if(args.length == 0) list = path.list(); else list = path.list(filter(args[0])); Arrays.sort(list, new AlphabeticComparator()); for(int i = 0; i < list.length; i++) System.out.println(list[i]); } } ///:~
Обратите внимание, что аргумент для filter( ) должен быть final. Это требуется анонимному внутреннему классу, так как он использует объект внешней части кода, по отношению к нему.
Это лучший дизайн, потому что класс FilenameFilter теперь тесно связан с DirList2. Однако вы можете выбрать этот подход на один шаг раньше, и определить анонимный внутренний класс как аргумент list( ), в этом случае программа будет даже меньше:
//: c11:DirList3.java
// Построение анонимного внутреннего класса "на месте".
import java.io.*; import java.util.*; import com.bruceeckel.util.*;
public class DirList3 { public static void main(final String[] args) { File path = new File("."); String[] list; if(args.length == 0) list = path.list(); else list = path.list(new FilenameFilter() { public boolean accept(File dir, String n) { String f = new File(n).getName(); return f.indexOf(args[0]) != -1; } }); Arrays.sort(list, new AlphabeticComparator()); for(int i = 0; i < list.length; i++) System.out.println(list[i]); } } ///:~
Теперь аргумент у main( ) является final, так как анонимный внутренний класс напрямую использует args[0].
Здесь показано как анонимный внутренний класс позволяет создать быстрые и грязные классы для решения проблемы. Так как все в Java вертится вокруг классов, это может быть полезной техникой написания программ. Одна из выгод в том, что программа содержит код, который решает определенную проблему, изолированную в одном месте. С другой стороны, это не всегда легче для чтения, так что вы должны использовать это с умом.
Анонимный внутренний класс
Следующий пример несколько странен:
//: c08:Parcel6.java
// Метод возвращающий анонимный внутренний класс.
public class Parcel6 { public Contents cont() { return new Contents() { private int i = 11; public int value() { return i; } }; // В этом случае требуется точка с запятой
} public static void main(String[] args) { Parcel6 p = new Parcel6(); Contents c = p.cont(); } } ///:~
Метод cont( ) комбинирует создание возвращаемого значения с описанием класса, который и есть это возвращаемое значение! В дополнение этот класс еще и не имеет своего имени. Делая тему обсуждения немного запутанной, он выглядит как будто Вы начинаете создавать объект Contents:
return new Contents()
Но затем, до того, как Вы поставите точку запятую, Вы заявляете: "Но подождите, я думаю, я описался в определении класса":
return new Contents() { private int i = 11; public int value() { return i; } };
А вот, что означает этот синтаксис: "Создание объекта анонимного класса, который наследует от Contents." Ссылка, возвращаемая выражением new, автоматически приводится к базовому типу, к ссылке Contents. Синтаксис анонимного внутреннего класса - короткая запись следующего кода:
class MyContents implements Contents { private int i = 11; public int value() { return i; } } return new MyContents();
В анонимном внутреннем классе, Contents создается при помощи конструктора по умолчанию. Следующий же код показывает, как поступить, если нужно создать его с помощью конструктора с аргументами:
//: c08:Parcel7.java
// Анонимный внутренний класс вызывающий
// конструткор базового класса.
public class Parcel7 { public Wrapping wrap(int x) { // Вызов базового конструктора:
return new Wrapping(x) { public int value() { return super.value() * 47; } }; // Требуется точка с запятой
} public static void main(String[] args) { Parcel7 p = new Parcel7(); Wrapping w = p.wrap(10); } } ///:~
То есть, Вы просто передаете соответствующий аргумент в конструктор базового класса, конкретно здесь x передается в new Wrapping(x). Анонимный класс не может иметь конструктор, в котором Вы могли бы нормально вызвать super( ).
В обоих предыдущих примерах, точка с запятой не означает конец тела класса (как в C++). Вместо этого, они показывают конец выражения, которое содержит анонимный класс. Таким образом, это равносильно использованию точки запятой где нибудь еще (т.е. они имеют то же значение, что и в любом другом месте).
Что случиться, если вам потребуется осуществить некую часть инициализации для объекта внутреннего анонимного класса? Поскольку он анонимный, то у него нет имени, которое можно передать конструктору, поэтому у него не может быть конструктора. Но все равно, Вы можете осуществить инициализацию в точке определения ваших полей:
//: c08:Parcel8.java
// Анонимный внутренний класс осуществляющий
// инициализацию. Краткая версия
// Parcel5.java.
public class Parcel8 { // Аргумент должен быть final для использования внутри
// анонимного внутреннего класса:
public Destination dest(final String dest) { return new Destination() { private String label = dest; public String readLabel() { return label; } }; } public static void main(String[] args) { Parcel8 p = new Parcel8(); Destination d = p.dest("Tanzania"); } } ///:~
Если Вы определяете анонимный внутренний класс и хотите использовать в нем объект, который определен снаружи этого анонимного внутреннего класса, то тогда компилятор потребует, что бы данный объект был объявлен, как final. Вот поэтому-то аргумент dest( ) - final. Если же Вы забыли, в противном случае появляется ошибка времени выполнения.
Как только Вы научились получать доступ к объектам вне внутреннего класса, так сразу же возникает вопрос, а что делать, если нужно осуществить некое действие похожее на конструктор При помощи инициализации экземпляра, Вы можете, в действительности, создать конструткор для анонимного внутреннего класса:
//: c08:Parcel9.java
// Использование "инициализации экземпляра" для осуществления
// создания анонимного внутреннего класса.
public class Parcel9 { public Destination dest(final String dest, final float price) { return new Destination() { private int cost; // Экземплярная инициализация для каждого объекта:
{ cost = Math.round(price); if(cost > 100) System.out.println("Over budget!"); } private String label = dest; public String readLabel() { return label; } }; } public static void main(String[] args) { Parcel9 p = new Parcel9(); Destination d = p.dest("Tanzania", 101.395F); } } ///:~
Внутри инициализатора Вы можете видеть код, который не будет исполнен как часть инициализатора полей (т.е., выражение if). На самом же деле, инициализатор экземпляра по своей сути есть ничто иное, чем конструктор анонимного класса. Разумеется, что он несколько ограничен; Вы не можете перегрузить инциализатор экземпляра, поэтому у вас есть только один такой "конструктор".
Аргументы 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: Вы можете прочитать его, но не можете изменить его.
Аргументы исключения
Как и многие объекты в Java, вы всегда создаете исключения в куче, используя new, который резервирует хранилище и вызывает конструктор. Есть два конструктора для всех стандартных исключений: первый - конструктор по умолчанию, и второй принимает строковый аргумент, так что вы можете поместить подходящую информацию в исключение:
if(t == null) throw new NullPointerException("t = null");
Эта строка позже может быть разложена при использовании различных методов, как скоро будет показано.
Ключевое слово throw является причиной несколько относительно магических вещей. Обычно, вы сначала используете new для создания объекта, который соответствует ошибочному состоянию. Вы передаете результирующую ссылку в throw. Объект, в результате, “возвращается” из метода, даже если метод обычно не возвращает этот тип объекта. Простой способ представлять себе обработку исключений, как альтернативный механизм возврата, хотя вы будете иметь трудности, если будете использовать эту аналогию и далее. Вы можете также выйти из обычного блока, выбросив исключение. Но значение будет возвращено, и произойдет выход из метода или блока.
Любое подобие обычному возврату из метода здесь заканчивается, потому что куда вы возвращаетесь, полностью отличается от того места, куда вы вернетесь при нормальном вызове метода. (Вы закончите в соответствующем обработчике исключения, который может быть очень далеко — на много уровней ниже по стеку вызова — от того места, где выброшено исключение.)
В дополнение, вы можете выбросить любой тип Выбрасываемого(Throwable)объекта, который вы хотите. Обычно вы будете выбрасывать различные классы исключений для каждого различного типа ошибок. Информация об ошибке представлена и внутри объекта исключения, и выбранным типом исключения, так что кто-то в большем контексте может определить, что делать с вашим исключением. (Часто используется только информация о типе объекта исключения и ничего значащего не хранится в объекте исключения.)
Атрибуты JSP страницы и границы видимости
Просматривая HTML документацию по сервлетам и JSP, вы найдете возможность получать информацию относительно запущенного сервлета или JSP. Приведенный ниже пример отображает некоторые из возможных данных.
//:! c15:jsp:PageContext.jsp
<%--Viewing the attributes in the pageContext--%> <%-- Note that you can include any amount of code inside the scriptlet tags --%> <%@ page import="java.util.*" %> <html><body> Servlet Name: <%= config.getServletName() %><br> Servlet container supports servlet version: <% out.print(application.getMajorVersion() + "."
+ application.getMinorVersion()); %><br> <% session.setAttribute("My dog", "Ralph"); for(int scope = 1; scope <= 4; scope++) { %> <H3>Scope: <%= scope %> </H3> <% Enumeration e = pageContext.getAttributeNamesInScope(scope); while(e.hasMoreElements()) { out.println("\t<li>" + e.nextElement() + "</li>"); } } %> </body></html> ///:~
Этот пример также показывает использование внедрения в HTML и посылку в out для получения результирующей HTML страницы.
Первый кусок информации выдает имя сервлета, который, вероятно, будет просто “JSP”, но это зависит от вашей реализации. Вы также можете обнаружить текущую версию контейнера сервлетов, используя объект приложения. Наконец, после установки атрибутов сессии, отображается “attribute names” в определенных пределах видимости. Чаще всего вы не используете границы видимости в JSP программировании. Они просто показаны тут для придания примеру увлекательности Есть четыре границы видимости атрибута. Вот они: пределы страницы (граница 1), пределы запроса (граница 2), пределы сессии (граница — здесь только один элемент доступен в пределах сессии - это “My dog”, добавленный перед цыклом for), и пределы приложения (граница 4), основанные на объекте ServletContext. Существует единственный ServletContext для кадого “Web приложения” в каждой Java Virtual Machine. (“Web приложение” - это набор сервлетов и содержимого страничек, относящихся к определенному подмножеству пространства имен URL сервера, таких как /каталог. Обычно это устанавливается в конфигурационном файле.) В пределах приложения вы видите объекты, которые представляют путь к рабочему директорию и временному директорию.
@Author
Вот форма:
@author author-information
в которой author-information это, предположительно, ваше имя, но она так же может включать ваш электронный адрес или любую другую подходящую информацию. Когда флаг -author вносится в командную строку javadoc, информация об авторе специально включается в генерируемый HTML документ.
Вы можете иметь несколько ярлыков авторства для всего списка авторов, но они будут помещены последовательно. Вся информация об авторах будет собрана вместе в один параграф генерируемого HTML.
Автоинкремент и автодекремент
Java, как и C, полон сокращений. Сокращения могут сделать код более простым в наборе и либо легким, либо трудным для чтения.
Два из лучших сокращений - это операторы инкремента и декремента (часто называемые операторами автоинкремента и автодекремента). Оператор декремента является -- и обозначает “уменьшение на одну единицу измерения”. Оператор инкремента - ++ и означает “увеличить на одну единицу измерения”. Если, например, a - int, выражение ++a еквивалентно (a = a + 1). Операторы инкремента и декремента в результате производят такое же значение, что и переменная.
Есть две версии каждого типа оператора, часто называемые префиксной и постфиксной версией. Преинкремент означает, что оператор ++ стоит перед переменной или выражением, а постинкремент означает, что оператор ++ стоит после переменной или выражения. Аналогично, предекремент означает, что оператор -- стоит перед переменной или выражением, а постдекремент означает, что оператор -- стоит после переменной или выражения. Для преинкремента и предекремента (т.е. ++a или --a), выполняется операция и производится значение. Для постинкремента и постдекремента (т.е. a++ или a--) сначала производится значение, а затем выполняется операция. Как пример:
//: c03:AutoInc.java
// Демонстрирует операторы ++ и --.
public class AutoInc { public static void main(String[] args) { int i = 1; prt("i : " + i); prt("++i : " + ++i); // Преинкремент
prt("i++ : " + i++); // Постинкремент
prt("i : " + i); prt("--i : " + --i); // Предекремент
prt("i-- : " + i--); // Постдекремент
prt("i : " + i); } static void prt(String s) { System.out.println(s); } } ///:~
Вывод этой программы:
i : 1 ++i : 2 i++ : 2 i : 3 --i : 2 i-- : 2 i : 1
Вы можете увидеть, что для префиксной формы вы получаете значение после выполнения операции, а при постфиксной форме вы получаете значение до выполнения операции. Это операторы (отличные от использующих присвоение), которые имеют побочные эффекты. (То есть, они меняют операнд раньше, чем используют его значение.)
Оператор инкремента - это одно из объяснений для имени C++, подразумевающее “один шаг в сторону от C”. В ранней речи о Java Bill Joy (один из создателей) сказал, что “Java=C++--” (C плюс плюс минус минус), намекая, что Java - это C++ с удаленной ненужной сложной частью и поэтому более простой язык. Когда вы будете продвигаться п окниге, вы увидите, что многие части проще, и теперь Java не так прост, как C++.
B: Java Native Interface (JNI)
Данное приложение было написано и используется с разрешения Andrea Parovaglio (www.AndreaProvaglio.com).
Язык Java и его стандартные API самодостаточны для написания полноценного приложения. Но в некоторых случаях Вы должны использовать не-Java код, например, в случае вызова функций специфичных для операционной системы, доступа к специальным аппаратным устройствам, использовании уже существующего не-Java кода или создании критичных ко времени выполнения частей кода.
Для взаимодействия с не-Java кодом требуется специальная поддержка в компиляторе и Виртуальной Машине, и дополнительные средства отображения Java кода в не-Java код. Стандартным решением для вызова не-Java кода, который обеспечивает JavaSoft, называется ava Native Interface, который был введен в этом приложении. Это не глубокая трактовка, и в некоторых случаях вы должны принимать на себя изучение части знаний относительно концепции и техники.
JNI достаточно богатый программный интерфейс позволяющий выполнять системные вызовы из приложений на Java. Данная возможность была добавлена в Java 1.1, устанавливая определенную степень соответствия с их эквивалентами в Java 1.0, native method interface (NMI). NMI имеет спроектированные характеристики которые делают его неподходящими для адаптации на всех виртуальных машинах. По этой причине, будущие версии языка могут не поддерживать NMI, и они не будут здесь описаны.
В настоящий момент JNI разработана как интерфейс с собственными методами написанными только на С или С++. Используя JNI ваши собственные методы могут:
Создавать, проверять и обновлять Java объекты (включая массивы и типы String)
Вызывать Java методы
Ловить и выбрасывать исключения
Загружать классы и получать информацию о классах
Выполнять проверку типов во время исполнения
Таким образом, практически все, что вы можете делать с классами и объектами в Java вы можете выполнить с собственными методами.
Безопасность
Автоматическая загрузка и запуск программ через Internet может показаться мечтой создателей вирусов. ActiveX особенно тернистый путь для безопасности в программировании на стороне клиента. Если вы кликаете на Web сайте, вы можете автоматически загрузить любое число вещей одновременно с HTML страницей: GIF файлы, код сценария, откомпилированный Java код и компоненты ActiveX. Некоторые из них доброкачественные; GIF файлы не могут причинить вред, а языки сценариев обычно ограничены в своих возможностях. Java также была разработана для запуска своих апплетов в “песочнице” безопасности, что предотвращает от записи на диск или доступ к памяти вне пределов песочницы.
ActiveX находится на противоположной стороне спектра. Программирование с ActiveX - это как программирование Windows — вы можете делать все, что захотите. Так что, если вы кликните на странице для закачки компонента ActiveX, этот компонент может стать причиной повреждений файлов на вашем диске. Конечно, программы, которые вы загрузили на ваш компьютер, которые не ограничены запуском внутри Web броузера, могут сделать то же самое. Вирусы, загруженные с Bulletin-Board Systems (BBS) долгое время являлись проблемой, но скорость Interneta увеличивает трудности.
Решением кажется “цифровая подпись”, в которой код проверяется, чтобы указать его автора. Это базируется на идее, что вирус работает потому, что его создатель может быть анонимным, так что если вы уберете анонимность, индивидуальности будут нести ответственность за свое авторство. Это выглядит как хороший план, потому что позволяет программам стать более функциональными, и, я подозреваю, снизит вред злоумышленников. Если, однако, программа имеет неумышленную деструктивную ошибку, это станет причиной проблем.
Подход Java исключает возникновение этих проблем через песочницу. Интерпретатор Java, который есть на вашем локальном Web броузере, проверяет апплет на наличие неблагоприятных инструкций, когда апплет загружается. Обычно, апплет не может писать файлы на диск или стирать файлы (главная опора вирусов). Апплеты, обычно, считаются безопасными и, так как существенно для надежности системы клиент/сервер, любые ошибки языка Java позволяют вирусам постоянно проникать. (Стоит отметить, что программное обеспечение броузера обычно предписывает эти ограничения безопасности, а некоторые броузеры позволяют вам выбирать уровень безопасности для обеспечения различных степеней доступа к вашей системе.)
Вы можете относиться скептически к этим, скорее драконским, ограничениям записи файлов на ваш диск. Например, вы можете пожелать построить локальную базу или сохранить данные, чтобы позже использовать их при автономной работе. На первый взгляд кажется, что, в конечном счете, каждый должен работать в онлайне, чтобы сделать что-нибудь важное, но, как было замечено ранее, это не практично (хотя дешевые “Internet-приборы” когда-нибудь смогут удовлетворить требования значительной части пользователей). Решение - это “подписанный апплет”, который использует шифрование с открытым ключом для проверки, что апплет на самом деле пришел оттуда, как это заявлено. Подписанный апплет все еще может засорить ваш диск, но теоретически, так как вы можете привлечь к ответственности создателя апплета, он не будет делать нехороших вещей. Java обеспечивает набор для цифровой подписи, так что вы будете способны со временем позволить апплету выйти за пределы песочницы, если необходимо.
Цифровая подпись упускает важную проблему, это скорость, из-за которой люди переходят в Internet. Если вы загрузили программу с ошибками, и она сделала что-то неблагоприятное, сколько времени займет восстановление повреждений? Это могут быть дни или даже недели. Поэтому, как вы можете отследить программу, которая сделала это? И что хорошего вы сделаете в результате?
Библиотека инструментов пользователя
С помощью этих знаний, Вы сейчас сможете создать свои собственные библиотеки инструментов, чтобы уменьшить, либо полностью исключить дублирование кода. Вот пример - создание псевдонима для System.out.println( ), чтобы уменьшить объем печати. Это может стать частью пакета с названием tools:
//: com:bruceeckel:tools:P.java
// P.rint & P.rintln сокращения.
package com.bruceeckel.tools;
public class P { public static void rint(String s) { System.out.print(s); } public static void rintln(String s) { System.out.println(s); } } ///:~
Вы можете использовать эти сокращения для печати String либо с новой строки (P.rintln( )), либо на текущей строке (P.rint( )).
Как Вы можете догадаться, этот файл должен располагаться в одном из каталогов, указанных в CLASSPATH плюс com/bruceeckel/tools. После компиляции файл P.class может использоваться где угодно в Вашей системе после выражения import:
//: c05:ToolTest.java
// Использует библиотеку инструментов.
import com.bruceeckel.tools.*;
public class ToolTest { public static void main(String[] args) { P.rintln("Available from now on!"); P.rintln("" + 100); // Приводит к типу String
P.rintln("" + 100L); P.rintln("" + 3.14159); } } ///:~
Обратите внимание, что все объекты могут быть преобразованы в представление String простой установкой их в выражение вместе с объектом String; в примере выше, это делается с помощью помещения пустой строки в начале выражения String. Однако, есть небольшое замечание. Если Вы вызываете System.out.println(100), это работает без приведения к типу String. Конечно, Вы можете, используя перегрузку, заставить класс P делать то же самое (это упражнение представлено в конце этой главы).
Итак, начиная с этого момента, как только у Вас появляется новая полезная утилита, Вы вполне можете добавить ее в каталог tools. (Либо в Ваш собственный каталог util или tools.)
Битовые операторы
Битовые операторы позволяют вам манипулировать индивидуальным битом в интегрированном примитивном типе данных. Битовые операторы вычисляются по Булевой алгебре над соответствующими битами двух аргументов для произведения результата.
Битовые операторы пришли из никоуровневой ориентации C; вы будите часто напрямую манипулировать оборудованием и устанавливать биты в регистрах апаратуры. Java изначально была разработана для встраивания в телевизор, так что низкоуровневая ориентация все еще чувствуется. Однако вы, вероятно, не будете часто использовать битовые операции.
Битовый оператор И (&) производит единицу в выходном бите, если оба входных бита были единицами; в противном случае результат - ноль. Битовый оператор ИЛИ (|) производит единицу в выходном бите, если один из входных бит - единица, и производит ноль, если оба бита - нули. Битовое ИСКЛЮЧАЮЩЕЕ ИЛИ, или XOR (^), производит единицу в выходном бите, если один или другой входной бит - единица, но не оба. Битовая операция НЕ (~, также называемый оператором дополнения) - это унарный оператор; он принимает только один аргумент. (Все остальные битовые операторы - бинарные.) Битовое НЕ на выходе производит бит, противоположных входящему — единицу, если входящий бит - ноль, и ноль, если входящий бит - единица.
Битовые операторы и логические операторы используют одинаковые символы, так что полезно иметь мнемоническуе схему, которая поможет вам запомнить значения: так как биты “малы”, то используется только один символ в битовых операторах.
Битовые операторы можно комбинировать со знаком = для соединения операции и присвоений: &=, |= и ^= являются допустимыми. (Так как ~ - это унарный оператор, он не может комбинироваться со знаком =.)
Тип boolean трактуется как однобитное значение, так что это кое в чем отличается. Вы можете выполнять битовое И, ИЛИ и XOR, но вы не можете выполнять битовое НЕ (предположительно для предотвращения путаницы с логическим НЕ). Для булевских битовых операций имеется то же эффект, что и для логических операций, за исключением того, что они не подвержены короткому замыканию. Также, битовые операции на булевыми типами, включают логический оператор XOR, который не включен в список “логических” операторов. Вы предохранены от использования булевских типов в выражениях сдвига, которые описаны далее.
BitSet
BitSet используется, если вы хотите эффективно хранить много информации. Эта эффективность относится к размеру; если вам нужен эффективный доступ, это немного медленнее, чем использование массива некоторого простого типа.
Кроме того, минимальный размер BitSet имеет размер long: 64 бит. Это подразумевает, что если вы храните что-то маленькое, длиной 8 бит, BitSet будет расточительным; лучше вам будет создать ваш собственный класс, или просто массив, для хранения ваших флагов, если размер имеет значение.
Обычный контейнер растягивается, когда вы добавляете больше элементов, и BitSet так же делает это. Следующий пример демонстрирует работу BitSet:
//: c09:Bits.java
// Демонстрация BitSet.
import java.util.*;
public class Bits { static void printBitSet(BitSet b) { System.out.println("bits: " + b); String bbits = new String(); for(int j = 0; j < b.size() ; j++) bbits += (b.get(j) ? "1" : "0"); System.out.println("bit pattern: " + bbits); } public static void main(String[] args) { Random rand = new Random(); // Получение младшего байта nextInt():
byte bt = (byte)rand.nextInt(); BitSet bb = new BitSet(); for(int i = 7; i >=0; i--) if(((1 << i) & bt) != 0) bb.set(i); else
bb.clear(i); System.out.println("byte value: " + bt); printBitSet(bb);
short st = (short)rand.nextInt(); BitSet bs = new BitSet(); for(int i = 15; i >=0; i--) if(((1 << i) & st) != 0) bs.set(i); else
bs.clear(i); System.out.println("short value: " + st); printBitSet(bs);
int it = rand.nextInt(); BitSet bi = new BitSet(); for(int i = 31; i >=0; i--) if(((1 << i) & it) != 0) bi.set(i); else
bi.clear(i); System.out.println("int value: " + it); printBitSet(bi);
// Test bitsets >= 64 bits:
BitSet b127 = new BitSet(); b127.set(127); System.out.println("set bit 127: " + b127); BitSet b255 = new BitSet(65); b255.set(255); System.out.println("set bit 255: " + b255); BitSet b1023 = new BitSet(512); b1023.set(1023); b1023.set(1024); System.out.println("set bit 1023: " + b1023); } } ///:~
Генератор случайных чисел используется для создания случайным образом byte, short и int, и каждое из них трансформируется в битовый шаблон в BitSet. Это хорошо работает, потому что BitSet - это 64 бита, и никакой из этих типов не заставит увеличиться в размере. Затем создается BitSet из 512 бит. Конструктор резервирует хранилище для удвоенного такого числа бит. Однако, вы все равно можете установить 1024 бита или больше.
Благодарности
Во-первых, спасибо ассоциациям, которые работают со мной для подготовки семинаров, обеспечивают консалтинговую деятельности и развивают проекты по обучению: Andrea Provaglio, Dave Bartlett (который также значительно помог при написании Главы 15), Bill Venners, и Larry O’Brien. Я благодарен вашему терпению в то время как я продолжал попытки создать лучшую модель совместного обучения для таких разных людей какими мы являемся. Спасибо Rolf Andr? Klaedtke (Швецария); Martin Vlcek, Martin Byer, Vlada & Pavel Lahoda, Martin the Bear, и Hanka (Прага); и Marco Cantu (Италия) за прием во время моего первого самостоятельно организованного семинаре в Европе. Спасибо Doyle Street Cohousing Community за поддержку в течении двух лет пока я писал первую редакцию книги (и просто за мою поддержку). Большое спасибо Kevin и Sonda Donovan за сдачу в субаренду отличного места в великолепном Crested Butte, Colorado пока я работал над первой редакцией книги. Также спасибо дружественным обитателям Crested Butte и Rocky Mountain Biological Laboratory, которые так доброжелательно ко мне относились. Спасибо Claudette Moore из Moore Literary Agency за ее громадное терпение и настойчивость в подаче того материала что я хотел. Моя первая книга была опубликована совместно с Jeff Pepper в качестве редактора в Osborne/McGraw-Hill. Jeff появился в нужный момент и в нужное время в Prentice-Hall, прочистил путь и сделал еще много полезных вещей, чтобы мой первый опыт публикации книги был достаточно приятным. Спасибо Jeff, это очень много для меня значило. Я особенно обязан Gen Kiyooka и его компании Digigami, котрые любезно предоставили мне Web сервер на первые несколько лет моего присутствия в сети. Это было бесценно с точки зрения обучения. Спасибо Cay Horstmann (соавтора Core Java, Prentice-Hall, 2000), D’Arcy Smith (Symantec), и Paul Tyma (соавтора Java Primer Plus, The Waite Group, 1996), за разъяснение мне некоторых концепций языка. Спасибо всем тем, кто выступал в секции по Java на конференции Software Development Conference, а также студентам моих семинаров, которые задавали мне те нужные вопросы помогавшие мне сделать материал более доступным. Особая благодарность Larry и Tina O’Brien, которые помогли записать семинары на CD ROM (более подробно на сайте www.BruceEckel.com). Множество людей отправляло мне замечания и я благодарен им всем, но особая благодарность (по первому изданию): Kevin Raulerson (нашедшиму тысячи ошибок), Bob Resendes (просто невероятно), John Pinto, Joe Dante, Joe Sharp (все три просто замечательны), David Combs (за многочисленные грамматические и стилистические исправления), Dr. Robert Stephenson, John Cook, Franklin Chen, Zev Griner, David Karr, Leander A. Stroschein, Steve Clark, Charles A. Lee, Austin Maher, Dennis P. Roth, Roque Oliveira, Douglas Dunn, Dejan Ristic, Neil Galarneau, David B. Malkovsky, Steve Wilkinson, и многие другие. Проф. Ir. Marc Meurrens проделал значительную работу в создании и публикации электронной версии книги, чтобы она стала доступной в Европе. Это был поток технически умных людей в моей жизни, которые стали друзьями, а также были влиятельными, и в то же время необычными в том, что они занимались йогой или практиковали другие формы духовного развития, которых я нашел очень вдохновленными и инструктивными. Kraig Brockschmidt, Gen Kiyooka, and Andrea Provaglio (которые помогли мне в понимании Java и программирование как таковом в Италии, а теперь и в Америке как коллеги по MindView team). Это не настолько необычно для меня, но понимание Delphi помогло мне в понимании Java, поскольку очень многие концепции и конструкции языка общие. Мои друзья, программисты на Delphi, помогли мне понять глубже отличную среду разработки. Это Marco Cantu (другой итальянец — возможно увлечение латинским языком дает способности и к языкам программирования?), Neil Rubenking (который занимался йогой, вегетаринством и Дзен буддизмом пока он не открыл для себя мир компьютеров), и конечно Zack Urlocker, давнишний товарищ, с которым мы путешествуем по свету. Понимание и поддержка моего друга Richard Hale Shaw’s была очень полезна (и Kim тоже). Richard и я провели много месяцев вместе, давая семинары друг другу и пытаясь найти наилучший способ обучения для слушателей. Также спасибо KoAnn Vikoren, Eric Faurot, Marco Pardi и всей команде MFI. Особая благодарность Tara Arrowood, который(ая)? (Прим.перев.) повторно вдохновил(а) меня на возможность создания конференции. Дизайн книги, обложки и все фотография были созданы моим другом - Daniel Will-Harris, известным автором и дизайнером (www.Will-Harris.com), который играл в слова в школе пока ожидал изобретения компьютера и настольных издательских систем, и жаловался на мое бормотание по поводу моих проблем с алгеброй. Однако, все опечатки в книге мои. Для написания книги, а также для создания версии для Adobe Acrobat в формате PDF, использовался Microsoft® Word 97 for Windows. Спасибо корпорациям, которые создали компиляторы: Borland, группе Blackdown (для Linux), и конечно, Sun. Особая благодарность всем моим учителям и всем моим студентам (которые также мои учителя). Учителем, который достаточно забавно писал был Gabrielle Rico (автор Writing the Natural Way, Putnam, 1983). Я буду всегда ценить ужасную неделю в Esalen. В группу поддержки также входили, хотя только ими не ограничивается: Andrew Binstock, Steve Sinofsky, JD Hildebrandt, Tom Keffer, Brian McElhinney, Brinkley Barr, Bill Gates из Midnight Engineering Magazine, Larry Constantine и Lucy Lockwood, Greg Perry, Dan Putterman, Christi Westphal, Gene Wang, Dave Mayer, David Intersimone, Andrea Rosenfield, Claire Sawyers, другие итальянцы (Laura Fallai, Corrado, Ilsa и Cristina Giustozzi), Chris и Laura Strand, Almquists, Brad Jerbic, Marilyn Cvitanic, Mabrys, Haflingers, Pollocks, Peter Vinci, Robbins Families, Moelter Families (и McMillans), Michael Wilk, Dave Stoner, Laurie Adams, Cranstons, Larry Fogg, Mike и Karen Sequeira, Gary Entsminger и Allison Brody, Kevin Donovan и Sonda Eastlack, Chester и Shannon Andersen, Joe Lordi, Dave и Brenda Bartlett, David Lee, Rentschlers, Sudeks, Dick, Patty и Lee Eckel, Lynn и Todd, а также их семья. И конечно, мои папа и мама.
Блок try
Если вы находитесь внутри метода, и вы выбросили исключение (или другой метод, вызванный вами внутри этого метода, выбросил исключение), такой метод перейдет в процесс бросания. Если вы не хотите быть выброшенными из метода, вы можете установить специальный блок внутри такого метода для поимки исключения. Он называется блок проверки, потому что вы “проверяете” ваши различные методы, вызываемые здесь. Блок проверки - это обычный блок, которому предшествует ключевое слово try:
try { // Код, который может сгенерировать исключение
}
Если вы внимательно проверяли ошибки в языке программирования, который не поддерживает исключений, вы окружали каждый вызов метода кодом установки и проверки ошибки, даже если вы вызывали один и тот же метод несколько раз. С обработкой исключений вы помещаете все в блок проверки и ловите все исключения в одном месте. Это означает, что ваш код становится намного легче для написания и легче для чтения, поскольку цель кода - не смешиваться с проверкой ошибок.
Блокировка во время операций ввода/вывода
Если поток ожидает какой-либо активности по вводу/выводу, то он автоматически блокируется. В следующей части примера два класса работают с универсальными объектамиReader иWriter, но для тестового примера канал данных будет установлен так, чтобы позволить двум процессам безопасно передавать данные друг другу (что и есть цель каналов данных).
Sender помещает данные в Writer и засыпает на случайный промежуток времени. Однако, Receiver не имеет sleep(), suspend() или wait() и когда происходит вызов read() он автоматически блокируется до тех пор пока есть данные.
///:Continuing
class Sender extends Blockable { // send
private Writer out; public Sender(Container c, Writer out) { super(c); this.out = out; } public void run() { while(true) { for(char c = 'A'; c <= 'z'; c++) { try { i++; out.write(c); state.setText("Sender sent: " + (char)c); sleep((int)(3000 * Math.random())); } catch(InterruptedException e) { System.err.println("Interrupted"); } catch(IOException e) { System.err.println("IO problem"); } } } } }
class Receiver extends Blockable { private Reader in; public Receiver(Container c, Reader in) { super(c); this.in = in; } public void run() { try { while(true) { i++; // Show peeker it's alive
// Blocks until characters are there:
state.setText("Receiver read: "
+ (char)in.read()); } } catch(IOException e) { System.err.println("IO problem"); } } } ///:Continued
Оба класса также помещают информацию в их поле state
и изменяют значение i, так что Peeker контролирует выполнение процессов.
Блокировки
Процесс может быть в одном из четырех состояний:
New: Процесс был создан, но не был еще запущен, так что он не может выполняться.
Runnable: Это означает, что процесс может
быть выполнен когда механизм распределения квантов времени CPU даст возможность выполняться процессу. Так, процесс может, а может и не быть выполняемым, но ему ни чего не препятствует быть выполняемым в том момент, когда пришла его очередь (квант времени); он не мертв и не заблокирован.
Dead: Нормальный способ процесса завершиться является возврат из его run() метода. Можно также вызвать stop( ), но это вызовет исключение являющееся подклассом Error (что означает, что вы не поместили вызов в блок try). Помните, что генерация исключения должно быть специальным событием и не является частью нормального хода выполнения программы; так, использование stop() запрещено (deprecated) в Java2. Также существует метод destroy() (который ни когда не был реализован), который вы не должны вызывать если можно этого избежать поскольку это радикальное решение и не снимает блокировку объекта.
Blocked: Процесс может быть запущен, но не будет выполняться. Пока процесс находиться в блокированном состоянии планировщик просто пропускает его и не выделяет квантов времени. До тех пор, пока процесс не перейдет в состояние runnable, процесс не выполнит ни одной операции.