Язык JavaФ [1] - это новый объектно-ориентированный язык программирования, созданный фирмой Sun для разработки программ, распространяемых по сети Internet [2]. Система программирования Java позволяет использовать World Wide Web (WWW) для распространения небольших интерактивных прикладных программ (апплетов), которые размещаются на серверах Internet, транспортируются клиенту по сети (точно так же, как картинки или звуковые файлы), автоматически устанавливаются и запускаются на месте, как часть документа WWW. При этом апплет имеет весьма ограниченный доступ к ресурсам компьютера клиента, так что он может предоставить произвольный мультимедийный интерфейс и выполнять сложные вычисления, не привнося при этом риска заражения вирусом или порчи данных.
Система программирования Java может служить основой для совместной разработки больших программных систем коллективом разработчиков, связанных между собой только через WWW (они и знакомы между собой могут быть лишь заочно, через e-mail, а когда они наконец повстречаются где-нибудь на международном симпозиуме, в их активе уже может быть совместно разработанная программная система). Java и WWW являются первыми системами, обеспечивающими такую возможность, поэтому их внедрение и распространение многие программисты справедливо называют революцией в разработке программного обеспечения. Ясно, что ведущую роль в обеспечении указанной возможности играет именно Java, так как именно Java позволяет распространять не просто тексты, а работающие программы и их фрагменты (апплеты) по WWW.
Отсюда большой интерес к языку и системе программирования Java со стороны буквально всех категорий разработчиков и пользователей программного обеспечения. Все ведущие фирмы, разрабатывающие компьютерную аппаратуру и программное обеспечение (и IBM, и DEC, и Microsoft, список можно продолжать очень долго) официально объявили о поддержке языка и системы программирования Java. Все распространенные инструментальные системы уже поддерживают программирование на Java. В WWW можно найти сотни тысяч публикаций и программной продукции, связанных с Java, в том числе свободно распространяемая система программирования Java, разработанная фирмой Sun-Soft (недавно из этой фирмы выделилась самостоятельная фирма Java-Soft), а также свободно распространяемая система программирования GNU-Java, разработанная FSF. Написаны десятки учебных пособий по Java (часть из них переведена на русский язык и издана в нашей стране), статьи и обзоры по языку Java и его применениям регулярно публикуются в серьезных и популярных программистских журналах и еженедельниках.
Цель данной публикации - ознакомить читателей с основными свойствами и особенностями системы программирования Java и показать, как можно использовать многочисленные Java-апплеты, доступные в среде Internet, и другие возможности языка и системы программирования Java в своей повседневной деятельности.
Сначала будет дано краткое описание особенностей
синтаксиса и семантики конструкций языка Java, используя для сравнения
всем хорошо известный объектно-ориенти-
рованный язык C++. Внимание читателя будет обращено только на
принципиальные моменты, с дальнейшими подробностями можно ознакомиться
по описанию языка и другим основополагающим документам фирмы Sun
[1,3,4], а также с помощью многочисленных учебных пособий и руководств
по языку Java (см., например, [5]).
Затем будет описана система программирования языка Java. В ее состав входят следующие компоненты:
Возможности системы программирования языка Java (в частности, утилиты и некоторые библиотечные классы) будут продемонстрированы на примере разработки и использования нескольких простых апплетов, доступных по WWW.
Язык Java и его окружение непрерывно развиваются: постоянно появляются новые инструменты, многие системы интегрируются с системой Java. Развитием окружения Java занимаются группы во всех университетах, а также мощные компании (фирмы), разрабатывающие компьютерную аппаратуру и программное обеспечение. Поэтому основная часть данной публикации посвящена проблемам развития языка и системы программирования Java. На примере Java можно проследить как язык, первоначально ориентированный, в основном, на написание апплетов, постепенно превращается в мощный универсальный язык программирования. Развитие окружения Java нетрудно проследить по многочисленным публикациям в WWW, но именно их многочисленность делает эту задачу довольно трудной: ведь нужно не только успевать просмотреть все эти, порой противоречивые, а порой и просто ошибочные публикации, но и суметь сделать правильные выводы о тенденциях развития Java. Одна из попыток уловить такие тенденции и сформулировать их сделана в данной статье.
Основным свойством апплетов является возможность выполнять их на различных платформах и в различных окружениях, не оказывая вредного влияния на аппаратуру, программы и данные их пользователей. Язык, ориентированный на программирование апплетов должен прежде всего обеспечивать надежность и безопасность. Этого легче всего достичь в интерпретируемом языке, хотя интерпретация, как правило, ведет к существенной потере эффективности программы (она выполняется гораздо медленнее, чем могла бы), причем эти потери растут (нелинейно!) с ростом объема программы и объема обрабатываемых ею данных. Пока язык используется для разработки сравнительно небольших апплетов (например, апплета, выводящего на экран текущее состояние табло аэропорта, или биржи, или баскетбольного матча), эти потери просто не замечаются. Создается иллюзия, что скоро вообще можно будет решить проблему составления новых программ путем объединения ("сшивания") уже имеющихся в сети апплетов. Если стать на такую точку зрения, то интерпретируемый язык является наиболее естественным: ведь программа, состоящая из вызовов большого числа апплетов, все равно интерпретируется. К сожалению, с ростом объема такой программы она вообще перестанет выполняться ввиду нехватки ресурсов, или будет выполняться неприемлемо долго. Поэтому в окружении Java остро стоит проблема эффективности Java-программ. Авторы языка Java предчувствовали возникновение этой проблемы. Они ввели в язык легковесные процессы (трэды), обеспечив их параллельное выполнение на многопроцессорных компьютерах (которых становится все больше), они сконструировали интерпретатор языка (JavaVM) таким образом, что в Java-программу можно включать фрагменты, написанные на других языках и выполняемые в объектном коде (native code) соответствующей платформы. Но, как будет показано, эти средства языка Java не решают проблемы эффективности в полной мере, так как потери, связанные с интерпретацией намного больше. В данной статье будут рассмотрены основные пути решения проблемы эффективности системы Java.
Как уже было отмечено, Java - объектно-ориентированный язык. Это дало возможность зафиксировать достаточно компактное ядро языка, ограничив его сравнительно небольшим числом различных синтаксических конструкций, а большую часть возможностей языка вводить с помощью классов. Так, трэды включены в язык с помощью классов Thread и ThreadGroup. В виде классов реализованы и такие базовые языковые понятия, как функции обработки строк и обработка исключений. Развитие языка Java тоже ведется путем включения в него новых классов и пакетов (пакеты заменяют в системе программирования Java файлы-заголовки окружения C/C++, однако, в отличие от файлов-заголовков, пакеты содержат как спецификацию классов, так и их реализацию; подробнее о пакетах см. ниже). Такой способ расширения языка удобен тем, что старые компиляторы остаются пригодными и для расширенного языка. Многие новые свойства языка, введенные в него через новые классы и пакеты, будут рассмотрены (или хотя бы упомянуты) ниже. В частности, будут рассмотрены пакеты GJL и SJL, обеспечивающие некоторые удобные для приложений структуры объектов (контейнеры и итераторы) [6,7]. В языке C++ такие возможности поддерживаются широко известной библиотекой классов STL [8], в 1995 году включенной в его стандарт.
В заключение будут кратко рассмотрены и некоторые дополнительные средства окружения Java. Такими средствами являются, например, web'овский браузер HotJava [9] (именно этому браузеру система Java обязана своей эмблемой - чашечкой дымящегося кофе), а также небольшой объектно-ориентированный язык JavaScript [10], предназначенный для разработки CGI-скриптов [2, с. 189 - 199], используя конструкции языка Java. Netscape Navigator 2.0, или HotJava, интерпретирует операторы JavaScript, которые включаются непосредственно в страницу HTML. На языке JavaScript часто программируют текущую дату, различные "бегущие строки" и другие меняющиеся во времени объекты, встречающиеся на страницах HTML.
Авторы языка Java стремились сделать его внешне похожим на широко распространенный среди программистов язык C++. Поэтому синтаксис языка Java во многом совпадает с синтаксисом C и C++. И действительно в Java сохранена лексика и основные синтаксические конструкции C++. Как и в C++ в Java определены унарные и бинарные арифметические, логические и битовые операции, несколько операций присваивания, тренарная условная операция, явные и неявные операции приведения типов (причем все перечисленные операции имеют такие же обозначения и приоритеты, что и соответствующие операции C/C++). Как и в C++ в Java определены управляющие операторы if, if-else, break, switch, return, while, do-while, for, continue (все перечисленные операторы имеют семантику аналогичную семантике соответствующих операторов C/C++). Как и в C++ в Java определены классы, имеющие закрытую (private), ограниченно доступную (protected) и общедоступную (public) части, классы служат шаблонами для порождения объектов, для классов определено отношение наследования (правда в Java отменено множественное наследование), определены конструкторы и деструкторы, указатель this, абстрактные классы. На первый взгляд может показаться, что Java - просто еще одна версия C++.
Но это впечатление неверное. Язык Java существенно отличается от C++, так как при разработке Java из C++, который был взят за основу, были исключены почти все особенности C++, имеющие корни в языке C, из которого развился C++. В результате Java оказался не очередным диалектом C++, а новым языком, который лучше C++ в основном за счет того, чего в нем нет по сравнению с C++. В отличие от C++ Java позволяет писать надежные, безопасные, простые, удобные и легкие для понимания программы, хотя и более медленные, чем программы, написанные на C++.
Что же было исключено из C++ при разработке Java? Прежде всего были исключены указатели. Указатели или адреса в памяти - наиболее мощное средство написания высокоэффективных программ в окружении C/C++, но это и наиболее опасное средство этих языков. И дело даже не в том, что, как отмечают авторы языка Java, при недостаточно аккуратном обращении с указателями могут возникать трудно устранимые ошибки в C++-программе. Более существенные трудности в работе с указателями выявляются при разработке распределенных программ, когда требуется осуществить удаленный вызов функции (метода), среди параметров которой есть указатели. Еще более существенные трудности связаны с возможностью работы с произвольными адресами памяти через бестиповые указатели, так как это позволяет полностью игнорировать защиту памяти.
В языке Java нет указателей; все объекты программы расположены в куче (heap) и доступны по объектным ссылкам, которые представляют объекты во всех структурах, в которые могут входить объекты в качестве компонентов. Поскольку при работе с кучей программист не может пользоваться взаимным расположением объектов в памяти, это решение означает, что из языка Java исключена "кусочно-линейная" модель памяти системы C/C++, при которой массив - лишь точка (ячейка) памяти, на которую ссылается указатель этого массива; конечно, это решение исключило непосредственный доступ к памяти, но оно усложнило работу с элементами массивов и, естественно, является источником более низкой эффективности Java-программ по сравнению с C++-программами. Необходимо отметить, что объектные ссылки языка Java содержат информацию о классе объектов, на которые они ссылаются, так что объектные ссылки - это не указатели, а дескрипторы объектов. Наличие дескрипторов позволяет JavaVM выполнять проверку совместности типов на фазе интерпретации кода, возбуждая исключение в случае ошибки.
В Java пересмотрена и концепция C/C++ динамического распределения памяти. Исключена функция освобождения динамически выделенной памяти free(), так как работа с ней сочтена сложной и чреватой многими ошибками в программе, возможность которых уменьшает надежность C++-программ. Вместо этого в Java разработана и реализована система автоматического освобождения динамически выделенной памяти (сборщик мусора). Наличие механизма автоматической сборки мусора, естественное для интерпретируемого языка, усложняет разработку оптимизирующих компиляторов для такого языка. Тем не менее как показывает практика использования Java нужда в таких компиляторах имеется (см. ниже).
Стремление упростить Java-программы и сделать их более понятными привело к отказу от файлов-заголовков (h-файлов) и препроцессорной обработки. По мнению авторов Java файлы-заголовки, содержащие прототипы классов и распространяемые отдельно от двоичного кода этих классов, усложняют управление версиями, а механизм поддержки этой возможности в C/C++ помогает злонамеренным пользователям получать доступ к приватным данным объектов. В Java-программах спецификация класса и его реализация всегда содержатся в одном и том же файле. Препроцессор C/C++ с его возможностями условной компиляции дает возможность писать непонятные тексты программ, что, конечно, не очень хорошо. Но отказ от препроцессора привел к невозможности параметризации классов по типам (классам) их членов, что усложняет программирование простых вещей (например, на Java, в отличие от C++, нельзя иметь массив, элементами которого являются объекты произвольного класса). И, конечно, из Java исключен ненавидимый многими программистами-педантами оператор goto, замененный на continue и break с меткой.
Кроме того из Java исключены "дублирующие" понятия и конструкции языка C++, в частности, функции (в Java есть только методы классов), а также структуры (struct) и объединения (union) (в Java для этого используются атрибуты классов).
Необходимо также отметить, что в куче размещаются все данные Java-программы. Это означает, что хотя в Java и определены данные простых типов (byte, short, int, long, char, float, double, boolean) переменные этих типов могут быть лишь атрибутами объектов. Отсюда следует, что если нужно завести переменную, например, целого типа (int), то необходимо завести объект класса Int, который имеет один атрибут типа int и два метода - соответственно чтения значения этого атрибута и записи в него нового значения.
Таким образом, несмотря на внешнее сходство с C++, Java не просто является новым языком программирования, он настолько глубоко отличается от C++, что конвертировать разумным образом C++-программы на язык Java (или наоборот) - очень сложная задача.
Завершая краткий обзор языка Java, рассмотрим его конструкции, которых нет в C++. Это операторы package, import, interface и implements.
Оператор package, помещаемый в начале исходного программного файла определяет пакет, т.е. область в пространстве имен классов, в которой определяются имена классов, содержащихся в этом файле; внутри указанной области пространства имен можно выделить подобласти, используя все тот же оператор package; действие оператора package аналогично действию объявления директории на имена файлов. Для обеспечения возможности использования коротких имен классов, помещенных в другие пакеты, используется оператор import. Пакет, определяемый оператором package по существу является структурной частью проектируемой программной системы, в аспекте интерфейса во многом похожей на объект, но имеющей более сложную структуру (он играет роль подсистемы). Если ввести пакеты (подсистемы) в объектно-ориентированные методологии структурного проектирования программных систем (например, в известную методологию OMT [11]), они станут еще более мощным средством поддержки программных проектов.
Оператор interface, позаимствованный разработчиками Java из языка Objective_C (там аналогичное понятие называется протоколом), открывает определение интерфейса. Интерфейс - это набор сигнатур методов без из реализации. Каждый интерфейс может быть реализован одним или несколькими классами, при этом классы, реализующие один и тот же интерфейс, могут быть никак не связаны по иерархии наследования. Класс может реализовывать любое число интерфейсов (список интерфейсов, реализуемых некоторым классом указывается в операторе implements, дополняющим определение соответствующего класса). На множестве интерфейсов тоже определена иерархия по наследованию, но она не имеет отношения к иерархии классов. В языке Java интерфейсы обеспечивают большую часть той функциональности, которая в C++ обычно представляется с помощью механизма множественного наследования.
Системная библиотека классов языка Java содержит классы и пакеты, реализующие различные базовые возможности языка. Методы классов, включенных в эту библиотеку, вызываются из JavaVM во время интерпретации Java-программы. Идея реализовывать часть языковых возможностей с помощью системных библиотек была успешно осуществлена еще в языке C и его преемнике C/C++ (откуда Java и взяла эту идею). Но если в системе программирования C/C++ нужна была известная осторожность при развитии языка с помощью библиотек, так как библиотеки вносили в язык элемент интерпретации, снижая эффективность программного кода, то в системе Java и так вся программа интерпретируется на JavaVM, так что возможности расширять язык, вводя все новые и новые классы и пакеты в системную библиотеку, практически неограничены.
Выше уже было упомянуто, что с помощью библиотечных классов Thread и ThreadGroup в Java введены легковесные процессы (трэды). Для синхронизации трэдов в Java используются мониторы Хоара, с помощью которых осуществляется синхронизация очередей (с приоритетами) к совместно используемым данным. Механизм наследования позволяет вводить в управление трэдами новые возможности: можно разработать "свои трэды", наследуясь от класса Thread.
Системная библиотека Java содержит также классы String и StringBuffer, поддерживающие работу со строками. Эти классы объявлены как final, что означает, что от этих классов нельзя производить подклассы. Класс String содержит основные операции для работы со строками (слияние строк, сравнение строк, поиск и извлечение символов и т.п.). Отметим, что операция (метод) конкатенации (слияния) строк обозначается символом "+", причем это единственный случай в языке Java, когда использована перегрузка знаков операций (в отличие от C++, широко использующем перегрузку операций +, * и др., что приводит к плохо понимаемым текстам программ, в Java запрещена перегрузка знаков операций). Класс StringBuffer является близнецом класса String, но, в отличие от класса String, объекты которого нельзя изменять (новая строка - это новый объект класса String), строки, являющиеся объектами класса StringBuffer, можно изменять.
Как и в системе программирования C/C++, в системную библиотеку Java включен пакет java.io, в котором реализованы потоки ввода-вывода. Возможности ввода-вывода в Java, в основном аналогичны реализации ввода-вывода в системе C/C++.
В системную библиотеку Java входят пакеты java.lang и java.util, которые содержат наборы вспомогательных классов, широко используемых в других встроенных пакетах Java и, естественно в прикладных пакетах и классах, разрабатываемых пользователями окружения Java. В частности, пакет java.lang содержит абстрактный класс Number, представляющий собой интерфейс для работы со всеми скалярными типами, его подклассы Integer, Long, Float и Double, являющиеся классами-оболочками для значений соответствующих типов (каждый объект класса-оболочки содержит одно значение соответствующего типа и имеет методы, обеспечивающие доступ к указанному значению), а также классы-оболочки Character и Boolean. Из других классов этих пакетов можно отметить класс Math, который содержит функции с плавающей точкой, используемые в физических и технических расчетах, а также константы E (приблизительно 2.72) и PI (приблизительно 3.14).
Интеграция системы Java с сетью Internet обеспечивается пакетом java.lang, входящим в системную библиотеку Java. При этом адреса абонентов, принятые в Internet, поддерживаются классом InetAddress, для поддержки дейтаграмм (пакетов протокола UDP) используются классы DatagramPacket и DatagramSocket, класс Socket поддерживает сокеты TCP/IP, для создания серверов Internet используются объекты класса ServerSocket. Наконец, имеется класс URL для связи с WWW (пользователи WWW знают, что URL, или Uniform Resource Locators - наиболее фундаментальный компонент WWW, обеспечивающий доступ к ее содержимому; в списке литературы к данной статье URL использованы для указания источников, доступных по Internet). Сетевые классы Java представляют ясный и простой в использовании интерфейс для работы в Internet, существенно упрощая написание программ для Internet.
В системную библиотеку Java включены также классы, поддерживающие разработку апплетов и упрощающие работу с окнами. Эти классы рассматриваются в следующем разделе.
Библиотека классов Java постоянно расширяется за счет новых классов и пактов. Эти классы фактически расширяют язык Java, предоставляя программистам новые возможности. В качестве примера такого расширения Java рассмотрим два пакета, реализующие для окружения Java знаменитую библиотеку STL (Standard Template Library) [8], которая имеет большой успех у программистов, использующих C++.
Первый из этих пакетов - SJL (Simple Java Library) [7] представляет собой набор контейнерных классов и алгоритмов, параметризованных по типам (классам) содержащихся в них объектов и разработанных таким образом, чтобы программисты моли использовать их в различных сочетаниях в своих программах. Контейнерные классы определяют контейнерные объекты (или просто - контейнеры), т.е. объекты, которые содержат в себе наборы (множества) других объектов, обеспечивая доступ к этим объектам. Примерами контейнеров является список, массив, очередь и т.п. С каждым контейнером обычно бывает связано один или несколько итераторов - объектов, обеспечивающих доступ к содержимому соответствующего контейнера в некотором порядке. SJL обеспечивает возможности библиотеки STL для пользователей системы Java.
К сожалению, в системе Java в отличие от C++ не предусмотрено препроцессора (авторы Java являются принципиальными противниками препроцессора). Поэтому параметрические (generic) классы, реализованные в языке C++ через шаблоны (templates), обрабатываемые препроцессором, и широко используемые в библиотеке STL (ведь это библиотека шаблонов), в SJL реализованы через вызовы методов соответствующих классов. В результате во время выполнения программы приходится выполнять часть функций, которые в STL выполняются в процессе компиляции (дополнительная интерпретация!), что конечно же резко снижает производительность классов библиотеки SJL по сравнению с аналогичными классами STL.
В другой, более популярной, реализации библиотеки
STL для окружения Java - пакете JGL (Java Generic Library) [6],
разработанном под руководством самого автора STL А. Степанова,
показано как в этом окружении можно интерпретировать параметрические
(generic) классы. Для этого авторы JGL разработали наборы интерфейсов,
по одному для каждого из одиннадцати видов контейнеров (Array
,
Deque
,
Dlist
,
Slist
,
HashSet
,
OrderedSet
,
Stack
,
Queue
,
PriorityQueue
,
HashMap
,
OrderedMap
),
и контейнерные классы, реализующие эти наборы интерфейсов. Каждый
интерфейс контейнера соответствует одному из классов объектов,
содержащихся в этом контейнере, так что как только появляется
новый класс объектов, включаемых в контейнер, соответствующий
набор интерфейсов должен быть пополнен еще одним интерфейсом.
Обращение к контейнеру всегда производится через один из его интерфейсов.
Конечно, по сравнению с шаблонами такой способ параметризации
классов по типам содержащихся в них объектов выглядит несколько
неуклюжим. Другим недостатком пакета JGL является возможность
включать в его контейнеры только объекты, так что элементами контейнера
"массив целых значений" будут не поля типа int,
а соответствующие объекты класса Int
из пакета java.lang.
Из описания двух способов реализации библиотеки STL для окружения Java (SJL и JGL) видно, что язык Java нуждается в более удобных средствах параметрического (generic) программирования. Предложения по расширению Java в этом направлении встречаются в публикациях (см., например, [12]). Однако пока эти предложения не встречают должного понимания у авторов Java.
Апплеты это маленькие прикладные программы на языке Java, которые размещаются на серверах Internet, транспортируются клиенту по сети, автоматически устанавливаются и запускаются на месте как часть документа WWW. Апплеты позволяют вставлять в документ, распространяемый по сети, поля, содержание которых меняется во времени (например, текущая дата, текущий курс валюты и т.п.), организовывать в нем "бегущие строки",
мультипликацию. Апплетами могут быть небольшие прикладные программы, распространяемые по Web, такие как калькулятор, преобразователь алгебраических выражений, вычислитель интегралов для первокурсников, различные игры и т.п. Апплеты компилируются в байткод на компьютере их разработчика и распространяются по Web в виде байткодных файлов.
Каждый апплет выбирается на компьютер его пользователя из Web с помощью браузера (авторы системы Java изготовили для этого свой браузер HotJava, однако большинство пользователей предпочитает Netscape). После этого он загружается, верифицируется и запускается на JavaVM на компьютере пользователя, имея доступ только к JavaVM и к памяти, которую он сам занимает (рис. 1). Ни к каким другим ресурсам компьютера пользователя апплет доступа не имеет, что обеспечивает защиту данных и других программ пользователя от порчи и предохраняет компьютер пользователя от заражения вирусами. Защита ресурсов компьютера, на котором выполняются апплеты, загружаемые из сети, является предметом особых забот разработчиков системы Java. Поэтому в системе поддерживается четыре уровня безопасности Java-программ (в частности, апплетов).
Первый уровень реализован в компиляторе на байткод. В отличие от традиционных компиляторов C/C++, в которых память распределяется во время компиляции, в компиляторе Java распределение памяти отложено до времени выполнения. Это, естественно, замедляет интерпретацию Java-программ, но это мешает потенциальным хаккерам взламывать систему, так как доступ к исходному коду Java-программы не дает возможности организовать несанкционированный доступ к ее памяти. Это связано и с тем, что в языке Java вместо указателей используются символические объектные ссылки в кучу, причем доступ к какому-либо объекту всегда ограничивается этим объектом.
Второй уровень защиты осуществляет верификатор байткода, который просматривает загруженный апплет перед его интерпретацией (рис. 1). Верификатор байткода - это несложная программа для доказательства теорем, которая проверяет загруженный байткод на удовлетворение им следующих условий (эти условия могут быть нарушены из-за неполадок в работе сети и при использовании разработчиком апплета недоброкачественного компилятора Java):
Такая верификация байткода стала возможной благодаря выбранной семантической модели языка Java, принятой схеме распределения памяти и сохранению информации о типах (классах) объектов Java-программы на период ее интерпретации.
Третий уровень защиты осуществляется при динамической загрузке классов при выполнении апплета на компьютере пользователя. Во время выполнения апплета он может вызывать различные классы из локальной файловой системы, а также классы с различных серверов сети. Для обеспечения большей безопасности каждый класс, загруженный из сети, выполняется в своем отдельном пространстве имен (пакете). Для классов, загруженных из локальной файловой системы, организуется общее пространство имен. Когда класс ссылается на какой-либо другой класс, загрузчик сначала пытается разрешить ссылку в пределах локального пространства имен, а затем в остальных (внешних) пространствах имен. Поэтому ни один из локальных классов не может "испортить" какой-либо внешний класс в сети. По этой же причине локальные классы не могут ошибочно ссылаться на классы из внешних (импортированных) пространств имен.
Наконец, четвертый уровень защиты связан с такой организацией интерфейсов с сетевыми протоколами (HTTP, FTP и др.), которые обеспечивают доступ не ко всей сети, а только к тем серверам, с которых загружаются классы, используемые интерпретируемым апплетом. Кроме того, имеется возможность организовать барьер, предотвращающий доступ к ресурсам компьютера пользователя апплета со стороны сети.
Отметим, что если третий и четвертый из вышеперечисленных уровней нетрудно организовать и в системах программирования других языков, то организация первых двух уровней возможна только в системе Java, так как эти уровни основаны на свойствах языка и системы программирования Java, которых нет в других языках.
Как показали недавние всесторонние исследования по безопасности Java-апплетов [14, 15], вышеописанных мер безопасности пока оказалось достаточно, чтобы обеспечить безопасное распространение и выполнение апплетов. Во всяком случае пока не известно примеров распространения вирусов через апплеты Java. В [15 и 16] можно также найти сообщения об ошибках, обнаруженных в различных компонентах системы Java.
Однако с вирусами все обстоит не так просто. Недавно компания Symantec объявила о возможности распространения двух типов вирусов с апплетами Java:
В связи с этим разработано расширение системы Norton AntiVirus (NAV) для сканирования классов (апплетов) Java на предмет обнаружения в них вирусов. Это расширение содержит сканер файлов, содержащих Java-классы, который позволит NAV обеспечить антивирусную защиту Java-кода в режиме реального времени, и монитор антивирусной службы для Java, работающий внутри Netscape или любого другого браузера для Web, поддерживаемого системой Java. Текущая версия антивирусной службы NAV сканирует апплеты, рассылаемые по Internet в файлах .CLASS, и может обнаруживать вирусы типа Java Type I, которые могут размножаться, модифицируя страницы HTML. В следующей версии предполагается реализовать новый сканер, который сможет обнаруживать более сложные вирусы, относящиеся к типу Java Type II, которые могут заражать файлы .CLASS.
Следует отметить, что требования к обеспечению безопасности апплетов автоматически выполняются для апплетов, разрабатываемых в инструментальном окружении JDK.
Инструментальное окружение Java Developer's Kit (JDK) предназначено для разработки, отладки и модификации Java-программ (в первую очередь, апплетов). В его состав входит несколько инструментальных пакетов, в частности, пакеты java.applett и java.AWT.
Поскольку большая часть апплетов являются составными частями ("активными полями") документов, распространяемых по сети, при их программировании часто возникает необходимость обмена данными с оконными системами (графический ввод/вывод в окна). В JDK программирование работы с окнами поддерживается инструментальным средством Abstract Window Toolkit (AWT), обеспечивающим возможность писать апплеты, которые можно выполнять как с оконными редакторами ОС UNIX, так и с оконными редакторами других ОС.
Для отладки программных компонент, разрабатываемых в окружении JDK (в частности, апплетов) используется отладчик JavaDebugger (JDB). Этот отладчик поддерживает простой и удобный интерфейс с пользователем JavaDebuggerAPI, его можно использовать для отладки удаленных программ совместно с браузером HotJava, а также для отладки программ, написанных на подмножестве Java для разработки скриптов - языке JavaScript.
Для работы в операционных системах Windows 95, Windows NT, UNIX (в том числе Solaris и Linux) и Macintosh текущую версию JDK (в настоящее время это версия 2.0 от 18 февраля 1997 г.) можно загрузить непосредственно с Web-сервера компании Sun по адресу java.sun.com . По этому же адресу доступен и браузер HotJava, написанный на Java, и комплект документации по системе Java. Размер дистрибутива JDK (в упакованном виде) составляет 4 - 5 мегабайт. Советы по установке JDK см., например в [5] (приложение А). Инструментальное окружение JDK содержит библиотечные классы и пакеты, существенно упрощающие программирование и отладку апплетов.
Здесь под эффективностью имеется в виду скорость выполнения Java-программ. Поскольку Java-программы интерпретируются на JavaVM, они, естественно, выполняются медленнее, чем программы, разработанные с помощью оптимизирующих компиляторов C/C++. Измерения на бенч-марках показали, что Java-программы выполняются примерно в 30 раз медленнее C-программ на однопроцессорных компьютерах (в частности, на персональных компьютерах) [17]. На параллельных компьютерных системах разрыв достигает 50 раз. Поэтому, конечно, существует проблема повышения эффективности Java-программ. В меньшей степени эта проблема актуальна для апплетов, ведь это небольшие интерактивные программы, и скорость их выполнения в большинстве случаев удовлетворяет их пользователей.
В тех редких случаях, когда возникает потребность увеличить скорость выполнения апплета, можно воспользоваться динамическим компилятором Java (just-in-time compiler), который работает параллельно с интерпретацией апплета (см. рис. 1) и переводит байткод в объектный код компьютера, на котором выполняется апплет. Этот объектный код оформляется как обычная подпрограмма и (после загрузки) вызывается из JavaVM (при программировании на Java разрешается использовать подпрограммы, выполняемые в объектном коде компьютера, на котором работает JavaVM (native subroutines); эти подпрограммы считаются подпрограммами самой JavaVM). Как показали измерения на бенч-марках, при этом выполнение апплета ускоряется примерно в 10 раз.
Но в окружении Java разрабатываются не только апплеты. Java - универсальный язык программирования, и его удобно использовать для написания клиентских программ, библиотек, поддерживающих пользовательские интерфейсы (особенно в тех случаях, когда они должны функционировать в неоднородной сети), прикладных пакетов и т.п. Всеобщее увлечение апплетами несколько отодвинуло на второй план эту сторону Java, но это - временное явление. Уже сейчас публикуется много работ по применениям Java, не связанным с разработкой апплетов, и число таких работ будет увеличиваться хотя бы потому, что проблема программирования и распространения апплетов во многом исчерпала себя (это должно быть видно и из данной публикации). При использовании Java для разработки более сложного программного обеспечения, чем апплеты, проблемы надежности и эффективности (производительности) Java-программ, а также проблемы, связанные с необходимостью защиты глобальной сети от вирусов, должны быть поставлены по новому.
Рассмотрим проблему производительности сравнительно крупных прикладных Java-программ. Если для большинства апплетов проблемы производительности вообще не стояло, так как для пользователей они выполнялись "мгновенно", то замедление произвольных прикладных программ почти на два порядка неизбежно приведет к отказу от их использования. Разработчики системы Java и Java-приложений хорошо понимали это с самого начала, но, как свидетельствуют их ранние публикации (см., например, [1]), они надеялись, что JavaVM настолько хороший интерпретатор, что замедление от интерпретации будет несущественным. Кроме того, они рекомендовали повышать быстродействие Java-программ путем использования высокоэффективных библиотечных подпрограмм, разработанных на C/C++ (native methods). К сожалению, как показали измерения (см. выше), их надежды оправдались лишь для небольших апплетов, которые, по существу, являются CGI-скриптами и для разработки которых достаточно примитивного подмножества Java - языка JavaScript [10]. Когда это было осознано, стали предприниматься усилия по повышению быстродействия Java-программ.
Первым шагом в этом направлении была аппаратная разработка JavaVM, предпринятая компанией Sun, но Java-процессор не оправдал возлагавшихся на него ожиданий по резкому увеличению быстродействия Java-программ. Кроме того, если для нормального выполнения Java-программ требуется специальный процессор, то это существенно меняет статус языка Java, как универсального языка для Web, так как очевидно, что далеко не во всех узлах Web имеются Java-процессоры.
Поэтому были предприняты попытки увеличить быстродействие Java-программ с помощью оптимизирующих компиляторов, переводящих Java-программу в оптимизированный объектный код компьютера, на котором она будет выполняться. Компания JavaSoft (дочерняя компания фирмы Sun по производству программного обеспечения, связанного с Java) разработала первый динамический оптимизирующий компилятор Java (just-in-time-compiler) [13]. Этот компилятор вызывается по прагме интерпретатором Java-байткода (JavaVM) и выполняется параллельно JavaVM (на однопроцессорном компьютере - в режиме разделения времени), компилируя из переданного ему байткода оптимизированный объектный код. Параллельно с компиляцией байткода JavaVM продолжает интерпретировать его. Когда компиляция завершается, динамический загрузчик загружает полученный объектный код как подпрограмму интерпретируемого байткода, а JavaVM вызывает эту подпрограмму, как она вызвала бы любую другую объектную (native) подпрограмму интерпретируемой Java-программы, и тем самым фактически прекращает интерпретацию байткода (схематически это показано на рис. 1). Динамические компиляторы Java реализованы и другими компаниями, занимающимися разработкой программного обеспечения Java. Но, как уже упоминалось, применение динамического компилятора повышает быстродействие Java-программы лишь на порядок, так что проигрыш остается весьма ощутимым.
Поэтому был предпринят еще один шаг: реализованы первые оптимизирующие компиляторы Java в объектный код различных компьютеров [18, 19]. При этом даже не всегда используется байткод в качестве внутреннего представления исходной Java-программы. Более того, уже предпринимаются первые попытки разработки компиляторов Java для мощных многопроцессорных компьютерных систем. Реализация таких компиляторов сделает Java вполне универсальным мощным языком программирования. Правда это поставит перед разработчиками и пользователями Java новые сложные проблемы.
Рефлексией называется возможность менять свойства окружения, в котором выполняется программа, и за счет этого изменить поведение самой программы. Многие объектно-ориентированные языки и их системы программирования (например, CLOS, Smalltalk и др.) являются рефлексивными, т.е. содержат языковые и системные средства, с помощью которых можно менять параметры окружения. Рефлексия очень удобна при разработке программных систем, так как сообщает им дополнительную гибкость, способность настраиваться на различные окружения, возможность взаимодействовать с программами, которые выполняются в других системах программирования.
Java с самого начала разрабатывался как рефлексивный язык. В Java определен класс Object, являющийся корнем иерархии классов по наследованию: любой класс Java наследует класс Object. В классе Object определен доступ к системной информации, которую можно использовать Java-программах. Наличие класса Object уже сообщает определенные рефлексивные свойства системе Java.
Дополнительные возможности по реализации рефлексивных Java-программ обеспечиваются пакетом JRI (Java Reflection Interface), в котором определены способы определения рефлексивной информации для классов и методов системы Java. Рефлексивная информация, связанная с некоторым классом, включает в себя описание его привилегий доступа к различным классам, их полям и методам, их суперклассам, к интерфейсам, присоединенным к этим классам, а также информацию о свойствах его полей и методов.
Проблема разработки рефлексивных возможностей для объектно-ориентированных языков программирования связана с проблемой реализации возможности взаимодействия различных объектно-ориентированных окружений, т.е. с проблемой разработки ORB (Object Request Broker) [21]. Сейчас уже разработано несколько коммерческих ORB для Java [22]. Изучение этих ORB показывает, что язык Java гораздо лучше, чем, например C++, приспособлен к рефлексивному программированию и реализации ORB. Развитие рефлексивного программирования на Java будет способствовать еще более широкому распространению системы Java. Вместе с тем Java в ее текущем состоянии имеет ряд недостатков, препятствующих ее использованию для реализации распределенных прикладных систем. В [21] отмечены следующие недостатки Java в этом плане:
Отсутствие языковой поддержки управления транзакциями и их журнализацией.
Перечисленные недостатки мешают применению Java для
разработки серьезных распределенных систем в рамках стандарта
CORBA. В настоящее время ведутся интенсивные исследования по преодолению
указанных недостатков.
В данной публикации была сделана попытка ознакомить широкие круги программистов с основными свойствами и особенностями системы программирования Java. Java - первая система программирования, обеспечивающая возможность разработки и выполнения программ в среде WWW. Было показано, что программы, разработанные в системе Java, обладают такими свойствами как переносимость, высокая надежность, адаптируемость к новым условиям их выполнения. Как отмечено в [21], Java позволяет уже сейчас разрабатывать высоконадежные коммерческие прикладные системы. Эти свойства привлекают к системе Java многих программистов, и в настоящее время, несомненно, Java - самая популярная система программирования. Однако система Java сейчас находится в самом начале своего развития. Еще нет даже первых публикаций, связанных со стандартизацией Java, еще много неясных вопросов, связанных с надежностью, безопасностью, эффективностью, обеспечением быстродействия Java-программ. Поэтому можно ожидать, что в самое ближайшее время, будут получены и опубликованы новые результаты как по развитию и стандартизации языка и системы программирования Java, так и по новым технологиям и подходам к применению Java в самых различных областях системного и прикладного программирования.
www.unidata.ucar.edu/staff/russ/java/jgl-bjug