автордың кітабын онлайн тегін оқу Программируем на Java
Переводчики В. Здобнов, Е. Матвеев
Марк Лой, Патрик Нимайер, Дэн Лук
Программируем на Java. 5-е межд. изд.. — СПб.: Питер, 2022.
ISBN 978-5-4461-1836-6
© ООО Издательство "Питер", 2022
Все права защищены. Никакая часть данной книги не может быть воспроизведена в какой бы то ни было форме без письменного разрешения владельцев авторских прав.
Предисловие
Эта книга научит вас программировать на языке Java и использовать среду разработки приложений. Если вы разработчик или опытный интернет-пользователь, то наверняка слышали об этом языке. Его появление стало одним из ярчайших событий в истории интернета, а бизнес в интернете вырос до сегодняшнего уровня во многом благодаря Java-приложениям. Вероятно, Java является самым популярным в мире языком программирования. Миллионы разработчиков пишут Java-приложения почти для всех видов компьютеров, которые только можно представить. В отношении спроса на программистов Java превосходит такие языки, как C++ и Visual Basic. Он стал фактическим стандартом для разработки некоторых видов программного обеспечения, особенно веб-сервисов. Многие вузы включают Java в начальные курсы программирования наряду с другими актуальными современными языками. Возможно, вы прямо сейчас читаете эти слова на своих учебных занятиях!
Книга даст вам хорошее представление об основах языка Java, в том числе об интерфейсах программирования приложений (API), библиотеках классов, приемах программирования и идиомах. Мы подробно рассмотрим многие интересные области, а некоторые темы затронем лишь в общих чертах. Другие книги издательства O’Reilly продолжают с того уровня знаний, на котором мы остановимся, и предоставляют более полную информацию о конкретных областях и сферах применения Java.
В подходящих случаях мы будем показывать наглядные, реалистичные и интересные примеры кода, избегая монотонного перечисления возможностей. Эти примеры очень просты, но они подскажут вам, что вы сможете делать с помощью Java самостоятельно. Мы не хотим заниматься на этих страницах разработкой очередного «приложения-бестселлера», а вместо этого постараемся дать вам отправную точку для долгих экспериментов и вдохновить на ваш собственный проект такого масштаба.
Кому пригодится эта книга
Эта книга написана для профессионалов в области информационных технологий, для студентов, технических специалистов и «финских хакеров». Она будет полезна всем, кому нужен практический опыт работы с Java, особенно с целью создания реальных приложений. Кроме того, книгу можно использовать в качестве экспресс-курса по объектно-ориентированному программированию, сетевым приложениям и пользовательским интерфейсам.
В процессе изучения Java вы освоите эффективный и практичный подход к разработке программного обеспечения, началом которого станет глубокое понимание основ языка Java и его API.
На первый взгляд Java имеет много общего с языками C и C++, и если у вас есть опыт программирования на одном из них, то вам будет проще изучить Java. Но если у вас нет такого опыта, не огорчайтесь. Не надо уделять излишнее внимание синтаксическому сходству между Java и C или C++. Во многих отношениях Java ближе к более динамическим языкам, таким как Smalltalk и Lisp. Хорошо, если вы уже знаете другие объектно-ориентированные языки, но в этом случае вам придется, скорее всего, пересмотреть некоторые представления и изменить некоторые привычки. Считается, что язык Java намного проще таких языков, как C++ и Smalltalk. Если вы хорошо учитесь на коротких примерах и на собственном опыте, то эта книга должна вам понравиться.
В конце книги мы будем рассматривать Java в контексте веб-приложений, веб-служб и обработки запросов, поэтому вы должны хотя бы в общих чертах знать, как устроены браузеры, серверы и документы.
Последние изменения
Это, пятое, издание книги «Программируем на Java» («Learning Java») можно также считать седьмым, обновленным и переименованным изданием нашей предыдущей популярной книги «Exploring Java». В каждом очередном издании мы старались не только добавлять материал о новых возможностях языка, но и тщательно пересматривать и обновлять весь существующий материал, чтобы систематизировать его и отражать на страницах наш многолетний опыт исследований и практического программирования.
Одно из заметных изменений в последних изданиях книги — сокращение (а затем и удаление) материала о работе с апплетами, которые теперь уже практически не используются при создании интерактивных веб-страниц. С другой стороны, значительно расширены темы веб-приложений и веб-служб Java, ставших вполне зрелыми технологиями.
Мы рассматриваем все важные особенности последней (на момент написания книги) из тех версий Java, которые сопровождаются долгосрочной поддержкой от Oracle. Это Java 11, а ее полное название — Java Standard Edition (SE) 11. (Бесплатный аналог — OpenJDK 11.) Кроме того, мы упоминаем некоторые особенности трех промежуточных версий: Java 12, Java 13 и Java 14. В компании Sun Microsystems, которая была «хранителем» Java до Oracle, за много лет несколько раз меняли схему нумерации версий. Чтобы подчеркнуть множество ценных возможностей, появившихся в версии Java 1.2, ее обозначили термином «Java 2», а также отказались от термина JDK в пользу SDK. Шестая по порядку версия (следующая после Java 1.4) получила название Java 5.0, и тогда же Sun вернула термин JDK. Только после этого продолжилась обычная нумерация: вышли версии Java 6, Java 7 и т.д.
Сейчас перед нами Java 14. Эта версия представляет собой хорошо развитый язык, в котором появился ряд изменений синтаксиса и обновлений API и библиотек. Мы постарались отразить эти новые возможности в примерах кода, чтобы показать современные приемы и стиль программирования на Java.
Что нового в этом издании книги (Java 11, 12, 13, 14)
Это издание мы по традиции переработали таким образом, чтобы сделать его как можно более полным и актуальным. Мы учли изменения, появившиеся в Java 11 (напомним: это версия с долгосрочной поддержкой), а также в промежуточных версиях: Java 12, 13 и 14. (Подробнее о средствах Java, включенных в последние версии и исключенных из них, рассказано в главе 13.) Мы добавили в это издание следующие темы:
• Новые возможности языка, в том числе автоматическое определение (выведение) типов в обобщениях, усовершенствованная обработка исключений и синтаксис автоматического управления ресурсами.
• Интерактивная среда jshell для экспериментов с фрагментами кода.
• Выражения switch.
• Лямбда-выражения.
• Обновленные примеры и объяснения по всей книге.
Структура книги
Структура книги выглядит примерно так:
• Главы 1 и 2 содержат введение в концепцию языка Java, а также простейшее руководство, которое поможет вам немедленно приступить к программированию.
• В главе 3 рассматриваются важнейшие инструменты для разработки программ на Java (компилятор, интерпретатор, jshell и упаковщик JAR).
• В главах 4 и 5 представлены фундаментальные концепции программирования, после чего описывается сам язык Java. Изложение начинается с базового синтаксиса, а затем переходит к классам и объектам, исключениям, массивам, перечислениям, аннотациям и другим темам.
• В главе 6 рассматриваются исключения, способы обработки ошибок и средства журналирования (логирования).
• В главе 7 рассматриваются коллекции, обобщения и отношения между параметризованными типами Java.
• Глава 8 посвящена обработке текста, математическим вычислениям и некоторым другим средствам базового API.
• В главе 9 рассматриваются средства для создания многопоточных приложений.
• В главе 10 представлены основы разработки графических интерфейсов (GUI) с помощью пакета Swing.
• Глава 11 посвящена вводу-выводу Java, потокам данных, файлам, сокетам, сетям и пакету NIO.
• В главе 12 рассматриваются веб-приложения, сервлеты, WAR-файлы и веб-службы.
• Глава 13 рассказывает о процессе развития Java. Она поможет вам отслеживать будущие изменения в языке и модернизировать существующий код, используя новые возможности (например, лямбда-выражения, впервые представленные в Java 8).
Если вы похожи на нас, то вы не читаете книги с начала до конца. А если вы очень похожи на нас, то наверняка не станете читать это предисловие. Но вдруг вы все-таки с него начнете? На этот случай мы дадим несколько рекомендаций:
• Если вы программист и хотите за пять минут понять всю суть Java, то вас, скорее всего, заинтересуют примеры кода. Для начала просмотрите главу 2. Если она не вызовет энтузиазма, перейдите к главе 3 — там рассказано, как использовать компилятор и интерпретатор. Это станет хорошим первым шагом.
• Если вы собираетесь писать приложения для работы в локальной сети или в интернете, обратитесь к главам 11 и 12. Сетевые функции — это одна из самых интересных и важных частей Java.
• В главе 10 рассматриваются графические средства и компоненты Java. Это важно, если вы собираетесь писать обычные десктопные приложения с графическим интерфейсом (то есть приложения для настольных компьютеров).
• Глава 13 рассказывает о том, как всегда быть в курсе происходящих в языке Java изменений, независимо от того, что именно вас интересует.
Интернет-ресурсы
В интернете есть множество источников с информацией о языке Java. Прежде всего, достоверную информацию вы найдете на официальном сайте Oracle (https://www.oracle.com/java/technologies). В частности, Oracle публикует документацию с описаниями классов, методов, операторов и других синтаксических конструкций языка, а также дистрибутивы выпусков Java. Именно с сайта Oracle вам лучше всего загрузить эталонную реализацию JDK, которая включает в себя компилятор, интерпретатор и другие инструменты. Oracle также поддерживает официальный сайт проекта OpenJDK (https://openjdk.java.net) — так называется основная версия Java с открытым исходным кодом, в состав которой также входят компилятор, интерпретатор и другие инструменты. Мы будем использовать OpenJDK для всех примеров кода в этой книге.
Условные обозначения
В книге используются следующие шрифтовые обозначения:
Исходный код
Этим шрифтом выделены примеры исходного кода, а также приглашения командной строки и результаты текстового вывода на экран.
Комментарии в исходном коде
Этим шрифтом в исходном коде выделены комментарии.
Команды и заменяемые элементы
Этим шрифтом выделены команды, которые вводятся в командной строке, а также те элементы в исходном коде, которые должны быть заменены читателем.
Служебные слова и символы
Этим шрифтом в основном тексте выделены слова и символы, которые обозначают классы, методы, команды, файлы, пути, форматы, параметры, значения, теги и другие компьютерные сущности.
Термины
Этим шрифтом в основном тексте выделены термины, когда они вводятся впервые, а также важные понятия и названия.
Веб-ссылки
Этим шрифтом в основном тексте выделены адреса веб-сайтов с полезной информацией.
Использование исходного кода примеров
Если у вас возникнут вопросы технического характера по использованию примеров кода, направляйте их по электронной почте на адрес bookquestions@oreilly.com.
В общем случае все примеры кода из книги вы можете использовать в своих программах и в документации. Вам не нужно обращаться в издательство за разрешением, если вы не собираетесь воспроизводить существенные части программного кода. Если вы разрабатываете программу и используете в ней несколько фрагментов кода из книги, вам не нужно обращаться за разрешением. Но для продажи или распространения примеров из книги вам потребуется разрешение от издательства O’Reilly. Вы можете отвечать на вопросы, цитируя данную книгу или примеры из нее, но для включения существенных объемов программного кода из книги в документацию вашего продукта потребуется разрешение.
Мы рекомендуем, но не требуем добавлять ссылку на первоисточник при цитировании. Под ссылкой на первоисточник мы подразумеваем указание авторов, издательства и ISBN.
За получением разрешения на использование значительных объемов программного кода из книги обращайтесь по адресу permissions@oreilly.com.
Благодарности
Многие люди внесли свой вклад в работу над книгой — как в ее первоначальном варианте «Exploring Java», так и в этом издании. Прежде всего, мы благодарим Тима О’Рейли за то, что он предоставил нам возможность написать эту книгу. Спасибо Майку Лукидесу (Mike Loukides), редактору всей серии; его терпение и опыт постоянно направляют нас на истинный путь. Другие сотрудники O’Reilly, в том числе Амелия Блевинс (Amelia Blevins), Зен Маккуэйд (Zan McQuade), Корбин Коллинз (Corbin Collins) и Джессика Хаберман (Jessica Haberman), непрестанно делились с нами своим опытом и вдохновением. Это предел наших мечтаний — работать со столь квалифицированной и доброжелательной командой.
Исходная версия глоссария позаимствована из книги Дэвида Фленагана (David Flanagan) «Java in a Nutshell», вышедшей в издательстве O’Reilly. Также из книги Дэвида взяты некоторые диаграммы об иерархии классов. Эти диаграммы построены на основе похожих диаграмм Чарльза Л. Перкинса (Charles L. Perkins).
Мы искренне благодарны Рону Бекеру (Ron Becker) за дельные советы и интересные идеи с точки зрения дилетанта, далекого от мира программирования. Благодарим Джеймса Эллиота (James Elliott) и Дэна Лука (Dan Leuck) за их превосходные и актуальные отзывы о техническом содержании этого издания. Как это часто бывает в мире программирования, взгляд со стороны бесценен. Нам очень повезло, что рядом с нами оказались такие внимательные люди.
От издательства
Ваши замечания, предложения, вопросы отправляйте по адресу comp@piter.com (издательство «Питер», компьютерная редакция).
Мы будем рады узнать ваше мнение!
На веб-сайте издательства www.piter.com вы найдете подробную информацию о наших книгах.
Глава 1. Современный язык
Сегодня самые сложные проекты и самые грандиозные возможности для разработчиков связаны с освоением потенциала сетей. Приложения (программы), создаваемые в наши дни, независимо от их масштаба и предполагаемой аудитории, почти наверняка будут работать на устройствах, подключенных к интернету. Сети становятся все более необходимыми, и соответственно меняются требования к привычному программному обеспечению и возникает спрос на совершенно новые виды приложений, список которых стремительно разрастается.
Всем нам нужны такие программные продукты, которые одинаково работают на всех платформах и хорошо совместимы с другими приложениями. Нам нужны динамические приложения, использующие всю мощь глобальной сети и взаимодействующие с разнородными распределенными источниками информации. Нам нужно по-настоящему распределенное ПО, которое легко расширяется и обновляется. Нам нужны «умные» приложения, способные самостоятельно анализировать интернет, выискивать нужную информацию и выполнять функции электронных агентов. Потребность в таких приложениях возникла давно, но они начали появляться только в последние годы.
В прошлом главная проблема была связана с недостаточными возможностями инструментария для создания таких приложений. Требования к скорости и портируемости были по большей части взаимоисключающими, а о безопасности часто забывали или понимали ее неправильно. Полностью портируемые языки были громоздкими, интерпретируемыми и медленными. Но они все равно пользовались популярностью: как из-за своей высокоуровневой функциональности, так и из-за портируемости. В быстрых языках скорость достигалась путем привязки к конкретным платформам, поэтому о полноценной портируемости говорить не приходилось. Существовали и безопасные языки, но они в основном были ответвлениями портируемых языков и имели те же недостатки. В отличие от них, Java — современный язык, в котором сочетаются все три качества: портируемость, скорость и безопасность. Именно поэтому Java занимает доминирующее положение в мире программирования более чем через 20 лет после своего появления.
Появление Java
Язык Java, разработанный в Sun Microsystems под руководством асов сетевых технологий Джеймса Гослинга (James Gosling) и Билла Джоя (Bill Joy), проектировался как машинно-независимый язык программирования, достаточно безопасный для работы в сети и при этом достаточно быстрый для замены низкоуровневого машинного кода. Java решил все названные выше проблемы и сыграл важную роль в развитии интернета, что привело нас к нынешнему положению вещей.
На первых порах энтузиазм по поводу Java был основан прежде всего на возможности создания встраиваемых в веб-страницы приложений, называемых апплетами (applets). Но в те времена возможности апплетов и клиентских приложений с графическим интерфейсом, написанных на Java, были невелики. Зато теперь в Java есть Swing — полноценный инструментарий для создания графических интерфейсов. В результате язык Java стал хорошей платформой для разработки традиционных клиентских приложений, хотя в этой области у него появилось немало конкурентов.
Но что еще важнее, язык Java стал ведущей платформой для приложений на базе веб-технологий и веб-служб. Эти приложения используют разные технологии, включая API сервлетов Java, веб-службы Java и многие популярные фреймворки и серверы приложений Java — как коммерческие, так и с открытым кодом. Благодаря своей портируемости и скорости, язык Java завоевал репутацию оптимальной платформы для разработки современных бизнес-приложений. Серверы Java, работающие на платформах Linux с открытым кодом, заняли центральное место в деловом и финансовом мире.
Эта книга покажет вам, как использовать Java для решения реальных задач программирования. В последующих главах рассматриваются многие возможности: от обработки текста до сетевых коммуникаций, создания десктопных приложений на базе Swing и облегченных приложений на базе веб-технологий и служб.
Происхождение Java
Фундамент Java заложил в 1990-е годы Билл Джой (Bill Joy) — патриарх и ведущий исследователь компании Sun Microsystems. В то время она конкурировала на относительно небольшом рынке рабочих станций, а компания Microsoft начала доминировать на более массовом рынке персональных компьютеров с процессорами Intel. Когда стало ясно, что Sun опоздала на поезд революции персональных компьютеров, Джой переехал в Аспен (штат Колорадо) для проведения перспективных исследований. Ему нравилась идея решения сложных задач простыми программными средствами, и он основал компанию с подходящим названием Sun Aspen Smallworks.
В небольшую команду программистов, собравшихся в Аспене, одним из первых пришел Джеймс Гослинг (James Gosling), которого называют отцом Java. В начале 1980-х он прославился как автор Gosling Emacs — новой версии текстового редактора Emacs, которую он написал на языке C для операционной системы Unix. Gosling Emacs стал популярным, но вскоре его затмила бесплатная версия под названием GNU Emacs, написанная разработчиком исходной версии Emacs. К тому времени Гослинг переключился на разработку оконной системы Sun NeWS, которая в 1987 году конкурировала с X Window System за позицию графической оболочки Unix. И хотя некоторые считают, что система NeWS превосходила X, она все-таки проиграла, потому что компания Sun сделала ее проприетарной и не стала публиковать исходный код, тогда как первые разработчики X сформировали «Консорциум X» и пошли по противоположному пути.
В ходе работы над NeWS Гослинг осознал всю мощь интеграции выразительного языка с оконным графическим интерфейсом, поддерживающим работу в сети. И в компании Sun поняли, что сообщество интернет-программистов категорически не хочет принимать проприетарные стандарты, даже самые замечательные. Неудача NeWS заложила основу схемы лицензирования Java и открытого кода (хотя до настоящего «open source» дело не дошло). Гослинг принес полученные знания в новый проект Билла Джоя. В 1992 году Sun основала для этого проекта дочернюю компанию FirstPerson. Она должна была вывести Sun на рынок бытовой электроники.
Команда FirstPerson создавала программное обеспечение для таких устройств, как сотовые телефоны и карманные компьютеры (PDA).
Работающие в этих устройствах приложения должны были в реальном времени передавать данные через дешевые инфракрасные интерфейсы и традиционные пакетные сети. Из-за недостатка оперативной памяти и малой пропускной способности каналов приходилось писать компактный и эффективный код. И конечно, приложения такого рода надо было делать безопасными и надежными. Вначале Гослинг с коллегами программировали на C++, но вскоре обнаружили, что для их задач этот язык слишком сложен, неповоротлив и уязвим. Тогда они решили начать с нуля, и Гослинг стал работать над тем, что он называл «C++ минус минус».
Провал Apple Newton (первого карманного компьютера Apple) показал, что время PDA еще не настало. Поэтому в Sun переключили усилия компании FirstPerson на интерактивное телевидение (ITV). Для программирования ТВ-приставок разработали язык Oak — один из ближайших предков Java. Но при всей своей элегантности и возможности безопасного обмена данными язык Oak не смог спасти бесперспективную идею интерактивного телевидения. Оно не интересовало покупателей, и вскоре в Sun отказались от этой концепции.
Тогда Джой и Гослинг объединили усилия в поиске актуальной стратегии применения своего новаторского языка. Дело было в 1993 году, и взрывной рост интереса ко Всемирной паутине открывал новые возможности. Язык Oak был компактным, безопасным, архитектурно-независимым и объектно-ориентированным. Так получилось, что все эти свойства входили в набор требований к универсальному языку программирования для интернета. Компания Sun быстро сменила приоритеты, и после небольшой переработки Oak превратился в Java.
Развитие
Не будет преувеличением сказать, что язык Java (и его вариант, ориентированный на разработчиков, — Java Development Kit, или JDK) распространялся стремительно, как степной пожар. Еще до первого официального выпуска, когда Java не был полноценным программным продуктом, за него ухватились лидеры ИТ-бизнеса. Лицензии на Java получили Microsoft, Intel, IBM и почти все остальные крупные производители компьютеров и программного обеспечения. Тем не менее даже при такой поддержке новый язык испытал в первые годы немало трудностей роста.
Начались нарушения контрактов и антимонопольные судебные процессы между Sun и Microsoft, вызванные использованием Java в браузере Internet Explorer. Это затормозило применение нового языка в Windows — самой популярной в мире десктопной операционной системе. Участие Microsoft в работе над Java также стало предметом крупного федерального иска о недобросовестной конкуренции; свидетели дали в суде показания, что гигантская корпорация намеренно стремилась подорвать Java, внедряя несовместимости в свою версию языка. Тем временем Microsoft представила в рамках инициативы .NET свой собственный язык C#, производный от Java, и передумала включать Java в Windows. C# оказался очень хорошим языком, и за последние годы в нем появилось даже больше инноваций, чем в Java.
Но Java и по сей день продолжает распространяться на многих платформах. С первого взгляда на архитектуру Java становится ясно, что ее самые замечательные возможности основаны на изолированной среде виртуальной машины, в которой выполняются Java-приложения. Язык Java предусмотрительно проектировали таким образом, чтобы можно было реализовать архитектуру его поддержки разными способами: как на программном уровне (в операционных системах), так и на аппаратном уровне (в специальном оборудовании). Аппаратные реализации Java используются в некоторых смарт-картах и в других встроенных системах. Вы можете найти интерпретаторы Java даже в таких «носимых устройствах», как электронные кольца и опознавательные жетоны. А программные реализации Java созданы для всех современных компьютерных платформ, в том числе мобильных. Один из вариантов Java стал компонентом операционной системы Google Android, на которой работают миллиарды смартфонов и других мобильных устройств.
В 2010 году корпорация Oracle купила Sun Microsystems и стала куратором языка Java. Начало оказалось неудачным: Oracle подала судебный иск против Google из-за интеграции Java в Android — и проиграла. В 2011 году Oracle выпустила Java 7 — важную версию, дополненную новым пакетом ввода-вывода. В 2017 году в состав Java 9 вошли модули для решения давно существующих проблем со списком путей classpath и с растущим размером JDK. После выпуска Java 9 начался быстрый процесс обновлений, в результате которого появилась актуальная на момент написания книги версия с долгосрочной поддержкой: Java 11. (Об этих и других версиях подробнее рассказано в разделе «История Java», с. 47.) Корпорация Oracle продолжает руководить разработкой языка, но она поделила мир Java надвое: перевела основную линейку версий Java на дорогостоящую коммерческую лицензию, но сохранила и бесплатный вариант под названием OpenJDK. Он по-прежнему общедоступен, его любят и на него ориентируются многие разработчики.
Виртуальная машина
Язык Java одновременно является компилируемым и интерпретируемым. Сначала компилятор преобразует написанный программистом исходный код в простые двоичные команды, очень похожие на машинный код обычных микропроцессоров. Отличие в том, что исходный код C и C++ компилируется в машинный код для конкретной модели процессора, а исходный код Java компилируется в универсальный формат — байт-код, представляющий собой команды для виртуальной машины (VM).
Скомпилированный байт-код выполняется интерпретатором исполнительной системы Java, который делает все то же самое, что обычный аппаратный процессор, но в безопасной виртуальной среде. Он выполняет набор команд на базе стека и управляет памятью подобно операционной системе. Он создает примитивные типы данных, управляет ими, загружает и активирует новые блоки кода по ссылкам. Важнее всего, что все это делается в соответствии со строго определенной общедоступной спецификацией, по которой любой разработчик может создать Java-совместимую виртуальную машину. Сочетание виртуальной машины и определения языка образует полную спецификацию. В базовом языке Java нет никаких деталей, которые остались бы неопределенными или зависели бы от конкретной реализации. Например, Java строго регламентирует размеры и математические свойства всех своих примитивных типов данных, не оставляя их на усмотрение разработчиков версий для разных платформ.
Интерпретатор Java относительно легок и компактен; его можно реализовать в любой форме, подходящей для конкретной платформы. Интерпретатор может быть отдельным приложением или встроенным компонентом другого приложения (например, веб-браузера). Таким образом, код Java является портируемым по своей природе. Один и тот же байт-код приложения будет правильно работать на любой платформе, в которой есть исполнительная среда Java (рис. 1.1). Вам не придется писать альтернативные версии своего приложения для разных платформ или распространять исходный код среди конечных пользователей.
Рис. 1.1. Исполнительная среда Java
Фундаментальной единицей кода Java является класс. Как и в других объектно-ориентированных языках, классы представляют собой компоненты приложений, содержащие исполняемый код и данные. Скомпилированные классы Java распространяются (публикуются) в универсальном двоичном формате, который содержит байт-код Java и другую информацию классов. Разработчик может заниматься созданием и сопровождением отдельных классов, сохраняя их в файлах и архивах — локально или на сетевом сервере. Java умеет находить и загружать нужные классы динамически, когда они требуются работающему приложению.
Кроме платформенно-зависимой исполнительной системы, в Java входит ряд фундаментальных классов, содержащих архитектурно-зависимые методы. Эти платформенные методы (native methods) образуют своего рода шлюз между виртуальной машиной Java и реальным миром. Они реализуются на языке, компилируемом в платформенный код на платформе размещения, и предоставляют низкоуровневый доступ к ресурсам компьютера: к сети, к оконной системе, к файловой системе и т.д. Однако основная часть кода Java написана на самом языке Java (инициализируемом этими базовыми примитивами), поэтому легко портируется. Сюда входят такие фундаментальные средства Java, как компилятор Java, поддержка сети и библиотек GUI, которые также написаны на Java, поэтому доступны в абсолютно одинаковом виде на всех платформах Java без портирования.
Исторически считалось, что интерпретаторы работают медленно, но Java не является традиционным интерпретируемым языком. Кроме компиляции исходного кода в портируемый байт-код, при разработке языка Java специально предусматривалось, чтобы программные реализации исполнительной системы могли дополнительно оптимизировать свое быстродействие методом компиляции байт-кода в платформенный машинный код «на ходу». Этот механизм называется динамической компиляцией, или JIT-компиляцией (Just In Time). Благодаря JIT-компиляции, код Java может выполняться с такой же скоростью, как платформенный код, без ущерба для мобильности и безопасности.
Этот момент часто создает недоразумения при сравнении быстродействия языков. Существует только одна причина внутреннего снижения производительности, присущая скомпилированному коду Java на стадии выполнения и необходимая для безопасности и эффективности архитектуры виртуальной машины: проверка границ массивов. Все остальное может оптимизироваться в платформенный код точно так же, как в языках со статической компиляцией. Кроме того, язык Java содержит больше структурной информации, чем многие другие языки; эта информация расширяет возможности оптимизации. Также надо помнить, что оптимизация может применяться на стадии выполнения с учетом фактической логики работы и характеристик приложения. Что можно хорошо сделать на стадии компиляции, но нельзя сделать еще лучше на стадии выполнения? Тут все определяется временем.
У традиционной JIT-компиляции есть проблема: оптимизация кода требует времени. Таким образом, JIT-компилятор может выдавать отличные результаты, но за это приходится расплачиваться заметной задержкой при запуске приложения. Обычно это не создает проблем для приложений, долго работающих на стороне сервера, но может стать серьезной проблемой для клиентских программ и приложений, работающих на компактных устройствах со скромными возможностями. Для решения этой проблемы технология компилятора Java, называемая HotSpot, использует прием адаптивной компиляции. Если проанализировать фактическое распределение времени выполнения типичной программы, то станет ясно: почти все время тратится на то, чтобы относительно малые части кода выполнялись снова и снова. Многократно выполняемые блоки кода могут составлять малую часть от размера программы, но такое поведение определяет ее общее быстродействие. Адаптивная компиляция также позволяет исполнительной среде Java применять новые виды оптимизации, попросту невозможные в языках со статической компиляцией; отсюда и утверждение, что код Java в некоторых случаях может выполняться быстрее кода C или C++.
Чтобы воспользоваться этим фактом, HotSpot вначале работает как обычный интерпретатор байт-кода Java, но с небольшим отличием: он измеряет время выполнения кода («профилирует» код), чтобы определить, какие его части выполняются многократно. Определив, какие части кода критичны для быстродействия, HotSpot компилирует эти секции в оптимальный платформенный машинный код. Поскольку в машинный код компилируется лишь небольшая часть программы, затраты времени на оптимизацию этой части оказываются приемлемыми. Оставшаяся часть программы может вообще не компилироваться (а только интерпретироваться) для экономии памяти и времени. В сущности, виртуальная машина Java может работать в любом из двух режимов: клиентском или серверном. Режим определяет, чему будет отдано предпочтение — скорости запуска и экономии памяти или общему быстродействию. В Java 9 также можно воспользоваться опережающей (Ahead-of-Time, AOT) компиляцией, если минимизация времени запуска вашего приложения действительно важна.
Возникает естественный вопрос: зачем удалять всю полезную информацию профилирования при каждом закрытии приложения? Вообще говоря, компания Sun частично уделила внимание этой теме в Java 5.0 — там появилась возможность использования общих классов, доступных только для чтения, которые хранятся в оптимизированной форме. Тем самым значительно сокращается как время запуска, так и непроизводительные затраты ресурсов на выполнение многих приложений Java на обычной машине. Технология, применяемая для достижения этой цели, сложна, но ее идея проста: оптимизировать только те части программы, которые должны выполняться быстро, и не беспокоиться обо всем остальном.
Сравнение Java с другими языками
Проектируя Java, его создатели руководствовались многолетним опытом программирования на других языках. Уделим немного времени сравнению Java с другими языками на концептуальном уровне — это будет полезно и читателям, уже умеющим программировать, и новичкам, которые хотят увидеть общую картину. Обратите внимание, что эта книга не требует от вас знания какого-либо конкретного языка. Мы надеемся, что все упоминания других языков будут достаточно понятными.
В основании любого универсального языка программирования стоят три столпа: портируемость, скорость и безопасность. На рис. 1.2 показаны результаты сравнения Java с некоторыми языками, популярными на момент его создания.
Вероятно, вы слышали, что у Java много общего с языками C и C++. На самом деле это не так, разве что на самом поверхностном уровне. При первом взгляде на код Java вы заметите, что базовый синтаксис напоминает C или C++… но на этом сходство заканчивается. Java ни в коем случае не является прямым потомком C или относящегося к следующему поколению C++. Сравнивая языки по их свойствам, вы увидите, что у Java больше общего с динамическими языками, такими как Smalltalk и Lisp. Практическая реализация Java в компьютере настолько далека от классического C, что трудно даже представить.
Рис. 1.2. Сравнение языков программирования
Читатели, знакомые с современной ситуацией в программировании, заметят, что в этом сравнении отсутствует такой популярный язык, как C#. Он в значительной мере стал ответом Microsoft на появление Java, и надо признать, что в C# добавлен ряд полезных возможностей. Вследствие общих целей и подходов к проектированию приложений (виртуальная машина, байт-код, изолированная среда и т.д.) эти две платформы не очень существенно различаются по скорости и по характеристикам безопасности. C# обладает примерно такой же портируемостью, как и Java. C# многое позаимствовал из синтаксиса C, но на самом деле является более близким родственником динамических языков. Многие Java-разработчики относительно легко осваивают C#, и наоборот. Большая часть времени, затраченного на переход с одного языка на другой, уходит на изучение стандартной библиотеки.
Тем не менее даже поверхностное сходство между этими языками заслуживает внимания. Java заимствует многое из синтаксиса C и C++, так что вы увидите многие лаконичные языковые конструкции с обилием фигурных скобок и точек с запятой. Java следует философии языка C в том, что хороший язык должен быть компактным; иначе говоря, достаточно кратким и систематичным, чтобы программист мог одновременно удерживать в голове все его возможности. Подобно тому как C можно расширять библиотеками, к основным компонентам Java можно добавлять пакеты классов, расширяющие синтаксис.
Язык C стал таким успешным, потому что он предоставляет достаточно функциональную среду программирования с высокой производительностью и приемлемым уровнем портируемости. Java также старается поддерживать баланс между функциональностью, скоростью и портируемостью, но делает это совершенно иным способом. Язык C жертвует функциональностью ради портируемости; Java на первых порах жертвовал скоростью ради портируемости. Кроме того, в Java решены проблемы безопасности, которые остались в C (хотя в современных компьютерах многие из них решаются в операционной системе и на аппаратном уровне).
Много лет назад, до появления JIT и адаптивной компиляции, Java был медленнее, чем языки со статической компиляцией. Критики постоянно говорили, что он никогда их не догонит. Но как мы рассказали в предыдущем разделе, теперь Java уже сравним по быстродействию с языками C и C++ в эквивалентных задачах, поэтому критики в основном притихли. Движок видеоигры Quake 2 от ID Software, распространяемый с открытым кодом, был портирован на Java. Если Java достаточно быстр даже для шутеров с видом от первого лица, то он наверняка будет достаточно быстрым и для большинства бизнес-приложений.
Языки сценариев, такие как Perl, Python и Ruby, не утратили популярности. Нет причин, по которым язык сценариев не подходил бы для безопасных сетевых приложений. Но как правило, языки сценариев не подходят для серьезного крупномасштабного программирования. Главное преимущество языков сценариев заключается в их динамичности; это мощные инструменты для быстрой разработки. Некоторые языки сценариев (такие, как Perl) также предоставляют мощные средства обработки текста, которые в языках более общего назначения становятся неудобными. Языки сценариев также отличаются высоким уровнем портируемости, хотя бы на уровне исходного кода.
JavaScript (не путайте с Java!) — это объектно-базированный язык сценариев, который компания Netscape много лет назад разработала для своего браузера. Сегодня он является встроенным компонентом большинства браузеров, его используют для динамичных и интерактивных веб-приложений. Название JavaScript происходит от интеграции и некоторого сходства с Java, но по своей сути эти языки совершенно разные. Впрочем, у JavaScript есть важные применения и за пределами браузеров (например, платформа Node.js1), а его популярность среди разработчиков в разных областях продолжает расти. За дополнительной информацией о JavaScript обращайтесь к книге Дэвида Фленагана (David Flanagan) «JavaScript: The Definitive Guide» (издательство O’Reilly).
Основная проблема языков сценариев — их неформальное отношение к структуре программы и к типам данных. Большинство языков сценариев не являются объектно-ориентированными. В них упрощены системы типов данных, и обычно в них нет проработанной системы видимости переменных и функций. Из-за этих особенностей они хуже подходят для создания больших модульных приложений. Другая проблема языков сценариев — это скорость; из-за своей высокоуровневой природы такие языки, обычно интерпретируемые на уровне исходного кода, часто отличаются медлительностью.
Сторонники конкретных языков сценариев не согласятся с некоторыми из этих обобщений — и в каких-то случаях будут правы. Языки сценариев в последние годы совершенствовались, особенно JavaScript, который стал быстрее в результате колоссальных исследований. Но нельзя отрицать фундаментальный факт: языки сценариев рождались как неформальные, менее структурированные альтернативы языкам системного программирования, и по многим причинам языки сценариев, как правило, плохо подходят для больших или сложных проектов (по крайней мере на данный момент).
Java обладает некоторыми важнейшими преимуществами языков сценариев, прежде всего высокой динамичностью, а также дополнительными преимуществами низкоуровневого языка. В Java есть мощный API регулярных выражений, способный конкурировать с Perl в области работы с текстом, а также есть языковые средства, упрощающие работу с коллекциями, переменными списками аргументов, статическим импортированием методов и другими удобными синтаксическими конструкциями, которые делают код более лаконичным.
Инкрементальная разработка на Java с объектно-ориентированными компонентами позволяет быстро создавать приложения и легко изменять их, тем более что этот язык отличается простотой. Исследования показали, что разработка на Java выполняется быстрее, чем на C или C++, в основном благодаря богатым синтаксическим возможностям2. Java также включает большую базу стандартных фундаментальных классов для решения таких типичных задач, как создание графических интерфейсов и сетевых приложений. Вы можете использовать Maven Central — внешний ресурс с огромным набором библиотек и пакетов, быстро подключая их к вашей среде программирования для решения всевозможных задач. В дополнение ко всему перечисленному Java обладает масштабируемостью и технологическими преимуществами более статических языков. Java предоставляет безопасную структуру, на базе которой создаются фреймворки более высокого уровня (и даже другие языки).
Мы уже отмечали, что Java по своей архитектуре близок к таким языкам, как Smalltalk и Lisp. Только они используются в основном как исследовательские инструменты, а не средства для разработки крупномасштабных систем. Одна из причин заключается в том, что для этих языков так и не появились стандартные портируемые интерфейсы с сервисами операционной системы, такие как стандартная библиотека C или фундаментальные классы Java. Исходный код Smalltalk компилируется в формат интерпретируемого байт-кода и может динамически компилироваться в платформенный код «на ходу» — примерно так же, как в Java. Но в Java эта архитектура улучшена: правильность скомпилированного байт-кода проверяется верификатором. Верификатор создает для Java преимущество перед Smalltalk, потому что код Java требует меньшего количества проверок на стадии выполнения. Кроме того, верификатор байт-кода Java помогает решать те проблемы безопасности, которые не решаются в Smalltalk.
Далее в этой главе приводится общий обзор языка Java. Мы расскажем, что нового появилось в Java, что осталось неизменным и почему.
Структурная безопасность
Вы уже знаете, что язык Java проектировался как безопасный. Но что имеется в виду под безопасностью? Безопасность — от чего или от кого? Наибольшее внимание к Java привлекают те средства безопасности, которые дают возможность создания новых типов программ с динамической портируемостью. Java предоставляет несколько уровней защиты от опасных ошибок кода, а также от таких вредоносных объектов, как вирусы и трояны. В следующем разделе будет показано, как архитектура виртуальной машины Java оценивает безопасность кода перед его выполнением и как загрузчик классов Java (механизм загрузки байт-кода в интерпретаторе Java) строит «стены» вокруг ненадежных классов. Эти средства закладывают основу для высокоуровневых политик безопасности, которые могут разрешать или запрещать определенные действия на уровне отдельных приложений.
В этом разделе рассматриваются некоторые общие свойства языка Java. По сравнению со специальными средствами безопасности (кстати, часто упускаемыми из виду) еще важнее та безопасность, которую Java обеспечивает благодаря отсутствию многих типичных проблем архитектуры и программирования. В Java есть максимально возможная защита от элементарных ошибок, которые допускают программисты, в том числе при наследовании от старого кода. Создатели Java всегда старались сделать язык простым, включать в него средства, доказавшие свою полезность, и дать программистам возможность создавать на базе языка сложные конструкции, когда возникает такая необходимость.
Упрощать, упрощать, упрощать…
В Java правит простота. Поскольку работа над Java начиналась с нуля, разработчикам удалось избежать тех особенностей, которые оказались проблемными или неоднозначными в других языках. Например, Java не допускает перегрузки операторов (которая в других языках позволяет программисту переопределять смысл таких знаков, как + и -). В Java нет препроцессора исходного кода, поэтому нет и таких средств, как макросы, команды #define или условная компиляция. Эти конструкции существуют в других языках прежде всего для поддержки платформенных зависимостей, и в этом смысле они не нужны в Java. Условная компиляция также часто применяется при отладке, но хорошо проработанные технологии оптимизации исполнительной системы Java и такие средства, как проверочные утверждения (assertions), решают проблему более элегантно. (Вам определенно стоит заняться их изучением после того, как вы освоите основы программирования на Java.)
Java предоставляет четко определенную структуру пакетов, в которых упорядочены файлы классов. Система пакетов позволяет компилятору реализовать часть функциональности традиционной утилиты make (программа для получения исполняемых файлов из исходного кода). Компилятор также может работать непосредственно со скомпилированными классами Java, потому что в них сохраняется вся информация о типах; лишние «заголовочные» файлы как в C/C++ не нужны. Все это означает, что для понимания кода Java требуется меньше контекстной информации. Иногда вам будет проще прочитать исходный код Java, чем документацию класса.
Java иначе подходит к некоторым принципам структурирования кода, которые вызывают проблемы в других языках. Например, иерархия классов в Java строится только путем одиночного наследования (у каждого класса может быть только один «родительский» класс), зато разрешено множественное наследование интерфейсов. Интерфейс (аналог абстрактного класса в C++) задает логику работы объекта, но не определяет его реализацию. Это чрезвычайно мощный механизм, позволяющий разработчику описать нужную логику работы объекта в виде «контракта», который затем будет действовать и использоваться независимо от любых конкретных реализаций объекта. Интерфейсы в Java устраняют необходимость во множественном наследовании классов и все связанные с ним проблемы.
Как будет показано в главе 4, Java — удивительно простой и элегантный язык программирования, и это одна из важных причин его популярности.
Безопасность типов и связывание методов
Одна из главных характеристик любого языка программирования — тот способ, которым в нем осуществляется проверка типов. В общем случае языки делятся на статические и динамические. Разница между ними определяется объемом информации о переменных, известной на стадии компиляции, относительно информации, известной на стадии выполнения.
В языках со строгой статической типизацией (таких, как C и C++) все типы данных жестко фиксируются на момент компиляции исходного кода. Компилятор извлекает пользу из этого факта, так как у него имеется достаточно информации для выявления многих видов ошибок перед выполнением кода. Например, компилятор не позволит сохранить вещественное значение в целочисленной переменной. Код не требует проверки типов во время выполнения, поэтому он компилируется в компактную и быструю форму. Однако языкам со статической типизацией не хватает гибкости. Они не поддерживают коллекции таким же естественным образом, как языки с динамической типизацией, и в них приложение не может безопасно импортировать новые типы данных во время выполнения.
С другой стороны, в динамических языках (таких, как Smalltalk или Lisp) есть исполнительная система, которая управляет типами объектов и выполняет необходимую проверку типов во время выполнения приложения. Такие языки позволяют реализовать более сложную логику работы и во многих отношениях оказываются более мощными. Но они обычно работают медленнее, менее безопасны и создают больше сложностей при отладке.
Различия между языками сравнивались с различиями между моделями автомобилей3. Языки со статической типизацией (такие, как C++) напоминают спортивный автомобиль: достаточно быстрый и безопасный, но практичный только в том случае, если вы едете по хорошей асфальтированной дороге. Языки с ярко выраженной динамической типизацией (такие, как Smalltalk) больше напоминают внедорожники: они предоставляют больше свободы, но иногда ими трудно управлять. На них веселее (а иногда и быстрее) проехать напрямую по перелеску, но там можно застрять в канаве или попасть в лапы медведям.
Еще одна характеристика языка — способ связывания вызовов методов с их определениями. В статических языках (таких, как C и C++) связывание методов обычно происходит во время компиляции, если только программист не потребует обратного. С другой стороны, такие языки, как Smalltalk, называются языками с поздним связыванием, потому что поиск определений методов осуществляется динамически во время выполнения. Раннее связывание важно по соображениям быстродействия; приложение может выполняться без лишних затрат, вызванных поиском методов во время выполнения. А позднее связывание обеспечивает большую гибкость. Оно также необходимо для таких объектно-ориентированных языков, в которых новые типы могут загружаться динамически и только исполнительная система может определить, какой метод надо вызвать в каждом конкретном случае.
Java обладает некоторыми сильными сторонами как C++, так и Smalltalk; это язык со статической типизацией и поздним связыванием. Каждый объект в Java имеет четко определенный тип, известный во время компиляции. Это означает, что компилятор Java может проводить такие же статические проверки типов и анализ использования, как в C++. В результате вы не сможете присвоить объект переменной неправильного типа или вызвать для объекта несуществующие методы. Более того, компилятор Java не позволяет использовать неинициализированные переменные и создавать недоступные команды (см. главу 4).
Однако Java также обладает полноценной динамической типизацией. Исполнительная система Java отслеживает все объекты и позволяет определять их типы и отношения между ними во время выполнения. Таким образом, вы можете проанализировать объект на стадии выполнения, чтобы определить, что он собой представляет. В отличие от C и C++, преобразования объекта от одного типа к другому проверяются исполнительной системой, а новые виды динамически загружаемых объектов могут использоваться с некоторой степенью безопасности типов. Поскольку Java является языком с поздним связыванием, субкласс может переопределять методы своего суперкласса, даже если субкласс загружается во время выполнения.
Инкрементальная разработка
Java переносит всю информацию о типах данных и сигнатурах методов из исходного кода в форму скомпилированного байт-кода. Благодаря этому становится возможной инкрементальная разработка классов Java. Ваш исходный код Java может безопасно компилироваться вместе с классами из других источников, совершенно неизвестных вашему компилятору. Иначе говоря, вы можете писать новый код, который работает с двоичными файлами классов без потери безопасности типов, даже если у вас нет исходного кода этих классов.
Java не страдает от проблемы «хрупкости базовых классов». В таких языках, как C++, разработка базового класса иногда останавливается из-за того, что он имеет множество производных классов; для внесения изменений в базовый класс пришлось бы перекомпилировать все производные классы. Эта проблема особенно сложна для разработчиков библиотек классов. Java обходит эту проблему динамическим поиском полей в классах. Пока класс сохраняет допустимую форму своей исходной структуры, он может развиваться без нарушения работоспособности других классов, производных от него или использующих его.
Динамическое управление памятью
Некоторые важные различия между Java и низкоуровневыми языками (такими, как C и C++) связаны с тем, как реализовано управление памятью. В Java запрещены так называемые указатели, способные ссылаться на произвольные области памяти; зато добавлен механизм уборки мусора и высокоуровневые массивы. Эти особенности устранили многие проблемы с безопасностью, портируемостью и оптимизацией, не решаемые другими способами.
Уборка мусора спасла бесчисленных разработчиков от самого распространенного источника ошибок программирования на C и C++: явного выделения и освобождения памяти. Исполнительная система Java не только размещает объекты в памяти, но и отслеживает все ссылки на них. Когда объект перестает использоваться, Java автоматически удаляет его из памяти. Как правило, на объекты, ставшие ненужными, можно не обращать внимания: интерпретатор уберет их в подходящий момент.
В Java есть сложный уборщик мусора, который работает в фоновом режиме; это означает, что большинство операций уборки мусора происходит в моменты бездействия, в паузах ввода-вывода, между щелчками мышью или нажатиями клавиш. Эффективные технологии исполнительной системы, такие как HotSpot, обеспечивают очень рациональную уборку мусора, в которой учитываются закономерности использования объектов (например, объектов с малым и большим сроком жизни) и оптимизируется их уничтожение. Исполнительная система Java умеет автоматически настраиваться для оптимального распределения памяти в разных видах приложений, в зависимости от их поведения. Благодаря профилированию на стадии выполнения, автоматическое управление памятью может работать намного быстрее самого аккуратного «ручного» управления — это факт, в который программисты старой школы все еще не могут поверить.
Мы уже отметили, что в Java нет указателей. Строго говоря, это утверждение истинно, но оно может вызвать недоразумения. В Java поддерживаются ссылки (references), которые представляют собой безопасную разновидность указателей. Ссылка — это строго типизованный идентификатор объекта. В Java обращение ко всем объектам, кроме примитивных числовых типов, осуществляется по ссылкам. Вы можете использовать ссылки для создания всех обычных структур, которые программисты на языке C привыкли создавать с помощью указателей: связанных списков, деревьев и т.д. Но есть одно отличие: со ссылками вы можете это делать только при соблюдении безопасности типов.
Другое важное отличие указателей от ссылок заключается в том, что программист не может выполнять арифметические операции со ссылками для изменения их значений; ссылки указывают только на конкретные методы, объекты или элементы массива. Ссылки атомарны; со значением ссылки нельзя выполнять другие операции, кроме присваивания объекту. Ссылки передаются по значению, и к объекту нельзя обращаться более чем через один уровень косвенности. Защита ссылок — один из самых фундаментальных аспектов безопасности Java. Отсюда следует, что код Java должен «играть по правилам»; он не может залезть туда, куда ему залезать не положено, и он не может обходить правила.
Наконец, надо упомянуть, что массивы в Java являются настоящими, полноценными объектами. Их можно динамически создавать и присваивать, как и любые другие объекты. Массивы знают свои размеры и типы. Хотя вы не можете напрямую определять классы массивов и создавать субклассы, у массивов есть четко определенные отношения наследования, основанные на отношениях их базовых типов. Когда в языке имеются настолько полноценные массивы, у вас практически исчезает необходимость в арифметических операциях с указателями как в C и C++.
Обработка ошибок
Язык Java уходит корнями к сетевым устройствам и встроенным системам. В этих областях важно иметь надежный и разумный способ контроля возникающих ошибок. Поэтому в Java есть мощный механизм исключений, напоминающий аналогичные механизмы в новых реализациях C++. Исключения — это очень естественный и элегантный способ обработки ошибок. Исключения позволяют отделить код обработки ошибок от основного кода, что делает приложения более стабильными, а их логику — более простой для понимания.
Как только в выполняемом приложении возникает исключение (вследствие какой-то ошибки), управление передается в заранее подготовленный блок кода catch. Этот блок получает исключение в виде объекта, содержащего информацию о ситуации, которая стала причиной исключения. Компилятор Java требует, чтобы каждый метод либо объявлял исключения, которые он может генерировать, либо перехватывал и обрабатывал их самостоятельно. Таким образом, информация об ошибках является столь же важной, как аргументы и возвращаемые типы методов. Программируя на Java, вы точно знаете, с какими исключительными ситуациями вам придется иметь дело, а компилятор помогает вам писать надежные приложения, не оставляющие эти ситуации необработанными.
Потоки
Современным приложениям необходима высокая степень параллелизма. Даже самое линейное приложение может иметь сложный пользовательский интерфейс, а это означает необходимость в нескольких параллельных процессах. В наши дни компьютеры должны работать быстро, и никому не нравится, когда они начинают «тормозить» на второстепенных задачах, отнимая время. Потоки (threads), также называемые потоками выполнения (в отличие от потоков данных), позволяют эффективно распределять и параллельно выполнять несколько задач, ускоряя работу как клиентских, так и серверных приложений. В Java работать с потоками несложно, потому что их поддержка встроена в язык.
Параллелизм очень полезен, но для программирования потоков недостаточно выполнять в них разные задачи в одно и то же время. В большинстве случаев потоки должны быть синхронизированы (скоординированы) между собой, что было бы трудно без явной поддержки со стороны языка. Java поддерживает синхронизацию на базе модели мониторов и условий — своего рода системы «ключей и замков» для обращения к ресурсам. Ключевое слово synchronized помечает методы и блоки кода для безопасного последовательного доступа к ним внутри объекта. Также предусмотрены простые, примитивные методы для явного ожидания своей очереди и для передачи сигналов между потоками, заинтересованными в одном объекте.
Кроме того, в Java есть высокоуровневый пакет параллелизма, предоставляющий мощные средства для реализации типовых паттернов многопоточного программирования, таких как пулы потоков, координация задач и сложная блокировка. Благодаря этому пакету и сопутствующим инструментам, Java считается одним из лучших языков для работы с потоками.
Некоторым разработчикам никогда не приходится писать многопоточный код, но все-таки работа с потоками считается важным навыком программирования на Java, которым должен владеть каждый разработчик. Эта тема рассматривается в главе 9.
Масштабируемость
На самом низком уровне программы Java состоят из классов. Обычно классы представляют собой небольшие модульные компоненты. Кроме классов, Java поддерживает пакеты — это тот уровень структуры, на котором классы группируются в функциональные единицы. Пакеты предоставляют схему формирования имен для упорядочения классов, а также второй уровень организационного управления видимостью переменных и методов в приложениях Java.
Внутри пакета класс либо обладает общедоступной видимостью, либо защищен от внешнего доступа. Пакеты формируют другой тип видимости, более близкий к уровню приложения. Этот уровень хорошо подходит для создания пригодных для повторного использования компонентов, совместно работающих в системе. Пакеты также упрощают создание масштабируемых приложений, которые можно развивать, не превращая их в дебри запутанного кода. Для повторного использования кода и масштабирования приложений наиболее эффективна модульная система (появившаяся в Java 9), но эта тема выходит за рамки книги. Модулям полностью посвящена книга Пола Беккера (Paul Bakker) и Сандера Мака (Sander Mak) «Java 9 Modularity» (издательство O’Reilly).
Безопасность на уровне исполнительной системы Java
Одно дело — создать язык, который мешает вам «выстрелить себе в ногу», и совсем другое — создать язык, который помешает кому-то другому «выстрелить вам в ногу».
Инкапсуляция — это концепция сокрытия внутри класса его данных и логики работы. Инкапсуляция является важной частью объектно-ориентированных архитектур, так как помогает писать чистый, состоящий из модулей код. Впрочем, в большинстве языков видимость элементов данных является всего лишь частью отношений между программистом и компилятором. Это вопрос семантики, а не утверждение о фактической безопасности данных в той среде, где выполняется программа.
Когда Бьёрн Страуструп выбрал ключевое слово private для обозначения скрытых компонентов классов в C++, он думал, скорее всего, о том, чтобы разработчика не беспокоили запутанные подробности кода других разработчиков, а не о том, как защищать классы и объекты от атак вирусов и троянов. Произвольные преобразования типов и арифметические операции с указателями в C и C++ позволяют легко нарушать разрешения доступа к классам, не нарушая при этом правила языка. Возьмем следующий код:
// Код C++
class Finances {
private:
char creditCardNumber[16];
...
};
main() {
Finances finances;
// Формирование указателя для получения доступа
// к конфиденциальным данным внутри класса
char *cardno = (char *)&finances;
printf("Card Number = %.16s\n", cardno);
}
В этой маленькой драме на C++ мы написали код, который нарушает инкапсуляцию класса Finances и извлекает секретную информацию. Подобные фокусы — злоупотребления с нетипизованными указателями — в Java невозможны. Если этот пример кажется вам нереалистичным, подумайте, как важно защищать фундаментальные (системные) классы исполнительной системы от подобных атак. Если ненадежный код сможет повредить компоненты, предоставляющие доступ к реальным ресурсам (к файловой системе, к сети, к оконной системе), то у него появится возможность перехватить номера ваших кредиток.
В тех случаях, когда приложение Java динамически загружает код из ненадежных источников в интернете и выполняет его одновременно с приложениями, содержащими конфиденциальную информацию, защита должна быть очень глубокой. Модель безопасности Java создает вокруг импортированных классов три уровня защиты (рис. 1.3).
Рис. 1.3. Модель безопасности Java
На внешнем контуре этой системы все решения безопасности на уровне приложения принимаются менеджером безопасности, в сочетании с гибкой политикой безопасности. Менеджер безопасности управляет доступом к системным ресурсам: к файловой системе, к сетевым портам, к оконной среде и т.д. Работа менеджера безопасности зависит от способности загрузчика классов защищать базовые системные классы. Загрузчик классов обеспечивает загрузку классов из локального хранилища или из сети. А на самом внутреннем уровне вся безопасность системы в конечном счете зависит от верификатора, гарантирующего целостность получаемых классов.
Этот верификатор байт-кода Java является специальным модулем и неотъемлемой частью исполнительной системы Java. Однако загрузчики классов и менеджеры безопасности (а точнее, политики безопасности) представляют собой компоненты, которые могут быть по-разному реализованы в разных приложениях (например, в серверах и браузерах). Для безопасности в среде Java необходимо правильное функционирование всех этих компонентов.
Верификатор
Первая линия защиты Java — верификатор байт-кода. Верификатор читает байт-код перед его выполнением и убеждается, что тот ведет себя правильно и соблюдает основные правила спецификации байт-кода Java. Надежный компилятор Java не станет генерировать такой байт-код, который нарушает эти условия. Тем не менее злоумышленник может намеренно собрать некорректный байт-код. Верификатор должен обнаружить эту опасность.
После того как код будет проверен, он считается безопасным в том смысле, что в нем нет случайных или злонамеренных ошибок определенных типов. Например, проверенный байт-код не может фальсифицировать ссылки или нарушать разрешения доступа к объектам (как в нашем примере с кредиткой). Он не может выполнять недопустимые преобразования типов и использовать объекты непредвиденным образом. Он даже не может порождать некоторые виды внутренних ошибок, таких как выход за верхнюю или нижнюю границу внутреннего стека. Эти фундаментальные гарантии лежат в основе всей модели безопасности Java.
Вы можете спросить: разве аналогичных мер безопасности нет во многих интерпретируемых языках? Конечно, вам не удастся повредить интерпретатор языка BASIC некорректной строкой написанного на BASIC кода, но не забывайте, что в большинстве интерпретируемых языков защита осуществляется на высоком уровне. В таких языках обычно есть ресурсоемкие интерпретаторы, выполняющие сложную работу на стадии выполнения, и поэтому эти языки неизбежно более медленные и громоздкие.
Для сравнения, байт-код Java представляет собой относительно простой низкоуровневый набор команд. И после того, как байт-код прошел предварительную статическую проверку, он затем выполняется интерпретатором на полной скорости и совершено безопасно, без затратных проверок на стадии выполнения. Это решение стало одним из принципиально важных нововведений Java.
Верификатор является разновидностью математической «системы доказательства теорем». Проходя по байт-коду Java, верификатор анализирует его, применяя простые индуктивные правила. Такой анализ возможен, потому что скомпилированный байт-код Java содержит больше информации о типах, чем объектный код других похожих языков. Байт-код также должен соблюдать определенные правила, упрощающие его логику работы. Во-первых, многие команды байт-кода работают только с конкретными типами данных. Например, при операциях со стеком используются разные команды для обращения к объектам и к каждому из числовых типов Java. Аналогичным образом для каждого типа данных есть отдельные команды, перемещающие значение в локальную переменную и из нее.
Во-вторых, тип объекта, полученного в результате любой операции, всегда известен заранее. Никакие операции байт-кода не уничтожают значения переменных и не генерируют на выходе более одного типа значения. Поэтому всегда возможно проанализировать следующую команду и ее операнды, чтобы узнать тип получаемого значения.
Так как операция всегда производит заранее известный тип, типы всех элементов в стеке и локальных переменных в любой момент будущего могут быть определены по исходному состоянию. Совокупность всей информации о типах в любой конкретный момент называется состоянием типов стека; именно его Java анализирует перед запуском приложения. Java ничего не знает о фактических значениях, хранящихся в стеке и в переменных; известны только их типы. Однако этой информации достаточно для соблюдения правил безопасности и для уверенности в том, что с объектами не будут выполняться некорректные операции.
Чтобы иметь возможность анализировать состояние типов стека, Java устанавливает дополнительное ограничение на выполнение команд своего байт-кода: все пути к любой точке в коде должны завершаться с одним и тем же состоянием типов.
Загрузчики классов
Второй уровень безопасности Java — это загрузчик классов. Он отвечает за передачу байт-кода классов Java интерпретатору. Каждое приложение, которое загружает классы из сети, должно использовать для этого загрузчик классов.
После того как класс будет загружен и пройдет проверку верификатора, он остается связанным со своим загрузчиком классов. В результате классы эффективно делятся на пространства имен в зависимости от своего происхождения. Когда загруженный класс ссылается на другое имя класса, местонахождение нового класса предоставляется исходным загрузчиком классов. Это означает, что всем классам, полученным из конкретного источника, может быть разрешено взаимодействовать только с другими классами из того же источника. Например, браузер с поддержкой Java может использовать загрузчик классов, чтобы сформировать отдельное пространство для всех классов, загруженных по заданному веб-адресу (URL). Загрузчики классов также позволяют реализовать комплексную защиту на основе криптографических цифровых подписей классов.
Поиск классов всегда начинается со встроенных системных классов Java. Они загружаются из тех мест, которые указаны в переменной classpath интерпретатора Java (см. главу 3). Классы из classpath загружаются системой только один раз и остаются неизменными. Это означает, что приложение не может заменить фундаментальные системные классы собственными версиями с измененной функциональностью.
Менеджеры безопасности
Менеджер безопасности отвечает за принятие решений безопасности на уровне приложения. Он представляет собой объект, который может быть установлен приложением для ограничения доступа к системным ресурсам. Менеджер безопасности получает возможность вмешиваться в происходящее каждый раз, когда приложение пытается обратиться к таким ресурсам, как файловая система, сетевые порты, внешние процессы и оконная среда. Менеджер безопасности может разрешить или отклонить каждый такой запрос.
Менеджеры безопасности в первую очередь представляют интерес для приложений, в которых выполнение ненадежного кода является частью нормальной работы. Например, браузер с поддержкой Java может запускать апплеты, загружая их из ненадежных источников в интернете. Одним из первых действий такого браузера должна стать установка менеджера безопасности. С этого момента менеджер безопасности будет ограничивать соответствующие виды доступа. У приложения появится возможность установить оптимальный уровень доверия перед выполнением произвольного блока кода. После того как менеджер безопасности будет установлен, его уже нельзя заменить.
Менеджер безопасности работает в сочетании с контроллером доступа, который позволяет реализовать политики безопасности на высоком уровне посредством редактирования файла декларативной политики безопасности. Политики доступа могут быть настолько простыми или сложными, насколько того требует конкретное приложение.
Иногда бывает достаточно запретить доступ ко всем ресурсам или к целым категориям сервисов (например, к файловой системе или к сети). Также возможно принятие сложных решений на основании высокоуровневой информации. Например, браузер с поддержкой Java может использовать такую политику доступа, которая позволяет пользователям указать уровень доверия для апплета и разрешать или запрещать доступ к определенным ресурсам в каждой конкретной ситуации. Конечно, предполагается, что браузер может определить, каким апплетам следует доверять. Вскоре вы узнаете, как эта проблема решается посредством цифровых подписей кода.
Целостность менеджера безопасности зависит от защиты, предоставляемой более низкими уровнями модели безопасности Java. Без гарантий, предоставляемых верификатором и загрузчиком классов, высокоуровневые предположения относительно безопасности системных ресурсов не имеют смысла. Безопасность, предоставляемая верификатором байт-кода Java, означает, что интерпретатор не может быть поврежден или фальсифицирован, а код Java будет использовать компоненты именно так, как следует. В свою очередь, это означает, что загрузчик классов может гарантировать, что приложение использует фундаментальные системные классы Java, а все обращения к базовым системным ресурсам могут осуществляться только через эти классы. При этих ограничениях контроль над такими ресурсами может быть централизован на высоком уровне с помощью менеджера безопасности и политики, определяемой пользователем.
Безопасность на уровнях приложения и пользователя
Есть тонкая граница между силой, достаточной для решения полезных задач, и силой делать все что угодно. Java закладывает основу для формирования безопасной среды, в которой ненадежный код может подвергаться карантину и выполняться безопасно. Тем не менее если вас не устраивает, что ваш код ограничивается узкими рамками «черного ящика» и выполняется в полной изоляции, вам придется разрешить ему доступ хотя бы к некоторым системным ресурсам. Каждый вид доступа сопряжен как с определенными рисками, так и с преимуществами. Например, в среде браузера преимущество предоставления ненадежному (неизвестному) апплету доступа к вашей оконной системе заключается в том, что он сможет отображать информацию. Значит, вы сможете выводить на экран что-то полезное. Но есть риск, что вместо этого апплет будет выводить какие-то бесполезные, надоедливые или оскорбительные слова.
Вот один крайний случай: даже простой запуск приложения предоставляет ему ресурс — машинное время. Оно может использоваться рационально или растрачиваться понапрасну. Трудно помешать ненадежному приложению расходовать ваше время или даже попытаться провести атаку «отказа в обслуживании». Другой крайний случай: мощное приложение, пользующееся доверием, может обоснованно требовать доступа к самым разным системным ресурсам (к файловой системе, к созданию процессов, к сетевым интерфейсам), а вредоносное приложение с доступом к этим же ресурсам может устроить хаос. Суть в том, что всегда необходимо уделять внимание важным проблемам безопасности, иногда очень сложным.
В некоторых ситуациях допустимо просто предложить пользователю подтвердить запрос кнопкой «ОК». Язык Java предоставляет средства для реализации любых политик безопасности. Тем не менее содержание этих политик в конечном счете зависит от вашей уверенности в происхождении и целостности кода. Здесь в игру вступают цифровые подписи.
Цифровые подписи в сочетании с сертификатами — это средства для проверки того, что данные действительно поступили из указанного источника и их никто не подменил по пути. Если банк снабжает своей цифровой подписью свое приложение «Чековая книжка», то вы можете убедиться в том, что приложение действительно получено от этого банка (а не от какого-то самозванца) и попало к вам в неизмененном виде. Следовательно, вы можете разрешить своему браузеру доверять апплетам с цифровой подписью этого банка.
История Java
Язык активно развивается, и бывает трудно уследить, что в нем доступно сейчас, что обещали разработчики и что изменилось за последнее время. В последующих разделах приведен краткий рассказ о прошлом, настоящем и будущем Java. Не огорчайтесь, если какие-то термины будут непонятны. Некоторые из них мы рассмотрим в книге, а остальные вы можете самостоятельно изучить по мере появления практических навыков и уверенного владения основами Java. Что касается версий Java, в прилагаемых к ним файлах документации (release notes) от Oracle есть хорошие списки вносимых изменений со ссылками на более подробные сведения. Если вы используете старые версии, попробуйте почитать документацию Oracle (https://docs.oracle.com/en/java).
Прошлое: Java 1.0 — Java 13
Версия Java 1.0 предоставила базовую инфраструктуру для разработки Java: сам язык и пакеты, позволяющие писать апплеты и простые приложения. Версия 1.0 официально считается устаревшей, но все еще существует немало апплетов, соответствующих ее API.
Версия Java 1.1 заменила 1.0. В ней были реализованы значительные улучшения пакета AWT (Abstract Window Toolkit) — исходного средства разработки графических интерфейсов в Java, а также новые паттерны событий, новые языковые средства (такие, как рефлексия и внутренние классы) и другие крайне важные возможности. Эта версия много лет поддерживалась многими версиями браузеров Netscape и Internet Explorer. По разным причинам мир браузеров надолго застрял в этом состоянии.
Версия Java 1.2, получившая от Sun дополнительное название «Java 2», была выпущена в качестве основной версии в декабре 1998 года. Она содержала много усовершенствований и дополнений, прежде всего в наборе функций API, включенном в стандартные поставки. Самым заметным дополнением стал пакет разработки графических интерфейсов Swing, представленный в качестве фундаментального API, и новый полноценный API для двумерной графики. Swing — это усовершенствованный GUI-инструментарий для Java, заметно превосходящий по своим возможностям старый пакет AWT. (Swing, AWT и некоторые другие пакеты получили общее название JFC, или Java Foundation Classes). В версии 1.2 также появился полноценный API для работы с коллекциями.
Версия Java 1.3, выпущенная в начале 2000 года, включала ряд второстепенных улучшений, но в первую очередь была направлена на быстродействие. В версии 1.3 код Java стал заметно быстрее работать на многих платформах, а заодно оказались исправленными многие ошибки пакета Swing. Тогда же происходило становление нескольких API корпоративного уровня, таких как Servlets и Enterprise JavaBeans.
В версию Java 1.4, выпущенную в 2002 году, был интегрирован новый набор API и многие давно ожидавшиеся возможности. В их число входили проверочные утверждения, регулярные выражения, API конфигураций и протоколирования, новая система ввода-вывода для крупномасштабных приложений, стандартная поддержка XML, фундаментальные усовершенствования в AWT и в Swing, а также развитая поддержка API сервлетов Java для веб-приложений.
Версия Java 5, выпущенная в 2004 году, стала основной версией, в которую были включены многие давно ожидавшиеся улучшения синтаксиса, в том числе: обобщения, безопасные по типам перечисления, расширенный цикл for, переменные списки аргументов, статическое импортирование, автоматическая упаковка и распаковка примитивных типов, а также усовершенствованные метаданные классов. Новый API многопоточности предоставил мощные средства управления потоками; также были добавлены API для форматирования печати и парсинга (разбора данных), аналогичные тем, которые есть в языке C. Механизм RMI (Remote Method Invocation) был переработан, чтобы исключить необходимость в скомпилированных каркасах и заглушках. Также был существенно дополнен стандартный XML API.
Версия Java 6, выпущенная в конце 2006 года, была относительно второстепенной. Она не добавила в язык Java никаких новых синтаксических элементов, зато содержала новые API расширений (например, для XML и веб-служб).
Версия Java 7, выпущенная в 2011 году, стала относительно существенным обновлением. В язык был внесен ряд дополнений — например, возможность использования строк в командах switch (об этом мы расскажем далее). Также за пять лет, прошедших с выхода Java 6, появились такие важные усовершенствования, как новая библиотека ввода-вывода java.nio.
В версии Java 8, выпущенной в 2014 году, была завершена работа над такими средствами, как лямбда-выражения и методы по умолчанию. (Ранее их исключили из Java 7 из-за того, что не успевали выпустить ее вовремя.) Также в восьмой версии была доработана поддержка даты и времени, в том числе возможность создания неизменяемых объектов с датами, удобных для использования в только что появившихся лямбда-выражениях.
В версии Java 9, выпущенной после нескольких задержек в 2017 году, появилась система модулей (Project Jigsaw), а также REPL-оболочка (Read-Evaluate-Print Loop) jshell. Мы будем использовать jshell во многих кратких примерах кода в последующих главах книги. В Java 9 из JDK также была исключена поддержка JavaDB.
Версия Java 10, выпущенная вскоре после Java 9 в начале 2018 года, получила обновленный механизм уборки мусора, а также ряд других возможностей, например корневые сертификаты для сборок OpenJDK. Была добавлена поддержка неизменяемых коллекций, а поддержка пакетов со старым стилем оформления (например, Aqua от Apple) прекратилась.
В версии Java 11, выпущенной в конце 2018 года, появился стандартный клиент HTTP и протокол TLS 1.3. Модули JavaFX и Java EE были удалены. (Модуль JavaFX был переработан в автономную библиотеку.) Также были исключены апплеты. Наряду с Java 8 версия Java 11 стала частью системы долгосрочной поддержки Oracle (LTS, Long Time Support). Некоторые версии — Java 8, Java 11 и, возможно, Java 17 — будут сопровождаться еще долго. Oracle пытается изменить процесс перехода пользователей и разработчиков на новые версии, однако у многих есть веские причины для того, чтобы продолжать пользоваться хорошо известными версиями. С планами и мыслями Oracle относительно LTS-версий и не-LTS-версий можно ознакомиться в Oracle Technology Network, в разделе «Oracle Java SE Support Roadmap» (https://www.oracle.com/java/technologies/java-se-support-roadmap.html).
В версии Java 12, выпущенной в начале 2019 года, были добавлены некоторые улучшения синтаксиса, в том числе предварительный вариант выражений switch.
Версия Java 13, выпущенная в сентябре 2019 года, включала предварительные варианты новых возможностей языка (например, текстовых блоков), а также значительное изменение реализации API сокетов. Согласно официальной документации, эта впечатляющая разработка предоставляет «более простую и более современную реализацию, которая упрощает сопровождение и отладку».
Настоящее: Java 14
В книгу включены все самые новые и полезные усовершенствования на момент последней фазы выпуска Java 14 весной 2020 года. В этой версии добавлен ряд улучшений синтаксиса в предварительных реализациях, обновлен механизм уборки мусора, удален API Pack200 и связанные с ним инструменты. Выражения switch, впервые представленные в Java 12, перешли из предварительного состояния в стандартный язык. К тому времени, когда вы будете читать эту книгу, появятся новые версии JDK, поскольку они выпускаются каждые полгода. Oracle хочет, чтобы разработчики рассматривали новые версии как обычные обновления функциональности. Для целей этой книги вам будет достаточно Java 11 — надежной версии с долгосрочной поддержкой. При изучении языка вам не надо беспокоиться обо всех его обновлениях, но если вы используете Java в реальных проектах, сверьтесь с «дорожной картой» Java, чтобы решить, имеет ли смысл идти ногу со временем. В главе 13 показано, как можно отслеживать эту «дорожную карту» и как перерабатывать существующий код для использования новейших функций.
Сводка функциональности
Краткая сводка важнейших функциональных возможностей текущего базового API языка Java:
• JDBC (Java Database Connectivity) — основное средство для взаимодействия с базами данных (начиная с Java 1.1).
• RMI (Remote Method Invocation) — система распределенных объектов Java. RMI позволяет вызывать методы объектов, размещенных на сервере в другом месте сети (начиная с Java 1.1).
• Java Security — механизм управления доступом к системным ресурсам, объединенный с единым интерфейсом к криптографическим средствам. Java Security закладывает основу для классов с цифровыми подписями, о чем говорилось ранее.
• Java Desktop — объединяющий термин для множества функций, появившихся в Java 9, среди которых: компоненты пользовательского интерфейса Swing; «вариативный пользовательский интерфейс» (способность интерфейса адаптироваться к разным платформам); перетаскивание; 2D-графика; печать на принтерах; работа с изображениями и звуком; средства доступности (интеграция со специальными программами и с оборудованием для людей с ограниченными возможностями).
• Интернационализация — возможность написания программ, которые адаптируются к языку и локальному контексту, выбранному пользователем; программа автоматически выводит текст на подходящем языке (начиная с Java 1.1).
• JNDI (Java Naming and Directory Interface) — общая служба для просмотра ресурсов. JNDI объединяет доступ к службам каталогов, включая LDAP, Novell NDS и ряд других.
Далее перечислены API стандартных расширений языка. Некоторые из них (например, предназначенные для работы с XML и веб-сервисами) входят в стандартное издание Java; другие надо загружать отдельно и развертывать в вашем приложении или на сервере.
• JavaMail — унифицированный API для приложений, работающих с электронной почтой.
• Java Media Framework — еще один обобщающий термин, включающий Java 2D, Java 3D, Java Media Framework (фреймворк для координации вывода со многими разными типами контента), Java Speech (для распознавания и синтеза речи), Java Sound (для работы со звуком высокого качества), Java TV (для интерактивного телевидения и аналогичных приложений) и т.д.
• Сервлеты Java — средство для создания веб-приложений, работающих на стороне сервера.
• Криптография Java — актуальные реализации криптографических алгоритмов. (Этот пакет был отделен от Java Security по юридическим причинам.)
• XML / XSL — средства для создания документов XML, для их обработки, проверки, отображения на объекты Java и обратно, преобразования при помощи таблиц стилей.
В этой книге мы стараемся дать вам представление о некоторых из этих возможностей. К сожалению для нас (но к счастью для разработчиков), среда Java теперь стала настолько богатой, что рассказать обо всем в одной книге уже невозможно.
Будущее
В наши дни язык Java уже не является модной новинкой, но остается одной из популярнейших платформ для разработки приложений. Это особенно справедливо в таких областях, как веб-службы, фреймворки веб-приложений и инструменты для работы с XML. Хотя языку Java не удалось доминировать на мобильных платформах (как ему, казалось бы, было суждено), тем не менее Java и его основные API можно использовать в программировании для мобильной операционной системы Google Android, используемой на миллиардах устройств по всему миру. В лагере Microsoft язык C#, производный от Java, захватил большую часть разработки .NET и принес базовый синтаксис и паттерны Java на соответствующие платформы.
Виртуальная машина Java (JVM) сама по себе является интересной областью исследования и развития. Появляются новые языки, которые используют набор возможностей и повсеместную доступность JVM. Clojure — мощный функциональный язык, у которого растет число поклонников в самых разных кругах. Kotlin — другой язык, убедительно завоевывающий популярность в сфере разработки для Android (где ранее господствовал Java). Это язык общего назначения, получающий широкое распространение в новых средах и функционально совместимый с Java.
Пожалуй, самые интересные области изменений в Java в наши дни связаны с двумя тенденциями: использование облегченных и упрощенных фреймворков для бизнеса, а также интеграция платформы Java с динамическими языками для сценарного программирования веб-страниц и расширений. Всех нас ждет еще более интересная работа.
Доступные средства
У вас есть выбор из нескольких вариантов сред разработки и исполнительных систем Java. Пакет Oracle JDK доступен для macOS, Windows и Linux. За дополнительной информацией о том, где получить новейшую версию JDK, обращайтесь на веб-сайт Java корпорации Oracle (https://www.oracle.com/java/technologies).
С 2017 года Oracle официально поддерживает обновления проекта с открытым кодом OpenJDK. Эта бесплатная версия может оказаться достаточной для отдельных программистов и для малых (и даже средних) компаний. Выпуски OpenJDK отстают от коммерческих выпусков и не включают профессиональную поддержку от Oracle, но Oracle твердо заявляет о сохранении бесплатного и открытого доступа к Java. Все примеры в книге мы писали и тестировали с помощью OpenJDK. Более подробную информацию вы найдете в первоисточнике, то есть на сайте OpenJDK (https://openjdk.java.net).
Для быстрой установки бесплатной версии Java 11 Amazon предоставляет свой дистрибутив Corretto с удобными инсталляторами для всех трех основных платформ. Версия Java 11 достаточна для всех примеров из этой книги, хотя мы упомянем и несколько возможностей из более поздних версий.
Также существует целый ряд интегрированных сред разработки (Integrated Development Environment, IDE) для Java. Одна из них рассматривается в этой книге: IntelliJ IDEA от компании JetBrains (https://www.jetbrains.com/idea) в бесплатном издании Community Edition. В этой многофункциональной среде разработки, содержащей полный набор необходимых инструментов, вам будет удобно писать, тестировать и упаковывать программы.
1 Если вас интересует Node.js, рекомендуем книгу: Пауэрс Ш. Изучаем Node.js. — СПб.: Питер.
2 Например, см.: Phipps G. Comparing Observed Bug and Productivity Rates for Java and C++. Software — Practice & Experience, том 29, 1999 г.
3 Эту аналогию предложил Маршалл П. Клайн (Marshall P. Cline), автор C++ FAQ.
Различия между языками сравнивались с различиями между моделями автомобилей3. Языки со статической типизацией (такие, как C++) напоминают спортивный автомобиль: достаточно быстрый и безопасный, но практичный только в том случае, если вы едете по хорошей асфальтированной дороге. Языки с ярко выраженной динамической типизацией (такие, как Smalltalk) больше напоминают внедорожники: они предоставляют больше свободы, но иногда ими трудно управлять. На них веселее (а иногда и быстрее) проехать напрямую по перелеску, но там можно застрять в канаве или попасть в лапы медведям.
Инкрементальная разработка на Java с объектно-ориентированными компонентами позволяет быстро создавать приложения и легко изменять их, тем более что этот язык отличается простотой. Исследования показали, что разработка на Java выполняется быстрее, чем на C или C++, в основном благодаря богатым синтаксическим возможностям2. Java также включает большую базу стандартных фундаментальных классов для решения таких типичных задач, как создание графических интерфейсов и сетевых приложений. Вы можете использовать Maven Central — внешний ресурс с огромным набором библиотек и пакетов, быстро подключая их к вашей среде программирования для решения всевозможных задач. В дополнение ко всему перечисленному Java обладает масштабируемостью и технологическими преимуществами более статических языков. Java предоставляет безопасную структуру, на базе которой создаются фреймворки более высокого уровня (и даже другие языки).
Например, см.: Phipps G. Comparing Observed Bug and Productivity Rates for Java and C++. Software — Practice & Experience, том 29, 1999 г.
Если вас интересует Node.js, рекомендуем книгу: Пауэрс Ш. Изучаем Node.js. — СПб.: Питер.
JavaScript (не путайте с Java!) — это объектно-базированный язык сценариев, который компания Netscape много лет назад разработала для своего браузера. Сегодня он является встроенным компонентом большинства браузеров, его используют для динамичных и интерактивных веб-приложений. Название JavaScript происходит от интеграции и некоторого сходства с Java, но по своей сути эти языки совершенно разные. Впрочем, у JavaScript есть важные применения и за пределами браузеров (например, платформа Node.js1), а его популярность среди разработчиков в разных областях продолжает расти. За дополнительной информацией о JavaScript обращайтесь к книге Дэвида Фленагана (David Flanagan) «JavaScript: The Definitive Guide» (издательство O’Reilly).
Эту аналогию предложил Маршалл П. Клайн (Marshall P. Cline), автор C++ FAQ.
Глава 2. Первое приложение
Прежде чем браться за детальное рассмотрение языка Java, попробуем себя в деле: возьмем фрагменты работоспособного кода и немного поэкспериментируем с ними. В этой главе мы напишем маленькое приложение, которое демонстрирует многие концепции, встречающиеся в книге. Оно послужит своего рода презентацией основных возможностей языка и написанных на нем приложений.
Эта глава также служит кратким введением в объектно-ориентированные и многопоточные аспекты Java. Вероятно, эти концепции вам еще неизвестны, и в таком случае мы надеемся, что первое знакомство с ними в Java будет простым и приятным. А если вы уже работали в других объектно-ориентированных или многопоточных средах программирования, то наверняка оцените простоту и элегантность Java. Эта глава дает самый краткий обзор языка и общее представление о том, как им пользоваться. Не беспокойтесь, если какие-то из описанных концепций покажутся непонятными: мы подробнее расскажем о них в последующих главах.
Трудно переоценить важность экспериментов при изучении новых концепций — как в этой главе, так и в других. Не ограничивайтесь чтением примеров кода — выполняйте их. При каждой возможности мы будем показывать, как использовать jshell (подробнее см. раздел «Первые эксперименты с Java», с. 98), чтобы проверять фрагменты кода в реальном времени. Исходный код этих примеров, а также всех остальных примеров книги вы можете загрузить с портала GitHub (https://github.com/l0y/learnjava5e). Компилируйте программы и проверяйте их в работе. А потом превращайте наши примеры в свои: экспериментируйте с ними, изменяйте их логику работы, ломайте, чините… в общем, получайте удовольствие.
Инструменты и среда Java
Теоретически для написания, компиляции и запуска простых Java-приложений достаточно пакета с открытым кодом Java Development Kit от Oracle (OpenJDK) и обычного текстового редактора (vi, «Блокнот» и т.д.). Но на практике почти каждый современный программист пишет приложения в интегрированной среде разработки (IDE). Это дает много преимуществ: удобный просмотр исходного кода c цветовым выделением синтаксиса, помощь с навигацией, управление версиями исходного кода, встроенная документация, сборка, рефакторинг (переработка кода) и развертывание приложений — все эти возможности находятся прямо под рукой. По этой причине мы пропустим академическое описание работы с командной строкой и начнем с популярной бесплатной IDE, которая называется IntelliJ IDEA CE (Community Edition). Впрочем, если вам не хочется работать в IDE, используйте командную строку. Примеры команд: javacHelloJava.java (для компиляции) и javaHelloJava (для запуска).
Для работы в среде IntelliJ IDEA надо сначала установить сам язык Java. В книге рассматривается версия Java 11 (с несколькими упоминаниями нововведений в версиях 12 и 13). Хотя примеры кода из этой главы будут успешно работать и в более ранних версиях, лучше установить JDK версии 11 или выше, чтобы все примеры из книги гарантированно компилировались. JDK содержит некоторые средства разработчика, которые будут рассмотрены в главе 3. Чтобы узнать, какая версия установлена на вашем компьютере (и установлена ли), введите команду java-version в командной строке. Если Java отсутствует или версия более ранняя, чем JDK 11, загрузите нужную версию с сайта OpenJDK (https://jdk.java.net). Вы найдете там весь ряд версий для Linux, macOS и Windows (https://jdk.java.net/archive).
Интегрированную среду IntelliJ IDEA вы можете загрузить с сайта компании JetBrains (https://www.jetbrains.com/idea/download). Для работы с этой книгой, как и для создания множества приложений, более чем достаточно бесплатного издания Community. Загружаемый файл представляет собой инсталлятор .exe (или архив .zip) для Windows, инсталлятор .dmg для macOS или архив .tar.gz для Linux. При необходимости распакуйте архив и запустите инсталлятор. В конце книги, в приложении (с. 497), приведена более подробная информация о загрузке и установке IDEA, а также о том, как загрузить примеры кода из книги.
Установка JDK
Следует сразу сказать, что вы можете свободно загружать и использовать официальный коммерческий пакет Oracle JDK для личных целей. На сайте Oracle (https://www.oracle.com/java) всегда есть новейшая версия, а также ряд предшествующих версий, в том числе с долгосрочной поддержкой. Старые версии иногда бывают нужны разработчикам в целях совместимости.
Но если вы планируете использовать Java в коммерческих целях или в составе проектной группы, то для таких случаев Oracle JDK предоставляется на строгих условиях платного лицензирования. Из-за этого (и по другим, более философским причинам) мы обычно используем бесплатный пакет OpenJDK, упоминавшийся ранее. К сожалению, в этой версии с открытым кодом нет удобных программ установки (инсталляторов) для разных платформ. Если вам нужна простота установки и версия с долгосрочной поддержкой (например, Java 8 или 11), выберите другой дистрибутив OpenJDK, например Amazon Corretto (https://aws.amazon.com/ru/corretto).
Для читателей, которые хотят иметь свободу выбора версии Java и не боятся небольшой работы по настройке, мы расскажем, как устанавливать OpenJDK на каждой из трех основных платформ. Для примера мы выбрали версию Java 13.0.1 — последнюю на момент написания книги. Независимо от того, в какой операционной системе вы работаете, для загрузки OpenJDK перейдите на соответствующую страницу на сайте Oracle (https://jdk.java.net/archive).
Установка OpenJDK в Linux
Файл, загружаемый для типичных систем Linux, представляет собой TAR-архив (.tar.gz). Вы можете распаковать его в любой общий каталог по вашему выбору (например, /usr/lib/jvm). Запустите приложение терминала и выполните следующие команды4, чтобы перейти в каталог загрузки (например, Downloads), распаковать архив и проверить Java:
~ $ cd Downloads
~/Downloads $ sudo tar xvf openjdk-13.0.1_linux-x64_bin.tar.gz \
--directory /usr/lib/jvm
...
jdk-13.0.1/lib/src.zip
jdk-13.0.1/lib/tzdb.dat
jdk-13.0.1/release
~/Downloads $ /usr/lib/jvm/jdk-13.0.1/bin/java -version
openjdk version "13.0.1" 2019-10-15
OpenJDK Runtime Environment (build 13.0.1+9)
OpenJDK 64-Bit Server VM (build 13.0.1+9, mixed mode, sharing)
После успешной установки Java настройте терминал для использования этой среды, присвоив значения переменных JAVA_HOME и PATH. Чтобы убедиться в правильности этой конфигурации, проверьте версию компилятора Java javac:
~/Downloads $ cd
~ $ export JAVA_HOME=/usr/lib/jvm/jdk-13.0.1
~ $ export PATH=$PATH:$JAVA_HOME/bin
~ $ javac -version
javac 13.0.1
Изменения в JAVA_HOME и PATH надо закрепить, обновив стартовые сценарии или сценарии rc для вашей оболочки. Например, обе строки export, только что использованные в терминале, можно добавить в файл .bashrc.
Также надо заметить, что многие дистрибутивы Linux предоставляют доступ к некоторым версиям Java через свои менеджеры пакетов. Поищите в интернете информацию по строкам вида «install java ubuntu» или «install java redhat» и посмотрите, есть ли для вашей системы альтернативные способы установки Java, которые лучше соответствуют вашему привычному стилю работы в Linux.
Установка OpenJDK в macOS
Для пользователей macOS установка OpenJDK похожа на установку в Linux: загрузите архив .tar.gz и распакуйте его в нужное место. В отличие от Linux, «нужное место» определяется вполне конкретно5.
Воспользуйтесь приложением «Терминал» (Terminal) из папки Applications
~ $ cd Downloads
Downloads $ tar xf openjdk-13.0.1_osx-x64_bin.tar.gz
Downloads $ sudo mv jdk-13.0.1.jdk /Library/Java/JavaVirtualMachines/
Команда sudo позволяет пользователям с административными привилегиями выполнять специальные действия, обычно зарезервированные для «суперпользователя». Вам будет предложено ввести пароль. После перемещения папки JDK задайте значение переменной среды JAVA_HOME. Команда java, включенная в macOS, теперь сможет найти установленную версию.
Downloads $ cd ~
~ $ export \
JAVA_HOME=/Library/Java/JavaVirtualMachines/jdk-13.0.1.jdk/Contents/Home
~ $ java -version
openjdk version "13.0.1" 2019-10-15
OpenJDK Runtime Environment (build 13.0.1+9)
OpenJDK 64-Bit Server VM (build 13.0.1+9, mixed mode, sharing)
Как и в случае с Linux, вам надо добавить строку JAVA_HOME в соответствующий стартовый файл (например, в файл .bash_proile в вашем домашнем каталоге), если вы будете работать с Java в командной строке.
У пользователей macOS 10.15 (Catalina) и более поздних версий могут возникнуть некоторые сложности при установке и проверке Java. Вследствие изменений в macOS корпорация Oracle еще не сертифицировала Java для Catalina (на момент выхода книги). Конечно, Java все равно можно запускать в системах Catalina, но наиболее сложные приложения могут столкнуться с ошибками. Заинтересованные пользователи могут прочитать технические заметки Oracle по использованию JDK с Catalina. В первой части этих заметок рассматривается установка официального JDK, а вторая часть посвящена установке из архива .tar.gz, описанной выше.
Установка OpenJDK в Windows
Системы Windows разделяют многие концепции с системами *nix, хотя пользовательские интерфейсы для работы с этими концепциями сильно различаются. Загрузите архив OpenJDK для Windows — это должен быть файл .zip (вместо файла .tar.gz). Распакуйте файл в нужную папку. Как и в случае с Linux, «нужную папку» выбираете вы. Мы создали папку Java в C:\Program Files и поместили в нее содержимое архива, как показано на рис. 2.1.
Когда папка JDK появится на своем месте, настройте переменные среды (по аналогии с macOS и Linux). Самый быстрый способ получить доступ к переменным среды — провести поиск по строке «environment» («переменные среды») и найти в результатах поиска вариант «Edit the system environment variables» («Изменение системных переменных среды»), как показано на рис. 2.2.
Сначала надо создать новую запись для переменной JAVA_HOME и дополнить строку Path информацией о Java. Мы решили добавить эти изменения в системную часть (System variables), но если вы единственный пользователь своего компьютера, их также можно добавить в пользовательскую часть (User variables).
Создайте новую переменную JAVA_HOME и присвойте ей значение: путь к папке, в которой установлен JDK (рис. 2.3).
Рис. 2.1. Папка Java в Windows
Рис. 2.2. Поиск редактора переменных среды в Windows
Рис. 2.3. Создание переменной среды JAVA_HOME в Windows
После того как вы присвоите значение переменной JAVA_HOME, добавьте соответствующую запись в переменную Path, чтобы Windows знала, где искать программы java и javac. Эта запись должна указывать на папку bin, находящуюся в папке с Java. Чтобы указать в Path значение переменной JAVA_HOME, заключите ее имя между символами % (%JAVA_HOME%), как показано на рис. 2.4.
Приложение «Командная строка» (Command Prompt) выполняет в Windows те же функции, что и терминалы в macOS и Linux. Запустите это приложение и введите команду для проверки версии Java. Результат выглядит примерно так, как показано на рис. 2.5.
Рис. 2.4. Редактирование переменной среды Path в Windows
Рис. 2.5. Проверка версии Java в Windows
Конечно, вы можете и дальше работать с Java в командной строке, но лучше указать в настройках IntelliJ IDEA путь к папке с JDK, а затем постоянно использовать эту удобную среду разработки.
Настройка конфигурации IntelliJ IDEA и создание проекта
При первом запуске IDEA выберите создание нового проекта («New Project»). Затем убедитесь, что в строке «Project SDK» указана версия Java 11 или выше, как показано на рис. 2.6, и нажмите кнопку Next.
