Java

Часть 1, Часть 2, Часть 3, Часть 4

Данные статьи помогут легко и быстро разобраться в концепциях и программировании на Java.

Даже при нулевых знаниях о программировании на Java трудностей в освоении этих материалов не возникнет. А опытные Java-разработчики смогут освежить свои знания.

JVM, JRE и JDK

Вот так и происходит вся магия: логика (т.е. код) прописывается в java файле, который затем преобразуется в файл класса. Машина его читает и выполняет.

Поток JVM, JRE и JDK 

А теперь подробнее:

  • JVM — виртуальная машина Java, выполняющая байт-код Java.
  • JVM можно загружать на разном железе. Байт-коды — это машинный язык JVM. Поэтому Java является самым портируемым языком. JVM — это некий объект, который и обеспечивает портируемость. Для разных операционных систем (Mac, Windows, Linux и т.д.) придуманы свои реализации JVM.
  • JRE — среда выполнения Java, достаточная для запуска программы.
  • JRE = JVM + файлы библиотеки/пакеты классов (Util, Lang, Math etc).
  • JDK — пакет средств разработки на Java. Нужен для написания, компиляции и выполнения программы.
  • JDK = JRE + инструменты, необходимые для разработки Java-программы.

Выделение памяти

Коротко о том, как происходит выделение памяти со стороны кода в фоновом режиме:

  • Каждый раз при создании объекта в Java он сохраняется в heap памяти.
  • Примитивы и локальные переменные хранятся в stack памяти, переменные-члены — в heap.
  • При многопоточности каждый поток имеет собственный stack, но находится в общей куче (heap). О многопоточности поговорим во второй части.
  • При вызове какого-либо метода все методы и переменные помещаются в stack. По завершении вызова указатель стека (stack) уменьшается.
  • 32-разрядная операционка тратит не более 4GB RAM на Java-приложения. В 64-разрядной затраты памяти на те же элементы увеличиваются вдвое.
  • Примитивный тип int тратит в 4 раза меньше памяти, чем Integer.
Графическое представление распределения памяти

Таблица ниже перечисляет различные типы данных и их диапазоны хранимых значений:

Типы данных и диапазоны значений

ООП — Инкапсуляция, наследование, полиморфизм и абстракция

Объектно-ориентированное программирование (ООП) — это концепция программирования, основанная на 4 базовых принципах.

1. Инкапсуляция

Инкапсуляция — это объединение данных и функциональных средств в единый компонент. Функциональные средства — это «методы», а данные — это «переменные». Все они объединяются в «класс». Это некая схема или набор инструкций.

Класс — это некий прообраз или прототип, который определяет переменные и методы. Пример:

Класс: Машина Переменные-члены или объекты: цвет, тип, модель и т.д. Методы: остановка, ускорение, предельная скорость.

Объект — это экземпляр класса. В примере выше моя машина будет экземпляром общего класса Машина.

Переменные: локальные, статические и переменные экземпляра. Локальные переменные объявляются в теле метода. Переменные экземпляра объявляются вне метода и являются специфичными для конкретного объекта. Статические переменные инициализируются только один раз при запуске программы. Статические переменные инициализируются первыми, но об этом чуть позже.

Методы— это различные функциональные средства. То есть, не что иное, как набор кода, на который поименно ссылаются или вызывают из любой части программы. Вы передаете несколько значений в метод и он их возвращает.

Пакет— это набор связанных классов. Используется для организации классов в структуру папок, а также для быстрого нахождения и повторного использования этих классов.

package com.example;
class Car {
    String color = "black"; //instance variable
    void accelerate() { 
        int speed = 90; //local variable
    }
}

2. Абстракция

Абстракция — это выбор данных из большого пула по определенным критериям объекта. Ниже показаны различные модификаторы доступа и то, как они ограничивают данные из класса.

3. Наследование

Наследование — это процедура, при которой один класс приобретает свойства другого. Например, потомок наследует признаки своего родителя.

class Developer{
  public void writeCode(){
  // writeCode method
  
}
class BackendDeveloper extends Developer{
  public void writeCode(){
  // writeCode method
  }
}
Class run{
  public static void main (String args[]){
    Developer developerObject = new Developer()
 // writeCode method in class Developer will be executed
    developerObject.writeCode();
   
    BackendDeveloper backendDeveloperObj = new BackendDeveloper();
    // writeCodemethod in class BackendDeveloper will be executed
    backendDeveloperObj.writeCode();
  }
}

4. Полиморфизм

Полиморфизм — это концепция ООП, при которой одно имя принимает множество форм, (другое название — перегрузка). Динамический полиморфизм — это механизм, с помощью которого несколько методов определяются одним именем и сигнатурой подкласса или суперкласса (другое название — переопределение).

  • Перегрузка — это несколько методов одного класса с одним именем, но разной сигнатурой.
  • Переопределение — два метода (один в родительском классе, другой — в дочернем) с одним именем и сигнатурой.
  • Метод подкласса переопределяет метод суперкласса.
  • При переопределении подклассов модификатор доступа должен быть больше родительского класса. Например, если использовать public abc() в родительском классе и private abc() в подклассе — это вызовет исключение.

Загрузка статического и динамического класса

  • Добавление класса для запуска в JVM называется загрузкой класса.
  • Классы загружаются статично с помощью нового оператора.
  • Первый класс загружается через метод static main(). Затем подгружаются остальные классы.
  • В серверных проектах отсутствует main(), поскольку сервер сам отвечает за всю инфраструктуру. Первый класс для загрузки отмечается в config файле. Довольно часто фреймворк реализует метод main() и предоставляет API. Пример: Контейнерный класс вызывает метод init() в сервлетах.
  • main нужен для запуска Java-программы из командной строки в JVM.
  • Если при загрузке статического класса не находится ссылка на класс, то выбрасывается NoClassDefinationFoundException.
  • Динамические классы загружаются через программный вызов при выполнении. Пример: Class.forName(String ClassName);
  • ClassNotFoundException выбрасывается при загрузке динамического класса.

Абстрактный класс и интерфейс

  • В интерфейсе отсутствует код реализации, а все методы являются абстрактными. То есть, все методы объявляются, но ни один не определяется.
  • В абстрактном классе есть исполняемые и абстрактные методы.
  • Класс реализует сколько угодно интерфейсов, но расширяет только один абстрактный класс.
  • Методы абстрактного класса могут быть или не быть абстрактными.
  • Абстрактный класс не может превратиться в экземпляр, но может стать подклассом.
  • Все абстрактные методы должны определяться в подклассе, то есть, подкласс является абстрактным.
  • Создавать экземпляры из интерфейса нельзя. Их можно реализовывать в других классах или расширять другими интерфейсами.
  • Переменные интерфейсов конечные и статические. По умолчанию, все методы интерфейса публичные и абстрактные.
  • Интерфейс не может содержать реализацию и не может превращаться в подкласс. Все переменные должны быть постоянными.

Java Packages

Ниже даны примеры библиотек из пакетов Java которые помогут при написании правильного кода. О них мы еще поговорим.

Java Packages

Конструкторы

  • Их единственная цель — создавать экземпляры класса. Они вызываются в процессе создания объекта класса.
  • Если конструктор с аргументами определен в классе, то нельзя будет работать со стандартным конструктором без аргументов (no-argument constructor) — придется их прописать.
  • Java не поддерживает конструктор копирования.
  • Имя конструктора и класса совпадает.
  • Если конструктор вызывается из другого конструктора с синтаксисом this, то речь идет именно об этом объекте.
  • В Java есть стандартный конструктор.

Приватный конструктор:

  • Защищает класс от явного превращения в экземпляр.
  • Построение объекта возможно только внутри конструктора.
  • Используется в шаблоне «Одиночка» (Singleton).

Вопрос: Можно ли синхронизировать конструкторы в Java?

Нет. В Java запрещен многопоточный доступ к конструкторам объекта, поэтому необходимость в синхронизации отсутствует.

Вопрос: Наследуются ли конструкторы? Может ли подкласс вызывать конструктор родительского класса?

Конструкторы не наследуются. При переопределении конструктора суперклассов нарушается инкапсуляция языка. Конструктор родительского класса вызывается ключевым словом super.


Static

  • Модификатор Static используется для создания чего-то в единственном экземпляре. Например, когда мы хотим создать переменную или объект, доступные для всех объектов класса.
  • Static необходим для передачи информации по всем объектам.
  • Static подходит для переменных, методов и блоков.
  •  или переменные принадлежат классу, а не объекту.
  • Статичный метод или переменная инициализируются один раз перед переменной экземпляра.
  • Статичный метод или переменная могут вызываться напрямую из имени класса. Пример: <className>.<variableName>
  • Статичный метод имеет доступ только к статичным данным.
  • Статичный метод не может ссылаться на this или super.
  • Статичный метод может вызывать только другие статичные методы.
  • main () — это статичный метод. Он должен быть доступен приложению до создания экземпляров.
  • Конструктор не бывает статичным, потому как компилятор считает его методом. Кроме того, конструктор нужен для инициализации нового объекта, а static выполняет совершенно противоположную функцию.
  • Статичная переменная загружается первой. После нее идет статичный блок. И очередность здесь важна. Статичные методы загружаются в конце.

· Иерархия следующая:

Статичный родитель → Статичный потомок → Экземпляр родитель → Конструктор родитель → Экземпляр потомок → Конструктор потомок.
  • При переопределении статичного метода компилятор не выдает ошибок. Но правильнее это называть не переопределением, а скрытием, т.к. пропадают все плюсы полиморфизма.

Final, Finalize и Finally

  • Ключевое слово final указывает на неизменность значения чего-либо.
  • Класс final не расширяется.
  • Метод final не переопределяется.
  • Переменные final равнозначны константам.
  • Блок finally вызывается для всех блоков try-catch и используется для очистки системных ресурсов, будь то подключения, выражения и т.д. Мы еще поговорим о них подробнее.
  • Метод finalize() помогает высвобождать память. Он вызывается перед тем, как сборщик мусора помещает объект на удаление.

Класс Object

В каждом классе есть суперкласс Object. В нем присутствуют следующие не конечные методы:

  • equal()
  • hashCode()
  • toString()
  • clone()
  • finalize()

Конечные методы суперкласса:

  • wait()
  • notify()
  • notifyAll()
  • getClass()

Equals и hashСode

  • Методы equals() и hashСode() переопределяются для сравнения двух объектов.
  • Метод equal() выполняет сравнение, а метод hashCode возвращает хеш-код.
public class Tiger {

private String color;
private String stripePattern;
private int height;

public String getColor() {
 return color;
}

public String getStripePattern() {
 return stripePattern;
}

public Tiger(String color, String stripePattern, int height) {
 this.color = color;
 this.stripePattern = stripePattern;
 this.height = height;
}

@Override
public boolean equals(Object object) {
 boolean result = false;
 if (object == null || object.getClass() != getClass()) {
   result = false;
 } else {
   Tiger tiger = (Tiger) object;
   if (this.color == tiger.getColor() && this.stripePattern == tiger.getStripePattern()) {
       result = true;
       } 
  }
return result;
}

@Override
public int hashCode() {
 int hash = 3;
 hash = 7 * hash + this.color.hashCode();
 hash = 7 * hash + this.stripePattern.hashCode();
 return hash;
}
}

Clone

  • Метод сlone нужен для копирования объекта.
  • В методе clone присутствует защищенный модификатор доступа.
  • Для вызова метода clone объекту требуется реализация интерфейса Cloneable. В противном случае выбрасывается исключение CloneNotSupportedException.
  • Интерфейс Cloneable является маркерным, то есть методы не определяют интерфейс, а говорят классу об особом отношении.
  • Плюс такого интерфейса: можно копировать только объекты, доступные для клонирования.
  • Если какое-то поле объекта ссылается на другой объект, то делаем поверхностную копию. В ней копируется только адрес памяти, т.е. используется один и тот же объект.
  • При глубоком копировании происходит создание объекта и новое динамическое распределение памяти.
Public Object Clone(){
Try{
Return super.clone();
}}
Public Object Clone(){
Try{
Object obj = (Object) super.clone();
Return obj;
}}

Не обращайте внимание на оператора try — к нему мы вернемся позже.


Агрегация и композиция

  • Агрегация выражает отношение is a («являться чем-то»). Пример: дом является зданием.
  • Композиция выражает отношение has a («быть частью чего-то»). Пример: в доме имеется ванная. То есть форма отношения, в котором эта часть не может существовать без остальных элементов.
  • Агрегация — это более слабое отношение. Композиция намного сильнее.
  • Как правило, агрегация достигается расширением класса. Для композиции нужна реализация интерфейса.

Примитивы и оболочки типов

Переменная примитивного типа всегда содержит его значение. В Java существует 8 примитивных типов: byteshortintlongcharbooleanfloat and double.

Класс-оболочка — это класс, объект которого оборачивает или содержит примитивные типы данных. При создании объекта в классе-оболочкепоявляется поле для хранения примитивных типов данных, а также других поддерживающих и операционных методов. Если использовать не сами примитивы, а Object-оболочки для примитивных типов данных, то процесс выполняется медленнее. Дополнительные ресурсы тратятся на создание экземпляра объекта, вызовы методов и т.д. За каждым из этих примитивных типов закреплен свой класс: Byte, Short, Integer, Long, String, Boolean, Float и Double.


Автоупаковка и распаковка

  • Компилятор Java 1.5 автоматически преобразует примитивы к оболочкам типов, то есть выполняет автоупаковку. Обратное действие называется распаковкой.
  • Для этого в компиляторе используется valueOf() и intValue().

Кастинг

  • Это присвоение значение другому примитиву.
byte → short → int → long → float → double
  • В Java возможно и восходящее преобразование (upcasting):
int i = 5; long j = i;
  • Нисходящее преобразование (downcasting) невозможно, т.к. требуется прямой кастинг:
long j = 5;
int i = j; (THIS IS WRONG, it will give classCastException)
int i = (int) j;
  • Преобразование int в String также невозможно.

Перевод статьи Madhu PathyA Beginner’s Guide to Java: Part 1 of 4

Предыдущая статьяРазработчики, которые постоянно что-то изучают — вот вам совет!
Следующая статьяРешение алгоритмических проблем: Поиск повторяющихся элементов в массиве