![](/user_photo/2706_HbeT2.jpg)
- •Кен Арнольд Джеймс Гослинг Дэвид Холмс Язык программирования Java
- •Глава 1 первое знакомство с java 6
- •Глава 2 классы и объекты 29
- •Глава 3 расширение классов 47
- •Глава 4 интерфейсы 70
- •Глава 5 лексемы, операторы и выражения 78
- •Глава 6 порядок выполнения 105
- •Глава 7 исключения 113
- •Глава 8 строки 121
- •Глава 9 потоки 134
- •Глава 10 пакеты 156
- •Глава 11 пакет ввода/вывода 158
- •Глава 12 стандартные вспомогательные средства 183
- •Глава 13 применение типов в программировании 205
- •Глава 14 системное программирование 218
- •Глава1 первое знакомство сjava
- •1.1. С самого начала
- •1.2.Переменные
- •1.3. Комментарии
- •1.4.Именованные константы
- •1.4.1. Символы Unicode
- •1.5.Порядок выполнения
- •1.6.Классы и объекты
- •1.6.1.Создание объектов
- •1.6.2.Статические поля
- •1.6.3.Сборщик мусора
- •1.7.Методы и параметры
- •1.7.1.Вызов метода
- •1.7.2.Ссылка this
- •1.7.3.Статические методы
- •1.8.Массивы
- •1.9.Строковые объекты
- •1.10.Расширение класса
- •1.10.1.Класс Object
- •1.10.2.Вызов методов суперкласса
- •1.11. Интерфейсы
- •1.12.Исключения
- •1.13.Пакеты
- •1.14.Инфраструктура Java
- •1.15.Прочее
- •Глава 2 классы и объекты
- •2.1. Простой класс
- •2.2. Поля
- •2.3. Управление доступом и наследование
- •2.4. Создание объектов
- •2.5. Конструкторы
- •2.6. Методы
- •2.6.1. Значения параметров
- •2.6.2. Применение методов для ограничения доступа
- •2.7. Ссылка this
- •2.8. Перегрузка методов
- •2.9. Статические члены
- •2.9.1. Блоки статической инициализации
- •2.9.2. Статические методы
- •2.10. Сборка мусора и метод finalize
- •2.10.1. Метод finalize
- •2.10.2. Восстановление объектов в методе
- •2.11. Метод main
- •2.12. Метод toString
- •2.13. Родные методы
- •Глава 3 расширение классов
- •3.1. Расширенный класс
- •3.2. Истинное значение protected
- •3.3. Конструкторы в расширенных классах
- •3.3.1. Порядок вызова конструкторов
- •3.4. Переопределение методов и скрытие полей
- •3.4.1. Ключевое слово super
- •3.5. Объявление методов и классов с ключевым словом final
- •3.6. Класс Object
- •3.7. Абстрактные классы и методы
- •3.8. Дублирование объектов
- •3.9. Расширение классов: когда и как
- •3.10. Проектирование расширяемого класса
- •Глава 4 интерфейсы
- •4.1. Пример интерфейса
- •4.2. Одиночное и множественное наследование
- •4.3. Расширение интерфейсов
- •4.3.1. Конфликты имен
- •4.4. Реализация интерфейсов
- •4.5. Использование реализации интерфейса
- •4.6. Для чего применяются интерфейсы
- •Глава 5 лексемы, операторы и выражения
- •5.1. Набор символов
- •5.2. Комментарии
- •5.3. Лексемы
- •5.4. Идентификаторы
- •5.4.1. Зарезервированные слова Java
- •5.5. Примитивные типы
- •5.6. Литералы
- •5.6.1. Ссылки на объекты
- •5.6.2. Логические значения
- •5.6.3. Целые значения
- •5.6.4. Значения с плавающей точкой
- •5.6.5. Символы
- •5.6.6. Строки
- •5.7. Объявления переменных
- •5.7.1. Значение имени
- •5.8. Массивы
- •5.8.1. Многомерные массивы
- •5.9. Инициализация
- •5.9.1. Инициализация массивов
- •5.10. Приоритет и ассоциативность операторов
- •5.11. Порядок вычислений
- •5.12. Тип выражения
- •5.13. Приведение типов
- •5.13.1. Неявное приведение типов
- •5.13.2. Явное приведение и instanceof
- •5.13.3. Строковое приведение
- •5.14. Доступ к членам
- •5.15. Арифметические операторы
- •5.15.1. Целочисленная арифметика
- •5.15.2. Арифметика с плавающей точкой
- •5.15.3. Арифметика с плавающей точкой и стандарт ieee-754
- •5.15.4. Конкатенация строк
- •5.16. Операторы приращения и уменьшения
- •5.17. Операторы отношения и условный оператор
- •5.18. Поразрядные операции
- •5.19. Условный оператор
- •5.20. Операторы присваивания
- •5.21. Имена пакетов
- •Глава 6 порядок выполнения
- •6.1. Операторы и блоки
- •6.2. Оператор if-else
- •6.3. Оператор switch
- •6.4. Цикл while и do-while
- •6.5. Оператор for
- •6.6. Метки
- •6.7. Оператор break
- •6.8. Оператор continue
- •6.9. Оператор return
- •Глава 7 исключения
- •7.1. Создание новых типов исключений
- •7.2. Оператор throw
- •7.3. Условие throws
- •7.4. Операторы try, catch и finally
- •7.4.1. Условие finally
- •7.5. Когда применяются исключения
- •Глава 8 строки
- •8.1. Основные операции со строками
- •8.2. Сравнение строк
- •8.3. Вспомогательные методы
- •8.4. Создание производных строк
- •8.5. Преобразование строк
- •8.6. Строки и символьные массивы
- •8.7. Строки и массивы byte
- •8.8. Класс StringBuffer
- •8.8.1. Модификация буфера
- •8.8.2. Извлечение данных
- •8.8.3. Работа с емкостью буфера
- •Глава 9 потоки
- •9.1. Создание потоков
- •9.2. Синхронизация
- •9.2.1. Методы synchronized
- •9.2.2. Операторы synchronized
- •9.3. Методы wait и notify
- •9.4. Подробности, касающиеся wait и notify
- •9.5. Планирование потоков
- •9.6. Взаимная блокировка
- •9.7. Приостановка потоков
- •9.8. Прерывание потока
- •9.9. Завершение работы потока
- •9.10. Завершение приложения
- •9.11. Использование Runnable
- •9.12. Ключевое слово volatile
- •9.13. Безопасность потоков и ThreadGroup
- •9.14. Отладка потоков
- •Глава 10 пакеты
- •10.1. Имена пакетов
- •10.2. Пакетный доступ
- •10.3. Содержимое пакета
- •Глава 11 пакет ввода/вывода
- •11.1. Потоки
- •11.2. Класс InputStream
- •11.3. Класс OutputStream
- •11.4. Стандартные типы потоков
- •11.5. Фильтрующие потоки
- •11.6. Класс PrintStream
- •11.7. Буферизованные потоки
- •11.8. Байтовые потоки
- •11.9. Класс StringBufferInputStream
- •11.10. Файловые потоки и FileDescriptor
- •11.11. Конвейерные потоки
- •11.12. Класс Seq uenceInputStream
- •11.13. Класс LineNumberInputStream
- •11.14. Класс PushbackInputStream
- •11.15. Класс StreamTokenizer
- •11.16. Потоки данных
- •11.16.1. Классы потоков данных
- •11.17. Класс RandomAccessFile
- •11.18. Класс File
- •11.19. Интерфейс FilenameFilter
- •11.20. Классы ioException
- •Глава 12 стандартные вспомогательные средства
- •12.1. Класс BitSet
- •12.2. Интерфейс Enumeration
- •12.3. Реализация интерфейса Enumeration
- •12.4. Класс Vector
- •12.5. Класс Stack
- •12.6. Класс Dictionary
- •12.7. Класс Hashtable
- •12.8. Класс Properties
- •12.9. Классы Observer/Observable
- •12.10. Класс Date
- •12.11. Класс Random
- •12.12. Класс String Tokenizer
- •Глава 13 применение типов в программировании
- •13.1. Класс Class
- •13.2. Загрузка классов
- •13.3. Классы-оболочки: общий обзор
- •13.4. Класс Boolean
- •13.5. Класс Character
- •13.6. Класс Number
- •13.7. Класс Integer
- •13.8. Класс Long
- •13.9. Классы Float и Double
- •Глава 14 системное программирование
- •14.1. Стандартный поток ввода/вывода
- •14.2. Управление памятью
- •14.3. Системные свойства
- •14.4. Создание процессов
- •14.5. Класс Runtime
- •14.6. Разное
- •14.7. Безопасность
- •14.8. Класс Math
- •Приложение а Родные методы
- •А.1 Обзор
- •А.2.1 Имена
- •А.2.2 Методы
- •А.2.3 Типы
- •А.2.5 Средства безопасности
- •А.2.6 Работа с памятью
- •А.3 Пример
- •А.3.1 Внутреннее строение LockableFile
- •А.4 Строки
- •А.5 Массивы
- •А.6 Создание объектов
- •А.7 Вызов методов Java
- •А.8 Последнее предупреждение
- •Приложение б Runtime-исключения в Java
- •Б.1 Классы RuntimeException
- •Б.2 Классы Error
- •Приложение в Полезные таблицы
13.2. Загрузка классов
Runtime-система Java обращается к классам, когда в этом возникает необходимость. Подробности загрузки классов могут отличаться для различных реализаций Java, однако в большинстве случаев используется механизм “пути класса” для поиска компилированного байт-кода класса, используемого в программе, но не загружавшегося ранее. Во многих случаях этот стандартный механизм работает отлично, однако немалая часть достоинств Java обусловлена возможностью реализовать загрузку классов с учетом специфики приложения. Чтобы написать программу, в которой механизм загрузки классов отличается от стандартного, необходимо создать объект ClassLoader, который получает байт-коды классов и загружает их во время выполнения программы.
Например, вы разрабатываете игру, в которой играющие могут создавать собственные классы, использующие выбранную ими стратегию. Для этого вы создаете абстрактный класс Player, расширяемый игроками для реализации своих идей. Когда игроки будут готовы испытать свою стратегию, они пересылают скомпилированный байт-код класса в вашу систему. Байт-код необходимо загрузить в игру, применить и вернуть игроку его результат (score). Схема выглядит следующим образом:
На сервере игровая программа загружает каждый ожидающий своей очереди класс Player, создает объект нового типа и дает ему возможность противопоставить свою стратегию игровому алгоритму. Когда выясняется результат, он сообщается игроку—разработчику стратегии.
Механизм коммуникаций здесь не рассматривается, однако он может быть упрощен до сообщений электронной почты, с помощью которых игроки посылают свои классы и получают результаты.
Наибольший интерес представляет процесс загрузки игровой программой скомпилированных классов. Здесь всем заправляет загрузчик классов. Чтобысоздать загрузчик классов, следует расширить абстрактный класс Class Loader и реализовать в подклассе метод loadClass:
protected abstract Class loadClass(String name, boolean resolve)throws ClassNotFoundException
Загружает класс с заданным именем name. Если значение resolve равно true, то метод должен вызвать resolveClass, чтобы обеспечить загрузку всех классов, используемых внутри заданного.
В нашем примере мы создадим класс PlayerLoader, предназначенный для чтения байт-кода классов со стратегией игры и подготовки их к работе. Основной цикл будет выглядеть следующим образом:
public class Game {
static public void main(String[] args) {
String name; // имя класса
while ((name = getNextPlayer()) != null) {
try {
PlayerLoader loader = new PlayerLoader();
Class
classOf = loader.loadClass(name, true);
Player
player = (Player)classOf.newInstance();
Game game = new Game();
player.play(game);
game.reportScore(name);
} catch (Exception e) {
reportException(name, e);
}
}
}
}
Для каждой новой игры нужен свой объект-загрузчик PlayerLoader; следовательно, новый класс Player не будет смешиваться с классами, загруженными ранее. Новый PlayerLoader загружает класс и возвращает представляющий его объект Class, который используется для создания нового объекта класса Player. Затем мы создаем новую игру game и играем в нее. После ее завершения возвращается результат.
Класс PlayerLoader расширяет класс ClassLoader и задает собственную реализацию метода loadClass:
class PlayerLoader extends ClassLoader {
private Hashtable Classes = new Hashtable();
public Class loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
try {
Class newClass = (Class)Classes.get(name);
if (newClass == null) { // еще не определен
try { // проверить, не является
// ли системным классом
newClass = findSystemClass(name);
if (newClass != null)
return newClass;
} catch (ClassNotFoundException e) {
; // продолжить поиск
}
// класс не найден - его необходимо загрузить
byte[] buf = bytesForClass(name);
newClass = defineClass(buf, 0, buf.length);
Classes.put(name, newClass);
}
if (resolve)
resolveClass (newClass);
return newClass;
} catch (IOException e) {
throw new ClassNotFoundException(e.toString());
}
}
// ... bytesForClass() и все прочие методы ...
}
Любая реализация loadClass должна загружать класс лишь в том случае, если это не было сделано ранее, поэтому метод содержит хеш-таблицу загруженных классов. Если класс уже присутствует в ней, то возвращается соответствующий объект Class. В противном случае loadClass сначала проверяет, можно ли найти класс в локальной системе, для чего вызывает метод find SystemClass класса ClassLoader; этот метод осуществляет поиск не только среди системных классов (находящихся в пакетах java), но и в пути, заданном для классов. Если при этом будет найден нужный класс, то после выполнения загрузки возвращается соответствующий ему объект Class.
Если поиск в обоих случаях заканчивается безрезультатно, необходимо прочитать байт-код класса, для чего служит метод bytesForClass:
protected byte[] bytesForClass(String name)
throws IOException, ClassNotFoundException
{
FileInputStream in = streamFor(name);
int length = in.available(); // получить количество байтов
if (length == 0)
throw new ClassNotFoundException(name);
byte[] buf = new byte[length];
in.read(buf); // прочитать байт-код
return buf;
}
В нем использован метод streamFor (не приводится) для получения потока FileInputStream, содержащего байт-код класса. Затем мы создаем буфер нужного размера, считываем весь байт-код и возвращаем буфер.
Когда loadClass получает байт-код и вызывается метод defineClass класса ClassLoader, в качестве параметров этот метод получает массив byte, начальное смещение и количество байтов— в указанной части массива должен находиться байт-код класса. В данном случае байт-код занимает весь массив.Когда определение класса завершается, он добавляется в хеш-таблицу Classes, чтобы предотвратить его повторную загрузку.
После успешной загрузки класса метод loadClass возвращает новый объект Class.
Невозможно выгрузить класс, когда он перестает быть нужным. Вместо этого вы просто прекращаете его использование, чтобы класс-загрузчик мог быть уничтожен сборщиком мусора.
Чтобы получить объект-загрузчик для заданного объекта Class, следует вызвать его метод getClassLoader. Процесс загрузки классов был рассмотрен выше. Если у данного класса отсутствует класс-загрузчик, метод возвращает null.
Класс-загрузчик применяется лишь на первой стадии подготовки класса к работе. Всего же существует три стадии:
1. Загрузка: получение байт-кода с реализацией класса.
2. Компоновка: поиск супертипов класса и загрузка их в случае необходимости.
3. Инициализация: присвоение начальных значений статическим полям класса и выполнение их инициализаторов, а также всех статических блоков.
Упражнение 13.2
Реализуйте классы Game и Player для какой-нибудь простой игры— например, “крестики-нолики”. Проведите оценку различных реализаций Player по данным нескольких запусков.