Java | |
---|---|
Класс языка | |
Появился в | 1995 |
Автор | Джеймс Гослинг и Sun Microsystems |
Разработчик | Sun Microsystems и Oracle[1] |
Расширение файлов | .java, .class, .jar, .jad, .jmod, .war, .ear |
Выпуск | Java SE 23[2] (17.09.2024) |
Испытал влияние | C++, Си, Ада, Simula 67[вд], Smalltalk, Objective-C, Object Pascal, Оберон, Eiffel, Модула-3, Mesa[вд], Симула, C#, UCSD Pascal, обёртка[вд], Вариативная функция, аннотация Java, Никлаус Вирт, Патрик Нотон[вд] и foreach[вд] |
Повлиял на | Ada, C#, Chapel, Clojure, ECMAScript, Fantom, Gambas, Groovy, Hack, Haxe, J#, Kotlin, PHP, Python, Scala, Seed7, Vala, JavaScript |
Лицензия | GNU GPL[3] |
Сайт | oracle.com/ru/java/ |
Медиафайлы на Викискладе |
Java[прим. 1] — строго типизированный объектно-ориентированный язык программирования общего назначения, разработанный компанией Sun Microsystems (в последующем приобретённой компанией Oracle). Разработка ведётся сообществом, организованным через Java Community Process; язык и основные реализующие его технологии распространяются по лицензии GPL. Права на торговую марку принадлежат корпорации Oracle.
Приложения Java обычно транслируются в специальный байт-код, поэтому они могут работать на любой компьютерной архитектуре, для которой существует реализация виртуальной Java-машины. Дата официального выпуска — 23 мая 1995 года. Java занимает высокие места в рейтингах популярности языков программирования (2-е место в рейтингах IEEE Spectrum (2020)[4] и TIOBE (2021[5]).
Изначально язык назывался Oak («Дуб»), разрабатывался Джеймсом Гослингом для программирования бытовых электронных устройств. Из-за того, что язык с таким названием уже существовал, Oak был переименован в Java[6]. Назван в честь марки кофе Java, которая, в свою очередь, получила наименование одноимённого острова (Ява), поэтому на официальной эмблеме языка изображена чашка с горячим кофе. Существует и другая версия происхождения названия языка, связанная с аллюзией на кофемашину как пример бытового устройства, для программирования которого изначально язык создавался. В соответствии с этимологией в русскоязычной литературе с конца двадцатого и до первых лет двадцать первого века название языка нередко переводилось как Ява, а не транскрибировалось.
В результате работы проекта мир увидел принципиально новое устройство, карманный персональный компьютер Star7[7], который опередил своё время более чем на 10 лет, но из-за большой стоимости в 50 долларов не смог произвести переворот в мире технологии и был забыт.
Устройство Star7 не пользовалось популярностью, в отличие от языка программирования Java и его окружения. Следующим этапом жизни языка стала разработка интерактивного телевидения. Однако в 1994 году стало очевидным, что интерактивное телевидение было ошибкой.
С середины 1990-х годов язык стал широко использоваться для написания клиентских приложений и серверного программного обеспечения. Тогда же определённое распространение получила технология Java-апплетов — графических Java-приложений, встраиваемых в веб-страницы; с развитием возможностей динамических веб-страниц в 2000-е годы технология стала применяться редко.
В веб-разработке применяется Spring Framework; для документирования используется утилита Javadoc.
Программы на Java транслируются в байт-код Java, выполняемый виртуальной машиной Java (JVM) — программой, обрабатывающей байтовый код и передающей инструкции оборудованию как интерпретатор.
Достоинством подобного способа выполнения программ является полная независимость байт-кода от операционной системы и оборудования, что позволяет выполнять Java-приложения на любом устройстве, для которого существует соответствующая виртуальная машина. Другой важной особенностью технологии Java является гибкая система безопасности, в рамках которой исполнение программы полностью контролируется виртуальной машиной. Любые операции, которые превышают установленные полномочия программы (например, попытка несанкционированного доступа к данным или соединения с другим компьютером), вызывают немедленное прерывание.
Часто к недостаткам концепции виртуальной машины относят снижение производительности. Ряд усовершенствований немного увеличил скорость выполнения программ на Java:
По данным сайта shootout.alioth.debian.org, для семи разных задач время выполнения на Java составляет в среднем в полтора-два раза больше, чем для C/C++, но при этом в некоторых случаях Java быстрее, а в отдельных случаях в 7 раз медленнее[8]. С другой стороны, для большинства из них потребление памяти Java-машиной было в 10—30 раз больше, чем программой на C/C++. Также примечательно исследование, проведённое компанией Google, согласно которому отмечается существенно более низкая производительность и бо́льшее потребление памяти в тестовых примерах на Java в сравнении с аналогичными программами на C++[9][10][11].
Идеи, заложенные в концепцию и различные реализации среды виртуальной машины Java, вдохновили множество энтузиастов на расширение перечня языков, которые могли бы быть использованы для создания программ, исполняемых на виртуальной машине[12]. Эти идеи нашли также выражение в спецификации общеязыковой инфраструктуры CLI, заложенной в основу платформы .NET компанией Microsoft.
Этот раздел нужно проверить на соответствие критериям взвешенности изложения. |
Разработка Java началась в 1990 году, первая официальная версия — Java 1.0, — была выпущена только 21 января 1996 года.
Вторая версия была выпущена 19 февраля 1997 года[13].
Дата выпуска — 8 декабря 1998 года[14]. Кодовое имя — Playground. Выпускались книги, например, Beginning Java 2 by Ivor Horton (Mar 1999), фактически по J2SE 1.2 (бывшее название — Java 2). Вместе с тем, по сей день такие книги публикуются, например: Х. М. Дейтел, П. Дж. Дейтел, С. И. Сантри. Технологии программирования на Java 2. Распределённые приложения (2011).
В то время, когда Java 2 была исторически заменена следующими релизами, подобные названия книг дезориентируют в понимании, о какой же версии Java они написаны на самом деле. Если J2SE 1.2 принято считать за Java 2, а авторы книг за Java 2 принимают JDK 7, что приводит к путанице.
Дата выпуска — 8 мая 2000 года. Кодовое имя — Kestrel.
Дата выпуска — 13 февраля 2002 года. Кодовое имя — Merlin.
Спецификация Java 5.0 была выпущена 30 сентября 2004 года, кодовое имя — Tiger. C этой версии изменена официальная индексация, вместо Java 1.5 правильнее называть Java 5.0. Внутренняя же индексация Sun осталась прежней — 1.x. Малые изменения теперь включаются без изменения индексации, для этого используется слово «Update» или буква «u», например, Java Development Kit 5.0 Update 22. Предполагается, что в обновления могут входить как исправления ошибок, так и небольшие добавления в API, JVM.
В данной версии разработчики внесли в язык целый ряд принципиальных дополнений:
Релиз версии состоялся 11 декабря 2006 года, кодовое имя — Mustang. Изменена официальная индексация — вместо ожидаемой 6.0 версия значится как 6. Малые изменения, как и в Java 5.0, вносятся в обычные обновления версии, например, Java Standard Edition Development Kit 6 Update 27. Внесены следующие изменения:
При этом организованы новые классы, реализующие перечисленные интерфейсы[16].
Дата выпуска — 8 октября 2013 года.
JavaFX 2.2 вошёл в состав Java SE 7 update 6[18]. С 11-й версии модуль поставляется отдельно от JDK[19].
Дата выпуска — 10 октября 2013 года. Кодовое имя — Micro Edition.
Релиз версии состоялся 28 июля 2011 года, кодовое имя — Dolphin[20]. В финальную версию Java Standard Edition 7 не были включены все ранее запланированные изменения. Согласно плану развития (план «Б»)[21], включение нововведений будет разбито на две части: Java Standard Edition 7 (без лямбда-исчисления, проекта Jigsaw, и части улучшений проекта Coin[22]) и Java Standard Edition 8 (всё остальное), намеченный на конец 2012 года.
В новой версии, получившей название Java Standard Edition 7 (Java Platform, Standard Edition 7), помимо исправления большого количества ошибок, было представлено несколько новшеств. Так, например, в качестве эталонной реализации Java Standard Edition 7 использован не проприетарный пакет JDK, а его открытая реализация OpenJDK, а сам релиз новой версии платформы готовился при тесном сотрудничестве инженеров Oracle с участниками мировой экосистемы Java, комитетом JCP (Java Community Process) и сообществом OpenJDK. Все поставляемые Oracle бинарные файлы эталонной реализации Java Standard Edition 7 собраны на основе кодовой базы OpenJDK, сама эталонная реализация полностью открыта под лицензией GPLv2 с исключениями GNU ClassPath, разрешающими динамическое связывание с проприетарными продуктами. К другим нововведениям относятся:
За 5 дней до выхода релиза Java Standard Edition 7 было обнаружено несколько серьёзных ошибок в горячей оптимизации циклов, которая включена по умолчанию и приводит виртуальную машину Java к краху. Специалисты Oracle найденные ошибки за столь короткий срок исправить не могли, но пообещали, что они будут исправлены во втором обновлении (Java 7 Update 2) и частично в первом[24].
String
[док. 1] в блоке switch
.try
(try-with-resources) — работает при использовании интерфейса AutoClosable
[док. 2].catch
(multi-catch exceptions) — перечисление обрабатываемых исключений в catch
(… | … | …).0b
укажет, что используется двоичное число.URLClassLoader
[док. 3].Релиз версии состоялся 19 марта 2014 года. Кодовое имя — Octopus.
default
в интерфейсах для поддержки функциональности по умолчанию.В связи со сложностями в реализации модульной системы в рамках проекта Jigsaw, релиз версии, первоначально запланированный 22 сентября 2016 года, несколько раз откладывался: сначала дата была перенесена на 23 марта 2017 года, потом — на 27 июля 2017 года, а затем — на 21 сентября 2017 года[28][29][30].
Последняя дата стала официальной датой релиза версии[31].
HttpURLConnection
[док. 4][34].String
отображается в значении переменной-флага, которая теперь есть у всех строк.[36]@Deprecated
добавлен параметр, позволяющий указывать версию программы, с которой использование отмеченного элемента не рекомендуется, а также — параметр, позволяющий указать, что запланировано удаление элемента в какой-либо из будущих версий[38].private
методы в интерфейсах[39].Дата релиза: 20 марта 2018 года[41].
Официальный частичный список нововведений и план релиза расположен на сайте OpenJDK.
var
[42].Официальный частичный список нововведений и план релиза расположен на сайте OpenJDK. Дата релиза — 25 сентября 2018 года.
var
) для унификации с синтаксисом локальных переменных, введённым в JDK 10[54].Дата релиза — 13 марта 2019 года.
switch
может использоваться в выражениях присваивания значения переменной;Дата релиза — 17 сентября 2019 года[59].
switch
добавлена возможность использования ключевого слова yield
, возвращающего указанное за ним значение;Дата релиза — 17 марта 2020 года[59].
record
— для обозначения неизменяемых (final
) классов, являющихся контейнерами неизменяемых данных (записей);instanceof
в блоках switch
;case
блоков switch
могут использоваться «стрелочные» (->) операторы;Дата релиза — 15 сентября 2020 года[59].
sealed
) классов/интерфейсов, при объявлении которых с помощью нового ключевого слова permits
можно указывать, какие классы/интерфейсы могут наследовать данный запечатанный класс/интерфейс;hidden
) классов, которые не могут использоваться непосредственно байт-кодом Java или другими классами;default
-метод isEmpty;Дата релиза — 16 марта 2021 года[59].
record
) и использования instanceof
в блоках switch
;Дата релиза — 14 сентября 2021 года[59].
sealed
) классами/интерфейсами;switch
— в них можно выполнять разные действия в зависимости от того, к какому классу относится указанный объект, то есть метки case могут быть именами классов;Дата релиза — 22 марта 2022 года[59].
Дата релиза — 20 сентября 2022 года[59].
record
);Дата релиза — 21 марта 2023 года[59].
Дата релиза — 19 сентября 2023 года[59].
switch
— в них можно выполнять разные действия в зависимости от того, к какому классу относится указанный объект, то есть метки case могут быть именами классов (с Java 17);record
) (с Java 19);Внутри Java существует несколько основных семейств технологий:
Компанией Microsoft была разработана собственная реализация JVM под названием Microsoft Java Virtual Machine[англ.] (MSJVM)[72], включавшаяся в состав различных операционных систем, начиная с Windows 98 (также входила в Internet Explorer от версии 3 и выше, что позволяло использовать MSJVM в ОС Windows 95 и Windows NT 4 после установки IE3+ на данные ОС).
MSJVM имела существенные отличия от Sun Java, во многом ломающие основополагающую концепцию переносимости программ между разными платформами:
Тесная интеграция Java с DCOM и Win32 поставила под вопрос кроссплатформенную парадигму языка. Впоследствии это явилось поводом для судебных исков со стороны Sun Microsystems к Microsoft. Суд принял сторону компании Sun Microsystems. В конечном счёте между двумя компаниями была достигнута договорённость о возможности продления срока официальной поддержки пользователей нестандартной Microsoft JVM до конца 2007 года[72].
В 2005 году компанией Microsoft для платформы .NET был представлен Java-подобный язык J#, не соответствующий официальной спецификации языка Java и исключённый впоследствии из стандартного инструментария разработчика Microsoft Visual Studio, начиная с Visual Studio 2008[73].
Язык Java активно используется для создания мобильных приложений под операционную систему Android. При этом программы компилируются в нестандартный байт-код для использования их виртуальной машиной Dalvik (начиная с Android 5.0 Lollipop виртуальная машина заменена на ART). Для такой компиляции используется дополнительный инструмент, а именно Android SDK (Software Development Kit), разработанный компанией Google.
Разработку приложений можно вести в среде Android Studio, NetBeans, в среде Eclipse.
Следующие успешные проекты реализованы с привлечением Java (J2EE) технологий: RuneScape, Amazon[74][75], eBay[76][77], LinkedIn[78], Yahoo![79].
Следующие компании в основном фокусируются на Java- (J2EE-) технологиях: SAP, IBM, Oracle. В частности, СУБД Oracle Database включает JVM как свою составную часть, обеспечивающую возможность непосредственного программирования СУБД на языке Java, включая, например, хранимые процедуры[80].
Программы, написанные на Java, имеют репутацию более медленных и занимающих больше оперативной памяти, чем написанные на языке C[8]. Тем не менее, скорость выполнения программ, написанных на языке Java, была существенно улучшена с выпуском в 1997—1998 годах JIT-компилятора в версии 1.1 в дополнение к другим особенностям языка для поддержки лучшего анализа кода (такие, как внутренние классы, класс StringBuffer
[док. 5], упрощённые логические вычисления и так далее). Кроме того, была произведена оптимизация виртуальной машины Java — с 2000 года для этого используется виртуальная машина HotSpot. По состоянию на февраль 2012 года код Java 7 приблизительно в 1,8 раза медленнее кода, написанного на языке Си[81].
Некоторые платформы предлагают аппаратную поддержку выполнения для Java[82]. К примеру, микроконтроллеры, выполняющие код Java на аппаратном обеспечении вместо программной JVM, а также основанные на ARM процессоры, которые поддерживают выполнение байт-кода Java через опцию Jazelle.
В языке Java только 8 примитивных (скалярных, простых) типов: boolean
, byte
, char
, short
, int
, long
, float
, double
. Существует также вспомогательный девятый примитивный тип — void
, однако переменные и поля такого типа не могут быть объявлены в коде, а сам тип используется только для описания соответствующего ему класса, для использования при рефлексии: например, с помощью класса Void
[док. 6] можно узнать, является ли определённый метод типа void
: Hello.class.getMethod("main", String[].class).getReturnType() == Void.TYPE
.
Длины и диапазоны значений примитивных типов определяются стандартом, а не реализацией, и приведены в таблице. Тип char сделали двухбайтовым для удобства локализации (один из идеологических принципов Java): когда складывался стандарт, уже существовал Unicode-16, но не Unicode-32. Поскольку в результате не осталось однобайтового типа, добавили новый тип byte, причём в Java, в отличие от других языков, он не является беззнаковым. Типы float
и double
могут иметь специальные значения , и «не число» (NaN). Для типа double они обозначаются Double.POSITIVE_INFINITY
, Double.NEGATIVE_INFINITY
, Double.NaN
; для типа float
— так же, но с приставкой Float
вместо Double
. Минимальные и максимальные значения, принимаемые типами float
и double
, тоже стандартизованы.
Тип | Длина (в байтах) | Диапазон или набор значений |
---|---|---|
boolean | 1 в массивах, 4 в переменных[83] | true, false |
byte | 1 | −128..127 |
char | 2 | 0..216−1, или 0..65535 |
short | 2 | −215..215−1, или −32768..32767 |
int | 4 | −231..231−1, или −2147483648..2147483647 |
long | 8 | −263..263−1, или примерно −9.2·1018..9.2·1018 |
float | 4 | -(2-2−23)·2127..(2-2−23)·2127, или примерно −3.4·1038..3.4·1038, а также , , NaN |
double | 8 | -(2-2−52)·21023..(2-2−52)·21023, или примерно −1.8·10308..1.8·10308, а также , , NaN |
Такая жёсткая стандартизация была необходима, чтобы сделать язык платформенно-независимым, что является одним из идеологических требований к Java. Тем не менее, одна небольшая проблема с платформенной независимостью всё же осталась. Некоторые процессоры используют для промежуточного хранения результатов 10-байтовые регистры или другими способами улучшают точность вычислений. Для того, чтобы сделать Java максимально совместимой между разными системами, в ранних версиях любые способы повышения точности вычислений были запрещены. Однако это приводило к снижению быстродействия. Выяснилось, что ухудшение точности ради платформенной независимости мало кому нужно, тем более если за это приходится платить замедлением работы программ. После многочисленных протестов этот запрет отменили, но добавили ключевое слово strictfp
, запрещающее повышение точности. Начиная с Java 17, запрет на повышение точности снова начал действовать, а ключевое слово strictfp
было объявлено устаревшим[84].
В языке Java действуют следующие правила:
double
, другой тоже преобразуется к типу double
.float
, другой тоже преобразуется к типу float
.long
, другой тоже преобразуется к типу long
.int
.Данный способ неявного преобразования встроенных типов полностью совпадает с преобразованием типов в Си/C++[85].
В языке Java имеются только динамически создаваемые объекты. Переменные объектного типа и объекты в Java — совершенно разные сущности. Переменные объектного типа являются ссылками, то есть аналогами указателей на динамически создаваемые объекты. Это подчёркивается синтаксисом описания переменных. Так, код на C++ может выглядеть следующим образом:
double a[10][20];
Foo b(30);
Но то же самое на Java будет выглядеть совсем по-другому:
double[][] a = new double[10][20];
Foo b = new Foo(30);
При присваиваниях, передаче в подпрограммы и сравнениях объектные переменные ведут себя как указатели, то есть присваиваются, копируются и сравниваются адреса объектов. А при доступе с помощью объектной переменной к полям данных или методам объекта не требуется никаких специальных операций разыменовывания — этот доступ осуществляется так, как если бы объектная переменная была самим объектом.
Объектными являются переменные любого типа, кроме примитивного. Явных указателей в Java нет. В отличие от указателей C, C++ и других языков программирования, ссылки в Java в высокой степени безопасны благодаря жёстким ограничениям на их использование.
int
или любого другого примитивного типа в указатель или ссылку и наоборот.++
, −−
, +
, −
или любые другие арифметические и логические операции (&&
, ||
, ^^
).&
) или взятия объекта по адресу (*
). Амперсанд (&
) означает всего лишь «побитовое и» (двойной амперсанд — «логическое и»). Однако для булевых типов одиночный амперсанд означает «логическое и», отличающееся от двойного тем, что цепь проверок не прекращается при получении в выражении значения false
[86]. Например, a == b && foo() == bar()
не повлечёт вызовов foo()
и bar()
в случае, если a != b
, тогда как использование &
— повлечёт в любом случае.Благодаря таким специально введённым ограничениям в Java невозможно прямое манипулирование памятью на уровне физических адресов (хотя определено значение ссылки, не указывающей ни на что: null
).
Если нужен указатель на примитивный тип, используются классы-обёртки примитивных типов: Boolean
, Byte
, Character
, Short
, Integer
, Long
, Float
, Double
.
При присваивании не происходит копирования объекта, так как объектные переменные — ссылочные. Так, если написать
Foo foo, bar;
. . .
bar = foo;
то произойдёт копирование адреса из переменной foo
в переменную bar
. То есть foo
и bar
будут указывать на одну и ту же область памяти, то есть на один и тот же объект; попытка изменить поля объекта, на который ссылается переменная foo
, будет менять объект, с которым связана переменная bar
, и наоборот. Если же необходимо получить именно ещё одну копию исходного объекта, пользуются или методом (функцией-членом, в терминологии C++) clone()
, создающим копию объекта, или (реже) копирующим конструктором (конструкторы в Java не могут быть виртуальными, поэтому экземпляр класса-потомка будет неправильно скопирован конструктором класса-предка; метод клонирования вызывает нужный конструктор и тем самым позволяет обойти это ограничение).
Метод clone()
[док. 7] требует, чтобы класс реализовывал интерфейс Cloneable
[док. 8]. Если класс реализует интерфейс Cloneable
, по умолчанию clone()
копирует все поля (мелкая копия). Если требуется не копировать, а клонировать поля (а также их поля и так далее), надо переопределять метод clone()
. Определение и использование метода clone()
часто является нетривиальной задачей[87].
Все переменные или требуют явного определения, или автоматически заполняются нулями (0, null
, false
). Таким образом, исчезают гейзенбаги, связанные со случайным использованием неинициализированной памяти, характерные для низкоуровневых языков вроде Си.
В языке Java невозможно явное удаление объекта из памяти — вместо этого реализована сборка мусора. Традиционным приёмом, дающим сборщику мусора «намёк» на необходимость освобождения памяти, является присваивание переменной пустого значения null
, что может оказаться эффективным при необходимости освободить более не требующийся объект, ссылка на который хранится в долгоживущем объекте[88]. Это, однако, не значит, что объект, заменённый значением null
, будет непременно и немедленно удалён, но есть гарантия, что этот объект будет удалён именно в будущем. Данный приём всего лишь устраняет ссылку на объект, то есть отвязывает указатель от объекта в памяти. При этом следует учитывать, что объект не будет удалён сборщиком мусора, пока на него указывает хотя бы одна ссылка из используемых переменных или объектов. Существуют также методы для инициации принудительной сборки мусора, но не гарантируется, что они будут вызваны исполняющей средой, и их не рекомендуется использовать для обычной работы.
Java не является процедурным языком: любая функция может существовать только внутри класса. Это подчёркивает терминология языка Java, где нет понятий «функция» или «функция-член» (англ. member function), а только метод. В методы превратились и стандартные функции. Например, в Java нет функции sin()
, а есть метод Math.sin()
класса Math
(содержащего, кроме sin()
, методы cos()
, exp()
, sqrt()
, abs()
и многие другие).
Как и в языке C, любая исполняемая программа на Java должна иметь точку входа, которой является функция с именем main — она должна быть единственной в программе и, в отличие от Си, всегда статической. При этом, начиная с Java 21, в которой были введены безымянные классы, допускается определять метод main (точку входа), не определяя имени главного класса.
Конструкторы в Java не считаются методами. Деструкторов в Java не существует, а метод finalize()
ни в коем случае нельзя считать аналогом деструктора.
Конструктор — это специальный метод, который обязательно вызывается при создании нового объекта, то есть объект (экземпляр класса) не может быть создан без вызова конструктора класса. Не всегда удобно инициализировать все переменные класса при создании его экземпляра, поэтому переменные экземпляра часто объявляют внутри тела конструктора, а инициализируют как аргументы конструктора при создании экземпляра класса. Иногда проще, чтобы какие-то значения были бы созданы по умолчанию при создании объекта. В таком случае переменные объявляются и инициализируются внутри тела конструктора.
Конструктор инициализирует объект непосредственно во время создания. Имя конструктора совпадает с именем класса, включая регистр, а по синтаксису конструктор похож на метод без возвращаемого значения.
private int Cat(); // так выглядит метод по имени Cat
Cat(); // так выглядит конструктор класса Cat
В отличие от метода, конструктор никогда ничего не возвращает.
Конструктор определяет действия, выполняемые при создании объекта класса, и является важной частью класса. Как правило, программисты стараются явно указать конструктор. Если явного конструктора нет, то Java автоматически создаст его (пустым) для использования по умолчанию.
В качестве примера можно рассмотреть некий класс Box
, представляющий собой описание коробки. Конструктор класса просто установит начальные размеры для коробки.
class Box {
int width; // ширина коробки
int height; // высота коробки
int depth; // глубина коробки
// Конструктор
Box(int a, int b) {
width = a;
height = b;
depth = 10;
}
// вычисляем объём коробки
int getVolume() {
return width * height * depth;
}
}
В Java (как и в C++) используются статические поля и статические методы (англ. static method — в теории программирования их также называют методами класса), которые задаются при помощи ключевого слова static
. Статические поля (переменные класса) имеют тот же смысл, что и в C++: каждое такое поле является собственностью класса, поэтому для доступа к статическим полям не требуется создавать экземпляры соответствующего класса.
Например, математические функции, реализованные в классе Math
[док. 9], представляют собой как раз статические методы данного класса. Поэтому их можно вызывать напрямую из класса, не создавая его экземпляра, например:
double x = Math.sin(1);
Создание экземпляра статического класса запрещают с помощью приватного конструктора. Например, создание экземпляра класса Math
приведёт к ошибке на этапе компиляции:
Math m = new Math(); // Error: Math() has private access in java.lang.Math
double x = m.sin(1); // Метода sin у объекта не существовало бы, т. к. он статичный
Поскольку статические методы существуют независимо от объектов (экземпляров класса), они не имеют доступа к обычным (не статическим) полям и методам данного класса. В частности, при реализации статического метода недопустимо использовать идентификатор this
.
Возможность статического импорта позволяет вызывать статические функции и константы без указания класса. Пример без статического импорта:
double x = Math.sin(Math.tan(Math.sqrt(y)) + Math.floor(24.5)) + Math.cos(42 * Math.PI);
Тот же пример, но со статическим импортом:
import static java.lang.Math.*;
...
double x = sin(tan(sqrt(y)) + floor(24.5)) + cos(42 * PI);
Ключевое слово final
(финальный) имеет разные значения при описании поля, метода или класса.
switch
(для констант типов int
и String
, в том числе — элементов перечислений), а также для условной компиляции (для констант типа boolean
) при использовании с оператором if
.final
, не могут быть изменены после присвоения. При этом их значения могут использоваться внутри анонимных классов.final
, не может быть переопределён при наследовании.В Java методы, не объявленные явно как static
, final
или private
, являются виртуальными в терминологии C++: при вызове метода, по-разному определённого в базовом и наследующем классах, всегда производится проверка времени выполнения.
Абстрактным методом (модификатор abstract
) в Java называется метод, для которого заданы параметры и тип возвращаемого значения, но не задано тело. Абстрактный метод определяется в классах-наследниках. Аналог абстрактного метода в C++ — чисто виртуальная функция (pure virtual function). Для того чтобы в классе можно было описывать абстрактные методы, сам класс тоже должен быть описан как абстрактный. Объекты абстрактного класса создавать нельзя.
Высшей степенью абстрактности в Java является интерфейс (модификатор interface
). Интерфейс содержит преимущественно абстрактные методы, имеющие всеобщий уровень доступа: описатели abstract
и public
для них даже не требуются. Однако с версий Java 8 и 9 были введены возможности использования в интерфейсах
— Java 8: статических (static
) методов и методов по умолчанию (default
);
— Java 9: методов, имеющих уровень доступа private
.
Эти методы содержат тело, а значит абстрактными не являются, но в конкретной реализации интерфейса default
-методы могут быть переопределены.
Интерфейс в Java не считается классом, хотя, по сути, является полностью абстрактным классом.
Класс может наследовать/расширять (extends
) другой класс или реализовывать (implements
) интерфейс. Интерфейсы же могут наследовать/расширять другие интерфейсы. В Java класс не может наследовать более одного класса, зато может реализовывать несколько интерфейсов. Множественное наследование интерфейсов не запрещено — один интерфейс может наследоваться от нескольких.
С версии Java 15 (экспериментально, официально — с версии Java 17) добавлена возможность использования запечатанных (sealed
) классов/интерфейсов, при объявлении которых, с помощью ключевого слова permits
, можно указывать, какие классы/интерфейсы могут наследовать данный запечатанный класс/интерфейс.
Интерфейсы можно использовать в качестве типов параметров методов. Нельзя создавать экземпляры интерфейсов.
В Java есть интерфейсы, которые не содержат методов для реализации, а специальным образом обрабатываются JVM: Cloneable
, Serializable
, RandomAccess
, Remote
.
Начиная с версии Java 5.0 в языке появился механизм обобщённого программирования — шаблоны, внешне близкие к шаблонам C++. С помощью специального синтаксиса в описании классов и методов можно указать параметры-типы, которые внутри описания могут использоваться в качестве типов полей, параметров и возвращаемых значений методов.
// Объявление обобщённого класса
class GenericClass<E> {
E getFirst() { ... }
void add(E obj) { ... }
}
// Использование обобщённого класса в коде
GenericClass<String> obj = new GenericClass<>();
obj.add("qwerty");
String p = obj.getFirst();
Допускается обобщённое объявление классов, интерфейсов и методов. Кроме того, синтаксис поддерживает ограниченные объявления типов-параметров: указание в объявлении конструкции вида <T extends A & B & C...>
требует, чтобы тип-параметр T реализовывал интерфейсы A, B, C и так далее.
В отличие от шаблонов C#, шаблоны Java не поддерживаются средой исполнения — компилятор просто создаёт байт-код, в котором никаких шаблонов уже нет. Реализация шаблонов в Java принципиально отличается от реализации аналогичных механизмов в C++: компилятор не порождает для каждого случая использования шаблона отдельный вариант класса или метода-шаблона, а просто создаёт одну реализацию байт-кода, содержащую необходимые проверки и преобразования типов. Это приводит к ряду ограничений использования шаблонов в программах на Java.
В Java можно явно проверить, к какому классу принадлежит объект. Выражение foo instanceof Foo
равно true
, если объект foo
принадлежит классу Foo
или его наследнику, или реализует интерфейс Foo
(или, в общем виде, наследует класс, который реализует интерфейс, который наследует Foo
).
Далее функция getClass()
[док. 10], определённая для всех объектов, выдаёт объект типа Class<?>
. Для каждого класса создаётся не более одного описывающего его объекта типа Class
, поэтому эти объекты можно сравнивать. Так, например, foo.getClass() == bar.getClass()
будет истинно, если объекты foo
и bar
принадлежат к одному классу.
Кроме того, объект типа Class<?>
любого типа можно получить так: Integer.class
, Object.class
.
Прямое сравнение классов не всегда является оптимальным средством проверки на принадлежность к классу. Зачастую вместо него используют функцию isAssignableFrom()
. Эта функция определена у объекта типа Class
и принимает объект типа Class<?>
в качестве параметра. Таким образом, вызов Foo.class.isAssignableFrom(Bar.class)
вернёт true
в случае, если Foo
является предком класса Bar
. Так как все объекты являются потомками типа Object
, вызов Object.class.isAssignableFrom()
всегда вернёт true
.
В паре с упомянутыми функциями объекта типа Class
используются также функции isInstance
[док. 11] (эквивалентно instanceof
), а также cast()
(преобразует параметр в объект выбранного класса).
Обработка ошибок в Java похожа на обработку ошибок в C++ за исключением необходимости в блоке finally
. Данное отличие обусловлено тем, что Java не может придерживаться концепции RAII из-за наличия сборщика мусора, а автоматическое освобождение ресурсов в деструкторе может идти в непредсказуемом порядке через произвольные промежутки времени.
Осуществляется обработка ошибок с помощью операторов try
, catch
и finally
. Выбрасываемая ошибка описывается объектом определённого класса, наследующегося от Throwable
[док. 12] и соответствующего типу ошибки. Внутрь блока try
помещается код, который может выбросить исключение, а блок catch
отлавливает заданные программистом типы ошибок. При этом можно указывать более одного блока catch
для обработки различных классов ошибок, или multi-catch для обработки нескольких ошибок. Блок finally
является необязательным, но при наличии выполняется независимо от возникновения ошибки и предназначен для освобождения выделенных в ходе работы блока try
ресурсов.
Начиная с Java 7 поддерживается интерфейс AutoCloseable
[док. 13], который позволяет реализовывать классы, автоматически освобождающие ресурсы. Объекты подобных классов требуется создавать в круглых скобках перед блоком try
. Простым примером автоматического освобождения ресурсом может служить чтение содержимого файла:
import java.io.*;
public class Main {
public static void main(String[] args) throws IOException {
if (args.length < 2) {
System.err.println("Не указано имя файла.");
return;
}
String filename = args[1];
// Открытый файл будет автоматически закрыт по ошибке
try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
String line;
for (int n = 1; (line = reader.readLine()) != null; ++n) {
System.out.println(n + ": " + line);
}
} catch (FileNotFoundException e) {
System.err.println("Указанный файл не найден.");
}
// finally {
// reader.close(); // автоматическое закрытие ресурса
// }
}
}
Java придерживается концепции обязательного указания классов ошибок, которые может выбросить метод. Делается это с помощью ключевого слова throws
после описания метода. Если в методе не указать класс исключения (или его предка), которое может быть выброшено из метода, то это вызовет ошибку компиляции. Концепция должна была позволить делать код самодокументируемым, обозначая, какие исключения может выбросить тот или иной метод, но на практике редко себя оправдывает, поскольку в силу разных обстоятельств программист может указать в качестве выбрасываемого исключения класс Exception
либо заключить проблемные части метода в блок try
…catch
для игнорирования отдельных ошибок, либо — в блок try
…finally
, скрывая все возможные ошибки. Недостатком концепции также является и то, что программист сам должен определять и прописывать исключения, которые может выбрасывать метод[89].
Идея пространств имён воплощена в Java-пакетах.
Название Java-пакета создаётся латиницей (используется нижний и верхний регистр) с цифрами (они не должны быть первыми в строке) и знаком подчёркивания (он не должен быть первым и последним в строке), не являющейся инструкциями языка (например, if
, null
), разделённой точками.
Правильные примеры названий:
project.types.net.media
a0.a_b.canrepeat.canrepeat.UPPERCASE.RaNdOmCaSe
(хотя нежелательно, из-за нечитабельности)Неправильные примеры названий:
doubledots..something
(две точки подряд)нестандартный.язык
(не латиница)0first.characret.is.number
(число в начале)contains.white space
(пробел вместо точки)true.asd
(содержит true
, см. выше)Пакеты содержат классы, интерфейсы, перечисления, аннотации (и т. д.), имена которых тоже создаются латиницей (используется нижний и верхний регистр) с цифрами (они не должны стоять первыми в строке). Публичный класс, интерфейс (и т. д.) в одном файле может быть только один. Имя публичного класса, интерфейса (и т. д.) в файле должно совпадать с названием файла. Каждый класс имеет своё пространство имён для функций, переменных и подклассов, подинтерфейсов (и т. д.), причём получить подкласс класса можно с помощью OuterClass.InnerClass
, а можно — с помощью OuterClass$InnerClass
, поэтому использование символа доллара в названии класса не рекомендуется.
Код программы «Hello, world!».
class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, world!");
}
}
import java.util.List;
import java.util.ArrayList;
public class Sample {
public static void main(String[] args) {
// Создание объекта по шаблону.
List<String> strings = new ArrayList<>();
strings.add("Hello");
strings.add("world");
strings.add("!");
for (var string : strings) {
System.out.print(string + " ");
}
}
}
import java.lang.reflect.Field;
import java.lang.reflect.Method;
class TestClass {
private int value;
public int getValue() { return value; }
public void setValue(int valueIn) { this.value = valueIn; }
}
public class Main {
public static void main(String[] args) {
var testClass = new TestClass();
for (var field: testClass.getClass().getDeclaredFields()) {
System.out.printf("name:%s, type:%s \n", field.getName(), field.getType().getCanonicalName());
}
for (var method : testClass.getClass().getDeclaredMethods()) {
System.out.printf("name:%s, return type:%s \n", method.getName(), method.getReturnType().getCanonicalName());
}
}
}
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface MyAnnotation {
public boolean value() default false;
}
@MyAnnotation(value=true)
public class TestClass {
}
public class Main {
public static void main(String[] args) {
var testClass = new TestClass();
var myAnnotation = testClass.getClass().getAnnotation(MyAnnotation.class);
if (myAnnotation != null) {
System.out.printf("value:%s \n", myAnnotation.value());
}
}
}