Эта статья является частью нашего Академического курса под названием Advanced Java .
Этот курс призван помочь вам наиболее эффективно использовать Java. В нем обсуждаются сложные темы, включая создание объектов, параллелизм, сериализацию, рефлексию и многое другое. Он проведет вас через ваше путешествие в мастерство Java! Проверьте это здесь !
Содержание
1. Введение
Согласно индексу сообщества программистов TIOBE, язык программирования Java, созданный в Sun Microsystems и выпущенный еще в 1995 году, является одним из наиболее широко используемых языков программирования в мире. Java — это язык программирования общего назначения. Он привлекателен для разработчиков программного обеспечения, прежде всего, из-за его мощной библиотеки и среды выполнения, простого синтаксиса, богатого набора поддерживаемых платформ (Write Once, Run Anywhere — WORA) и потрясающего сообщества.
В этом уроке мы рассмотрим расширенные концепции Java, предполагая, что наши читатели уже имеют некоторые базовые знания языка. Это ни в коем случае не полная справка, скорее подробное руководство, чтобы перевести ваши навыки Java на следующий уровень.
Вдоль курса будет много фрагментов кода, чтобы посмотреть. Там, где это имеет смысл, тот же пример будет представлен с использованием синтаксиса Java 7 и Java 8.
2. Экземпляр Конструкция
Java является объектно-ориентированным языком, и поэтому создание новых экземпляров классов (объектов), возможно, является наиболее важной концепцией этого. Конструкторы играют центральную роль в инициализации экземпляров нового класса, и Java предоставляет им несколько опций для их определения.
2.1. Неявный (сгенерированный) конструктор
Java позволяет определять класс без каких-либо конструкторов, но это не значит, что у класса их не будет. Например, давайте рассмотрим этот класс:
| 1 2 3 4 | packagecom.javacodegeeks.advanced.construction;publicclassNoConstructor {} | 
 Этот класс не имеет конструктора, но компилятор Java будет генерировать его неявно, и создание новых экземпляров класса будет возможно с использованием new ключевого слова. 
| 1 | finalNoConstructor noConstructorInstance = newNoConstructor(); | 
2.2. Конструкторы без аргументов
Конструктор без аргументов (или конструктор без аргументов) — это самый простой способ явного выполнения работы компилятора Java.
| 1 2 3 4 5 6 7 | packagecom.javacodegeeks.advanced.construction;publicclassNoArgConstructor {    publicNoArgConstructor() {        // Constructor body here    }} | 
  Этот конструктор будет вызываться после создания нового экземпляра класса с использованием ключевого слова new . 
| 1 | finalNoArgConstructor noArgConstructor = newNoArgConstructor(); | 
2,3. Конструкторы с аргументами
Конструкторы с аргументами являются наиболее интересным и полезным способом параметризации создания новых экземпляров классов. В следующем примере определяется конструктор с двумя аргументами.
| 1 2 3 4 5 6 7 | packagecom.javacodegeeks.advanced.construction;publicclassConstructorWithArguments {    publicConstructorWithArguments(finalString arg1,finalString arg2) {        // Constructor body here    }} | 
  В этом случае, когда экземпляр класса создается с использованием ключевого слова new , должны быть указаны оба аргумента конструктора. 
| 1 2 | finalConstructorWithArguments constructorWithArguments =     newConstructorWithArguments( "arg1", "arg2"); | 
  Интересно, что конструкторы могут вызывать друг друга с помощью специального ключевого слова this .  Хорошей практикой считается цепочка конструкторов таким образом, чтобы уменьшить дублирование кода и, в основном, привести к единой точке входа инициализации.  В качестве примера, давайте добавим еще один конструктор с одним аргументом. 
| 1 2 3 | publicConstructorWithArguments(finalString arg1) {    this(arg1, null);} | 
2,4. Блоки инициализации
У Java есть еще один способ предоставить логику инициализации, используя блоки инициализации. Эта функция используется редко, но лучше знать, что она существует.
| 1 2 3 4 5 6 7 | packagecom.javacodegeeks.advanced.construction;publicclassInitializationBlock {    {        // initialization code here    }} | 
Определенным образом блок инициализации может рассматриваться как анонимный конструктор без аргументов. Конкретный класс может иметь несколько блоков инициализации, и все они будут вызываться в порядке, определенном в коде. Например:
| 01 02 03 04 05 06 07 08 09 10 11 12 | packagecom.javacodegeeks.advanced.construction;publicclassInitializationBlocks {    {        // initialization code here    }    {        // initialization code here    }} | 
Блоки инициализации не заменяют конструкторы и могут использоваться вместе с ними. Но очень важно отметить, что блоки инициализации всегда вызываются перед любым конструктором.
| 01 02 03 04 05 06 07 08 09 10 | packagecom.javacodegeeks.advanced.construction;publicclassInitializationBlockAndConstructor {    {        // initialization code here    }        publicInitializationBlockAndConstructor() {    }} | 
2.5. Строительная гарантия
Java предоставляет определенные гарантии инициализации, на которые могут положиться разработчики. Неинициализированные переменные экземпляра и класса (статические) автоматически устанавливаются в значения по умолчанию.
| Тип | Значение по умолчанию | 
| логический | Ложь | 
| байт | 0 | 
| короткая | 0 | 
| ИНТ | 0 | 
| долго | 0L | 
| голец | \ u0000 | 
| поплавок | 0.0f | 
| двойной | 0.0d | 
| ссылка на объект | ноль | 
Таблица 1
Давайте подтвердим это, используя следующий класс в качестве простого примера:
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | packagecom.javacodegeeks.advanced.construction;publicclassInitializationWithDefaults {    privatebooleanbooleanMember;    privatebytebyteMember;    privateshortshortMember;    privateintintMember;    privatelonglongMember;    privatecharcharMember;    privatefloatfloatMember;    privatedoubledoubleMember;    privateObject referenceMember;    publicInitializationWithDefaults() {             System.out.println( "booleanMember = "+ booleanMember );        System.out.println( "byteMember = "+ byteMember );        System.out.println( "shortMember = "+ shortMember );        System.out.println( "intMember = "+ intMember );        System.out.println( "longMember = "+ longMember );        System.out.println( "charMember = "+             Character.codePointAt( newchar[] { charMember }, 0) );        System.out.println( "floatMember = "+ floatMember );        System.out.println( "doubleMember = "+ doubleMember );        System.out.println( "referenceMember = "+ referenceMember );    }} | 
  После создания экземпляра с использованием new ключевого слова: 
| 1 | finalInitializationWithDefaults initializationWithDefaults = newInitializationWithDefaults(), | 
Следующий вывод будет показан в консоли:
| 1 2 3 4 5 6 7 8 9 | booleanMember = falsebyteMember = 0shortMember = 0intMember = 0longMember = 0charMember = 0floatMember = 0.0doubleMember = 0.0referenceMember = null | 
2.6. видимость
Конструкторы подчиняются правилам видимости Java и могут иметь модификаторы контроля доступа, которые определяют, могут ли другие классы вызывать конкретный конструктор.
| Модификатор | пакет | Подкласс | Все остальные | 
| общественности | доступной | доступной | доступной | 
| защищенный | доступной | доступной | Недоступно | 
| <без модификатора> | доступной | Недоступно | Недоступно | 
| частный | Недоступно | Недоступно | Недоступно | 
Таблица 2
2,7. Вывоз мусора
Java (и JVM в частности) использует автоматическую сборку мусора. Проще говоря, всякий раз, когда создаются новые объекты, для них автоматически выделяется память. Следовательно, всякий раз, когда на объекты больше не ссылаются, они уничтожаются и их память восстанавливается.
Сборка мусора Java основана на поколениях и основана на предположении, что большинство объектов умирают молодыми (на них больше не ссылаются вскоре после их создания, и поэтому их можно безопасно уничтожить). Большинство разработчиков полагали, что создание объектов в Java происходит медленно, и создания новых объектов следует избегать, насколько это возможно. На самом деле это не так: создание объектов в Java довольно дешево и быстро. Что дорого, тем не менее, это ненужное создание долгоживущих объектов, которые в конечном итоге могут заполнить старое поколение и вызвать сборку мусора «остановить мир».
2,8. Финализаторы
До сих пор мы говорили об инициализации конструкторов и объектов, но на самом деле ничего не упоминали об их аналоге: уничтожении объектов. Это потому, что Java использует сборщик мусора для управления жизненным циклом объектов, и сборщик мусора несет ответственность за уничтожение ненужных объектов и освобождение памяти.
  Однако в Java есть одна особенность, называемая финализаторами, которая немного напоминает деструкторы, но служит для другой цели очистки ресурсов.  Финализаторы считаются опасной функцией (которая приводит к многочисленным побочным эффектам и проблемам с производительностью).  Как правило, они не являются необходимыми и их следует избегать (за исключением очень редких случаев, в основном связанных с нативными объектами).  Гораздо лучшая альтернатива финализаторам — представленная языковой конструкцией Java 7 под названием try-with-resources и интерфейс AutoCloseable который позволяет писать чистый код, например так: 
| 1 2 3 | try( finalInputStream in = Files.newInputStream( path ) ) {    // code here} | 
3. Статическая инициализация
  До сих пор мы рассматривали создание и инициализацию экземпляра класса.  Но Java также поддерживает конструкции инициализации на уровне класса, называемые статическими инициализаторами .  Они очень похожи на блоки инициализации, за исключением дополнительного static ключевого слова.  Обратите внимание, что статическая инициализация выполняется один раз для загрузчика классов.  Например: 
| 1 2 3 4 5 6 7 | packagecom.javacodegeeks.advanced.construction;publicclassStaticInitializationBlock {    static{        // static initialization code here    }} | 
Подобно блокам инициализации, вы можете включить в определение класса любое количество статических блоков инициализатора, и они будут выполняться в том порядке, в котором они появляются в коде. Например:
| 01 02 03 04 05 06 07 08 09 10 11 | packagecom.javacodegeeks.advanced.construction;publicclassStaticInitializationBlocks {    static{        // static initialization code here    }    static{        // static initialization code here    }} | 
Поскольку статический блок инициализации может быть запущен из нескольких параллельных потоков (когда загрузка класса происходит в первый раз), среда выполнения Java гарантирует, что он будет выполнен только один раз и безопасным для потока способом.
4. Строительные конструкции
За прошедшие годы в сообществе Java появилось несколько понятных и широко применимых шаблонов построения (или создания). Мы рассмотрим самые известные из них: синглтон, помощники, фабрика и внедрение зависимостей (также известное как инверсия управления).
4.1. одиночка
Singleton — один из самых старых и противоречивых шаблонов в сообществе разработчиков программного обеспечения. По сути, основная идея заключается в том, чтобы в любой момент времени мог быть создан только один экземпляр класса. Однако, будучи настолько простым, singleton поднял много дискуссий о том, как сделать это правильно и, в частности, поточно-ориентированным. Вот как может выглядеть наивная версия синглтон-класса:
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 | packagecom.javacodegeeks.advanced.construction.patterns;publicclassNaiveSingleton {    privatestaticNaiveSingleton instance;        privateNaiveSingleton() {            }        publicstaticNaiveSingleton getInstance() {        if( instance == null) {            instance = newNaiveSingleton();        }                returninstance;    }} | 
  По крайней мере, одна проблема с этим кодом состоит в том, что он может создавать много экземпляров класса, если он вызывается одновременно несколькими потоками.  Один из способов правильно спроектировать синглтон (но не ленивым способом) — использовать static свойство final класса. 
| 01 02 03 04 05 06 07 08 09 10 11 12 13 | finalproperty of the class.packagecom.javacodegeeks.advanced.construction.patterns;publicclassEagerSingleton {    privatestaticfinalEagerSingleton instance = newEagerSingleton();        privateEagerSingleton() {            }        publicstaticEagerSingleton getInstance() {        returninstance;    }} | 
Если вы не хотите тратить свои ресурсы и хотели бы, чтобы ваши синглеты создавались лениво, когда они действительно необходимы, необходима явная синхронизация, которая может привести к снижению параллелизма в многопоточных средах (более подробные сведения о параллелизме в Java будут обсуждаться в часть 9 учебника « Лучшие практики параллелизма» .
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 | packagecom.javacodegeeks.advanced.construction.patterns;publicclassLazySingleton {    privatestaticLazySingleton instance;        privateLazySingleton() {            }        publicstaticsynchronizedLazySingleton getInstance() {        if( instance == null) {            instance = newLazySingleton();        }                returninstance;    }} | 
В настоящее время синглтоны в большинстве случаев не считаются хорошим выбором, в первую очередь потому, что они делают код очень сложным для тестирования. Преобладание модели внедрения зависимостей (см. Раздел « Введение зависимостей » ниже) также делает ненужными синглтоны.
4.2. Утилита / Хелпер Класс
  Утилиты или вспомогательные классы — довольно популярная модель, используемая многими разработчиками Java.  По сути, он представляет неинстанцируемый класс (с конструктором, объявленным как private ), опционально объявленным как final (более подробная информация об объявлении классов как final будет предоставлена в части 3 руководства « Как разрабатывать классы и интерфейсы» ) и содержит static методы только.  Например: 
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 | packagecom.javacodegeeks.advanced.construction.patterns;publicfinalclassHelperClass {    privateHelperClass() {            }        publicstaticvoidhelperMethod1() {        // Method body here    }        publicstaticvoidhelperMethod2() {        // Method body here    }} | 
С точки зрения опытного разработчика программного обеспечения, такие помощники часто становятся контейнерами для всех видов несвязанных методов, которые не находят другого места для вставки, но должны каким-то образом использоваться и использоваться другими классами. В большинстве случаев таких проектных решений следует избегать: всегда можно найти другой способ повторного использования требуемой функциональности, сохраняя код чистым и лаконичным.
4,3. завод
  Заводская модель оказалась чрезвычайно полезной техникой в руках разработчиков программного обеспечения.  Как таковой, он имеет несколько разновидностей в Java, от фабричного метода до абстрактной фабрики .  Простейшим примером фабричного шаблона является static метод, который возвращает новый экземпляр определенного класса ( фабричный метод ).  Например: 
| 01 02 03 04 05 06 07 08 09 10 | packagecom.javacodegeeks.advanced.construction.patterns;publicclassBook {    privateBook( finalString title) {    }         publicstaticBook newBook( finalString title ) {         returnnewBook( title );    }} | 
  newBook может возразить, что не имеет смысла вводить newBook фабрики newBook но использование такого шаблона часто делает код более читабельным.  Другая разновидность фабричного шаблона включает интерфейсы или абстрактные классы ( абстрактная фабрика ).  Например, давайте определим фабричный интерфейс : 
| 1 2 3 | publicinterfaceBookFactory {    Book newBook();} | 
С несколькими различными реализациями, в зависимости от типа библиотеки:
| 01 02 03 04 05 06 07 08 09 10 11 12 13 | publicclassLibrary implementsBookFactory {    @Override    publicBook newBook() {        returnnewPaperBook();    }}publicclassKindleLibrary implementsBookFactory {    @Override    publicBook newBook() {        returnnewKindleBook();    }} | 
  Теперь конкретный класс Book скрыт за BookFactory интерфейса BookFactory , все еще обеспечивая общий способ создания книг. 
4.4. Внедрение зависимости
Внедрение зависимостей (также известное как инверсия управления) считается хорошей практикой для разработчиков классов: если какой-либо экземпляр класса зависит от других экземпляров класса, эти зависимости должны быть предоставлены (внедрены) в него с помощью конструкторов (или сеттеров, стратегий). и т. д.), но не созданные самим экземпляром. Давайте рассмотрим следующий пример:
| 01 02 03 04 05 06 07 08 09 10 11 12 | packagecom.javacodegeeks.advanced.construction.patterns;importjava.text.DateFormat;importjava.util.Date;publicclassDependant {    privatefinalDateFormat format = DateFormat.getDateInstance();        publicString format( finalDate date ) {        returnformat.format( date );    }} | 
  Класс Dependant нуждается в экземпляре DateFormat, и он просто создает его, вызывая DateFormat.getDateInstance() во время создания.  Лучше было бы использовать аргумент конструктора, чтобы сделать то же самое: 
| 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 | packagecom.javacodegeeks.advanced.construction.patterns;importjava.text.DateFormat;importjava.util.Date;publicclassDependant {    privatefinalDateFormat format;        publicDependant( finalDateFormat format ) {        this.format = format;    }        publicString format( finalDate date ) {        returnformat.format( date );    }} | 
В этом случае класс имеет все свои зависимости, предоставленные извне, и было бы очень легко изменить формат даты и написать тестовые сценарии для него.
5. Загрузите исходный код
- Вы можете скачать исходный код здесь: com.javacodegeeks.advanced.java
6. Что дальше
  В этой части руководства мы рассмотрели классы и методы создания и инициализации экземпляров классов, а также рассмотрели несколько широко используемых шаблонов.  В следующей части мы рассмотрим класс Object и использование его хорошо известных методов: equals , hashCode , toString и clone .