Основные понятия которые необходимо знать в JAVA в области объектов классов.
Инкапсуляция — сокрытие информации, запрещение прямого доступа к полям экземпляра данного класса из других классов. Программы должны взаимодействовать с данными объекта только через методы этого объекта. Состояние объекта может измениться только в результате вызовов методов (в противном случае, принцип инкапсуляции не соблюден).
Данные в объекте — поля экземпляра, функции и процедуры, выполняющие операции над данными — методами.
Object — глобальный суперкласс. Все остальые классы расширяют его.
Наследование — расширение класса и получение на основе него нового. Новый класс содержит все свойства и методы расширяемого класса. Также в него добавляются новые методы и поля данных.
В объектно-ориентированном программировании сначала необходимо идентифицировать классы, а затем добавить методы в каждый класс.
Отношения между классами
Зависимость (использует — что-то). Например объекты типа Order должны иметь доступ к объектам типа Account, чтобы проверить количество денег на счету заказчика.
Агрегирование (содержит — что-то). Например объект типа Order (заказ) может содержить объекты типа Item (товары).
Наследование (является — чем-то).
Объектная переменная не содержит никакого объекта. Она лишь ссылается на него. Значение любой объектной переменной в Java представляет собой ссылку на объект, размещенный в другом месте. Операция new также возвращает ссылку.
[java]
Date birthday = new Date();
[/java]
Объектной переменной можно явно присвоить пустое значение null, чтобы указать на то, что она пока не ссылается ни на один из объектов:
[java]
birthday = null;
[/java]
Объектные переменные в Java следует считать аналогами указателей на объекты.
[java]
Date birthday; // Java
[/java]
почти эквивалентна
[cpp]
Date* birthday; // C++
[/cpp]
Все объекты в Java располагаются в динамической области памяти, иначе называемой «кучей». Если объект содержит другую объектную переменную, то она представляет собой всего лишь указатель на другой объект, расположенный в этой области памяти.
Пример простого класса
[java]
class Employee
{
private String name; // Private означает, что к данным полям имеют доступ только методы самого класса Employee. Ни один внешний метод не может читать или записывать данные в эти поля.
private double salary;
private Date hireDay;
public Employee(String n, double s, int year, int month, int day)
{
name = n;
salary = s;
hireDay = LocalDate.of(year, month, day);
}
public String getName()
{
return name;
}
// И так далее
}
[/java]
Не следует объявлять поля экземпляра как public, так как любые компоненты и программы могут к ним обратиться и изменить их.
Внимание
[java]
class Employee
{
private Date hireDay;
…
public Date getHire()
{
return hireDay; //Так делать нельзя!
// Используйте return hireDay.clone(); если нужно скопировать изменямое поле данных.
}
}
[/java]
Дело в том, что объекты типа Date, в отличие от LocalDate, имеют модифицирующие методы. Из-за этого нарушается принцип инкапсуляции.
Привилегии доступа к данным в классе
Метод имеет доступ к закрытым данным того объекта, для которого он вызывается. Но он также может обращаться к закрытым данным всех объектов своего класса.
Закрытые методы
Для взаимодействия с другими объектами требуются открытые методы, однако в ряде случаев для вычислений нужны вспомогательные методы. Как правило, они не являются частью интерфейса, поэтому чаще всего они объявляются как private, т.е. закрытые. Пока метод является закрытым (private), разработчики класса могут быть уверены в том, что он никогда не будет использован в операциях, выполняемых за пределами класса, а значит можно его просто удалить.
Неизменяемые поля экземпляра
[java]
private final String name;
[/java]
Такое поле должно инициализироваться при создании объекта, то есть необходимо гарантировать, что значение поля будет установлено по завершении каждого конструктора. После этого его значение изменить уже нельзя.
Модификатор final удобно применять при объявлении полей простых типов или полей, типы которых задаются неизменяемыми классами. Неизменяемым называется такой класс, методы которого не позволяют изменить состояние объекта. Например, неизменяемым является класс String. Если класс допускает изменения, то ключевое слово final может стать источником недоразумений.
Статические поля
Поле с модификатором доступа static существует в одном экземпляре для всего класса.
[java]
class Article
{
private int id;
private static int nextId = 1;
public void setId()
{
id = nextId;
nextId++;
}
}
[/java]
Даже если не создано ни одного объекта типа Article, статическое поле nextId все равно существует. Оно принадлежит классу, а не конкретному объекту.
Статические константы
Статические переменные используются достаточно редко, однако статические константы применяются намного чаще. Например:
[java]
public class Math
{
public static final double PI = 3.14;
}
[/java]
Делать поля открытыми в коде не рекомендуется, но открытыми константами (т.е. полями, объявленными с final) можно пользоваться смело.
Статические методы
Статическими называют методы, которые не оперируют объектами. У них нет явного параметра this. Так как статические методы не оперируют объектами, из них нельзя получить доступ к полям экземпляра. Но статические методы имеют доступ к статическим полям класса.
Для вызова статических методов рекомендуется использовать имена их классов, а не объекты.
Статические методы следует применять в следующих случаях:
1. Когда методу не требуется доступ к данным о состоянии объекта, поскольку все необходимые параметры задаются явно (например, Math.pow()).
2. Когда методу требуется доступ лишь к статическим полям класса.
P.S. В C++ ключевое слово static обозначает переменные и методы, принадлежащие классу, но не принадлежащие ни одному из объектов этого класса. Именно этот смысл значения перебрался в Java.
Фабричные методы
Фабричные методы — еще одно применение статических методов.
Например:
[java]
NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance();
[/java]
Метод main
Метод main не оперирует никакими объектами. При запуске программы еще нет никаких объектов. Статический метод main() выполняется и конструирует объекты, необходимые программе.
P.S. Каждый класс может содержать метод main(). С его помощью удобно организовать блочное тестирование классов. Причем если класс ClassWithMain с методом main является частью большого приложения ClassMainProgram, то при выполнении java ClassMainProgram метод main в ClassWithMain не выполнится.
Параметры методов
Вызов по значению — метод получает значение, переданное ему из вызывающей части программы.
Вызов по ссылке — метод получает из вызывающей части программы местоположение переменной. Таким образом, метод может модифицировать значение переменной, передаваемой по ссылке, но не переменной, передаваемой по значению.
В языке Java всегда используется только вызов по значению. Таким образом, метод получает копии значений всех своих параметров. Именно по этому метод не может видоизменять содержимое ни одной из переменных, передаваемых ему в качестве парметров.
Причем, методы не могут изменит параметры примитивных типов. Другое дело обстоит с объектами. Метод получает копию ссылки на объект, поэтому копия и оригинал ссылки указывают на один и тот же объект. Однако передача объектов в Java ведется не по ссылке. Если мы передаем объект в метод, то ссылка на этот объект копируется перед началом выполнения метода.
Таким образом, в Java для передачи объектов не применяется вызов по ссылке. Вместо этого ссылки на объекты передаются по значению.
- Метод не может изменять параметры примитивных типов
- Метод может изменять состояние объекта, передаваемого в качестве параметра
- Метод не может делать в своих параметрах ссылки на новые объекты
Конструирование объектов
Перегрузка
Перегрузка — тот случай, когда у нескольких методов имеются одинаковые имена, но разные параметры. Компилятор должен сам решить, какой метод вызвать, сравнивая типы параметров, определяемых при объявлении методов, с типами значений, указанных при вызове методов.
В языке Java можно перегрузить любой метод, а не только конструкторы. Тип возвращаемого методом значения не входит в его сигнатуру. Таким образом, нельзя создать два метода, имеющих одинаковые имена и типы параметров и отличающихся лишь типом возвращаемого значения.
Инициализация полей по умолчанию
Если значение поля в конструкторе явно не задано, то ему автоматически присваивается значение по умолчанию: числам — нули, логическим переменным — логическое значение false, ссылкам на объект — пустое значение null. Однако полагаться на действия по умолчанию не следует.
Конструктор без аргументов
Многие классы содержат конструктор без аргументов, создающий объект, состояние которого устанавливается соответствующим образом по умолчанию.
Пример конструктора без аргументов для класса Building
[java]
public Building()
{
street = "";
house = 0;
}
[/java]
Если в классе совсем не определены конструкторы, то автоматически создается конструктор без аргументов. В этом конструкторе всем полям экземпляра присваиваются их значения, предусмотренные по умолчанию.
Если же в классе есть хотя бы один конструктор и явно не определен конструктор без аргументов, то создавать объекты, не предоставляя аргументы нельзя.
Явная инициализация полей
[java]
private String name = "";
[/java]
Это присваивание выполняется до вызова конструктора. Такой подход оказывается полезным, когда требуется, чтобы поле имело конкретное значение независимо от вызова конструктора класса.
Также поле может инициализироваться с помощью метода.
[java]
class Building
{
private static int nextId;
private int id = assignId();
….
private static int assignId()
{
int r = nextId;
nextId++;
return r;
}
}
[/java]
Имена параметров
Зачастую в качестве параметров служат отдельные буквы. Например:
[java]
public Building(String s, int h)
{
street = s;
house = h;
}
[/java]
Однако при чтении программы невозможно понять, что же означают параметры s и h. Поэтому некоторые программисты добавляют к осмысленным именам параметров префикс «a».
[java]
public Building(String aStreet, int aHouse)
{
street = aStreet;
house = aHouse;
}
[/java]
Есть еще один прием. Дело в том, что параметры скрывают поля экземпляра с такими же именами. Если вызвать метод с параметром house, то ссылка house будет делаться на параметр, а не на поле экземпляра. Доступ к полю экземпляра осуществляется с помощью выражения this.house.
this — обозначает неявный параметр, т.е. конструируемый объект.
[java]
public Building(String street, int house)
{
this.street = street;
this.house = house;
}
[/java]
Вызов одного конструктора из другого
this обозначает неявный параметр метода. Однако у этого слова имеется еще одно значение.
Если первый оператор конструктора имеет вид this( … ), то вызывается другой конструктор этого же класса.
Блоки инициализации
Поле можно инициализировать следующими способами
- Установка значения в конструкторе
- Присваивание значения при объявлении
Однако, есть еще и третий механизм — блок инициализации. Такой блок выполняется каждый раз, когда создается объект данного класса.
[java]
class Building
{
private static int nextId;
private int id;
//Блок инициализации
{
id = nextId;
nextId++;
}
}
[/java]
Блок инициализации выполняется первым, вслед за ним — тело конструктора. Этот механизм обычно не применяется.
Действия, происходящие при вызове конструктора
- Все поля инициализируются значениями, предусмотренными по умолчанию (0, false или null).
- Инициализаторы всех полей и блоки инициализации выполняются в порядке их следования в объявлении класса.
- Если в первой строке кода одного конструктора вызывается другой конструктор, то выполняется вызываемый конструктор.
- Выполняется тело конструктора.
Если для инициализации статических полей класса требуется сложный код, то удобнее использовать статический блок инициализации. Для этого необходимо разместить код в блоке инициализации и пометить его ключевым словом static.
[java]
// Статический блок инициализации
static
{
Random generator = new Random();
nextId = generator.nextInt(10000);
}
[/java]
Статическая инициализация выполняется в том случае, если класс загружается впервые. Аналогично полям экземпляра, статические поля принимают значения 0, false и null.
Уничтожение объектов и метод finalize()
Так как некоторые объекты используют кроме памяти и другие ресурсы, например файлы или другие объекты, которые обращаются к системным ресурсам. Таким образом в данном случае очень важно, чтобы ресурсы освобождались вовремя. Для этого в любой класс можно ввести метод finalize(), который будет вызван перед тем, как система сборки мусора уничтожит объект.
Однако, если требуется возобновить ресурсы и сразу использовать их повторно, нельзя полагаться только на метод finalize(), так как неизвестно когда именно этот метод будет вызван.
Если ресурс должен быть освобожден сразу после его использования, то придется написать соответствующий код самому. Для этого следует предоставить метод close(), который занимается очисткой памяти. Его необходимо вызывать, когда какой-либо объект больше не нужен.
Пакеты
Java позволяет объединять классы в пакеты.
Например, java.lang, java.util являются пакетами.
Пакеты необходимы для того, чтобы обеспечить однозначность имен классов. Например, если в программе будут использованы два класса с одинаковыми именами, но они будут находиться в разных пакетах, то конфликт имен не возникнет.
Для обеспечение однозначности имени пакета используют доменное имя компании, написанно в обратном порядке. В составе пакета можно создавать подпакеты.
Например, в пакете ru.virand можно создать подпакет ru.virand.android
Подпакеты необходимы для гарантирования однозначности имен. С точки зрения компилятора пакет и его подпакет вообще никак не связаны друг с другом.
Импорт классов
В классе могут использоваться все классы из собственного пакета и все открытые классы из других пакетов. Доступ к классам из других пакетов можно получить двумя способами:
- Указав перед именем каждого класса полное имя пакета
- Импортировать один конкретный класс, так и пакет в целом
Статический импорт
[java]
import static java.lang.System.*;
[/java]
Дана форма записи позволяет импортировать не только классы, но и статические методы и поля.
Это позволяет использовать статические методы и поля, определенные в классе System, не указывая имени класса:
[java]
out.println("Hello, this is VIRAND 🙂 "); // С обычным import пришлось бы писать System.out
exit(0); // С обычным import пришлось бы писать System.exit
[/java]
Ввод классов в пакеты
Для того, чтобы ввести класс в пакет, необходимо указать имя пакета в начале исходного файла перед определением класса.
Например:
[java]
package ru.virand.android
public class MathGenerator
{
}
[/java]
Если оператор package в исходном файле не указан, то классы, описанные в этом файле, вводятся в пакет по умолчанию, у которого нет имени.
Пакеты необходимо размещать в подкаталоге, путь к которому соотвтетсвует полному имени пакета: ru/virand/android
[bash]
javac ru/virand/Payment.java
java ru.virand.Payment
[/bash]
Область действия пакетов
Открытые компоненты public могут использоваться любым классом.
Закрытые компоненты private могут использоваться только тем классом, в котором они были определены.
Если ни один из модификаторов доступа не указан, то компонент программы (метод, переменная или класс) доступен всем методам в том же самом пакете.
Переменные должны быть явно обозначены как private, иначе их область действия будет по умолчанию расширена до пределов пакета, а это нарушает принцип инкапсуляции.
Однако разработчики пакета java.awt нарушили этот принцип.
[java]
public class Window extends Container
{
String warningString;
}
[/java]
Как видим у warningString отсутствует модификатор доступа private. Значит методы всех классов из java.awt могут обратиться к ней и изменить ее.
Путь к классам
Классы хранятся в подкаталогах файловой системы. Путь к классу должен совпадать с именем пакета.
Путь к классам лучше всего указывать с помощью параметра -classpath или -cp:
[bash]
java -classpath /home/user/classdir:.:/home/user/archives/archive.jar MyProg.java
[/bash]
Также можно установить переменную окружения CLASSPATH
[bash]
export CLASSPATH=/home/user/classdir:.:/home/user/archives/archive.jar
[/bash]
Документирующие комментарии
Утилита javadoc — составляет документацию в формате HTML.
javadoc извлекает сведения о следующих компонентах программы:
- Пакеты
- Классы и интерфейсы, объявленные как public
- Методы, объявленные как public или protected
- Поля объявленные как public или protected
<h3>Комментарии к методам</h3>
- @param описание_переменной
- @return описание
- @throws описание класса
Документироват нужно лиш открытые поля. Они как парвило являются статическими константами.
<h3>Комментарии общего характера</h3>
- @author имя
- @version текст
- @since текст — создает раздел начальной точки отсчета. Здесь текст означает описаниеверсии программы, в которой впервые был внедрен данный компонент.
- @deprecated текст — добавляет сообщение о том, чтокласс, метод или переменная не рекомендуется к применению.
- @see ссылка — например @see ru.virand.android#Balance(double)
Рекомендации по разработке классов
- Всегда храните данные в переменных, объявленых как private
- Всегда инициализируйте данные
- Не употребляйте в классе слишком много простых типов. Несколько связанных между собой полей простых типов следует объединять в новый класс.
- Не для всех полей нужно создавать методы доступа и модификации.
- Разбивайте на части слишком крупные классы.
- Классы и методыдолжны иметь осмысленные имена.
- Отдавайте предпочтение неизменяемым классам.