- Правила именования ссылок переменных
- Использование объектов
- Жизненный цикл любого класса в Java
- Жизненный цикл instances (объектов) класса
- Память и объекты
- Рекомендация
- Полезные ссылки
Внимание. В слайде была ошибка. time: 01:48 в классе Main метод main должен быть
void
, а не voind
.
В этом уроке много базовых терминов. Их нужно знать как алфавит. Вам придется неоднократно возвращаться и повторять этот материал — это нормально. :)
Java Virtual Machine (JVM) — это программная система, в которой исполняются java-программы. Для java-программ JVM является реальной, а не виртуальной вычислительной машиной. А вот уже JVM написана под различные платформы и операционные системы.
blueprint — буквально это "фотографическая копия раннего плана здания или машины". Выполняется на бумаге синего (голубого) цвета. В рамках программирования это тоже чертеж, на основании которого будет создан объект. Только этот чертеж состоит не из линий на чертежной доске, а из строк кода, которые задают свойства и возможности будущих объектов.
класс — это тот самый blueprint в мире java, по которому будут создаваться объекты. Класс состоит из кода, который хранится в соответствующем файле.
Вот пример кода класса Human, который должен обязательно находится в файле Human.java
:
class Human {
int age = 99;
String name = "Oldman";
}
На основе этих строк будут создаваться объекты.
члены класса — это основные элементы, которые может содержать в себе класс. Ими являются переменные (поля) и методы.
экземпляр, объект, instance — это сущность созданная на основе класса. Так же как и деталь на токарном станке создается на основе чертежа или человек "создается" на основе ДНК. Например, объект класса Object создается так: new Object()
. А объект класса Human — new Human()
. В мире java все является объектом.
оператор new
выделяет место в памяти JVM под объект.
идентификатор (ссылка на документацию) — это, по сути, имя. Такое же, как и у людей, мест, объектов во всем мире. Но в документации java применяется термин "identifier". Потому что он позволяет идентифицировать конкретную сущность в мире java. В java идентификатор используется для переменных, методов, классов. Например, Object, String, Human, SomeClassName — это примеры идентификаторов (имен) классов. Имена классов всегда пишутся с большой буквы в camelStyle. Имена методов мы разберем в отдельном уроке. А имена переменных — в этом.
метод — это член класса в котором описывается логика (последовательность действий) над данными. О методах мы поговорим в других уроках.
reference, ссылка, переменная — это идентификатор (имя), с заданным типом, который может указывать на конкретный объект в оперативной памяти или на значение примитива. Ссылку часто называют переменной, но это не всегда соответствует ее свойствам. Важно понимать, что любая переменная предназначена для хранения адреса в памяти на конкретный объект или значение примитива, потому — ссылка на что-то. Правила именования ссылок будут даны ниже. При объявлении ссылки, естественно, нужно указать ее тип и имя. Например, так: Object myObject
. Здесь Object
— это тип, который сможет хранить в себе данная ссылка. myObject
— это имя. Но эта ссылка, пока что, указывает в никуда, в null
. Ей нужно присвоить объект — инициализировать.
null
— это специальное ключевое слово, которое означает буквально "ничто".
инициализация переменной — это присвоение ссылке адреса конкретного объекта. Делается это с помощью знака равно "=
". В java это оператор присваивания. Запишем объявление ссылки, ее инициализацию и создание объекта в одну строчку: Object otherObject = new Object();
.
На один и тот же объект могут ссылаться несколько ссылок:
Human firstHuman = new Human(); Human secondHuman = firstHuman; Human thirdHuman = firstHuman;
В данном кусочке кода создан один объект типа
Human
и присвоен трем ссылкам (передан его адрес в памяти по цепочке). А вот если написать напротив каждой ссылкиnew Human()
— то у каждой ссылки будет свой объект.У одной ссылки (идентификатора) — может быть только один объект!
блок кода — это часть кода, заключенная в фигурные скобки: {}
. {
— открывает блок кода. }
— закрывает блок кода. Класс, метод, цикл, условный оператор — имеют свои блоки кода. Блок кода каждого из них, для простоты, называют телами. Методы, циклы, условные операторы — мы рассмотрим в других уроках. А сейчас давайте просто посмотрим на их тела в таком примере:
class Computer { // начало тела класса
String foo = "просто пример поля класса";
int computeSumm(int[] incomeArray) { // начало тела метода computeSumm
int result = 0;
if (incomeArray != null) { // начало тела условного оператора
for (int number : incomeArray) { // начало тела цикла
result += number;
} //конец тела цикла
} // конец тела условного оператора
return result;
} // конец тела метода computeSumm
void printFoo() { // начало тела метода printFoo
System.out.println(foo);
} // конец тела метода printFoo
} // конец тела класса
Переменные (ссылки), согласно документации, делятся на такие типы:
- Поля класса. Это ссылки, которые объявлены в теле класса. Если взять в пример класс Human, то его полями будут
name
иage
. В классеComputer
это будет поле с именемfoo
. Объявление и инициализация поля класса не может быть разбито на две строки в рамках тела класса! Например, нельзя написать так: ```java class Computer2 { String foo; // объявляем поле класса foo = "просто пример поля класса"; // безуспешно пытаемся его инициализировать, ошибка компиляции! }
Но можно вынести инициализацию в другой блок кода так:
```java
class Computer3 { // начало тела класса
String foo; // объявляем поле класса
void printFoo() { // начало тела метода printFoo
foo = "просто пример поля класса"; // успешно инициализируем поле foo в теле метода
System.out.println(foo); // печатаем на экран содержимое foo
} // конец тела метода printFoo
} // конец тела класса
Где, как и когда инициализировать поля класса — зависит от условий задачи, которую Вы решаете.
- Локальные переменные. Это ссылки, которые объявлены в любом блоке кода, который вложен в тело класса. Если рассмотреть класс
Computer
, то в нем локальной переменной будетresult
, поскольку эта ссылка объявлена внутри тела методаcomputeSumm
. - Параметры. Это ссылки, задающие начальное состояние блока кода. Например, у метода
computeSumm
есть свой параметрincomeArray
, который имеет типint[]
. Параметры метода еще называют "аргументы метода". А вотnumber
уже является параметром циклаfor
.
Любая переменная — это просто ссылка на конкретный объект или значение (примитив) в памяти компьютера.
область видимости — это границы, в которых доступны переменные для членов класса. Каждый блок кода видит переменные внешнего блока кода, но не видит содержимое внутренних блоков кода. Например, ссылка foo
объявлена в теле класса Computer
— она видна любым другим членам текущего класса и вложенным друг в друга блокам кода. То есть мы можем печатать переменную foo
не только в методе printFoo
, но и в методе computeSumm
или даже в цикле for
. А вот переменная (параметр метода computeSumm
) incomeArray
— видна только в рамках тела этого метода. Это значит, что класс Computer
не знает о существовании incomeArray
. И метод printFoo
, соответственно, тоже не знает о существовании переменной incomeArray
. Зато все вложенные в метод computeSumm
блоки кода (циклы, условные операторы и все, что мы туда засунем) могут обращаться к incomeArray
и использовать эту переменную. Аналогичной по видимости incomeArray
является переменная result
. А переменная number
видна только в рамках тела цикла for
.
Правила именования ссылок переменных
- имя ссылки всегда пишется с маленькой буквы;
- в верблюжем стиле и слитно, если более одного слова в имени ссылки, например,
theAdam
; - константы пишутся в теле класса и ПОЛНОСТЬЮЗАГЛАВНЫМИБУКВАМИ с подчеркиванием между словами, например:
double NUMBER_PI = 3,14
; - имена переменных могут начинаться с:
a - z
,_
,$
; - имена не могут начинаться с любого числа или точки;
- имена не могут содержать в себе
#
,:
,/
и прочих символов, но могут содержать числа (не в начале!); - имена не могут состоять только из ключевого слова синтаксиса java или литерала;
например, нельзя объявить
int new;
, но можно объявитьint newCounter;
; нельзя объявитьString true;
, но можно объявитьString someTrueString;
; - в теле одного блока кода не может быть полей с одинаковыми идентификаторами (именами)! Это значит, что если мы уже объявили в теле класса
Computer
полеString foo
, то второй раз уже не нужно писатьString foo
в теле этого класса. Но зная об области видимости мы смело можем объявить еще один разString foo
, например, в рамках тела метода. Попробуйте это прямо сейчас в коде. Экспериментируйте и внимательно изучайте вывод компилятора — он дает много подсказок об ошибках в коде.
Ссылка на перечень ключевых слов в документации. Ознакомьтесь с ними.
Ссылка на перечень разновидностей литералов. Для начала, проще запомнить эти литералы, которые нельзя использовать как готовые имена: true
, false
, null
.
Использование объектов
Объект можно создать только из ссылочного типа бесконечное количество раз (в пределах возможностей компьютера). Примитивные типы просто имеют уже конечный перечень значений, которые мы можем использовать, а создавать объекты на их основе примитивов — невозможно. Но есть "обертки" над примитивами, из которых можно создавать объекты. Обертки являются ссылочными типами данных (см. предыдущий урок).
Для начала рассмотрим пример создания объектов на основе класса Human
. Изучите код:
public class Main {
public static void main(String[] args) {
Human theAdam; // Объявим ссылку типа Human. Эта запись равносильна `Human theAdam = null;`
theAdam = new Human(); // создаем новый объект Human справа от = и присваиваем его адрес ссылке theAdam
// Сделаем все то же самое но с Евой и в одну строчку:
Human theEve = new Human(); // Кстати, Еву можно было объявить и в теле класса.
// А теперь, ради фана, создадим тут же объект класса Main:
Main main = new Main();
}
}
Экспериментируйте с этим кодом. Покрутите его в IDEA. Только ж не забудьте еще написать класс Human. ;)
использование объекта — это любая форма взаимодействия с объектом. Например, обращение к его членам или передача объекта в качестве параметра (аргумента) метода.
Для обращения к объекту, почти всегда, необходимо имя его ссылки. Например, для обращения к одному из двух объектов класса Human
нам нужно написать theAdam
или theEve
. А дальше мы можем получить доступ к членам этих объектов.
доступ к членам объектов осуществляется посредством обычной точки ".
". Точка говорит: "Раскрыть этот объект!". А за точкой нужно указать идентификатор нужного нам члена объекта.
Например, обратимся к полю name
объекта theAdam
и назначим ему нормальное имя: theAdam.name = "Adam";
. Так же можно поменять имя и для theEve
: theEve.name = "Eve";
.
Рассмотрим это чуть ниже в примерах.
Обратите внимание, что поле name
ссылается на конкретные объекты класса String
. А это значит, что мы можем получить доступ к членам и этого объекта! Например так: theAdam.name.length()
.
Рассмотрим это в коде:
public class Main {
public static void main(String[] args) {
Human theAdam = new Human(); //объявили и инициализировали ссылку theAdam
theAdam.name = "Adam"; // изменили содержимое поля name у объекта на который ссылается theAdam
int lengthOfName = theAdam.name.length(); // объявили и инициализировали переменную lengthOfName
System.out.println(lengthOfName); // вывод на экран: 4
}
}
Рассмотрим инициализацию переменной lengthOfName
:
- обращаемся к объекту переменной
theAdam
; - "раскрыли" его точкой;
- обращаемся к полю
name
, который по сути есть ссылка на объект классаString
; - "раскрыли" его точкой;
- а у любого объекта класса
String
есть методlength()
, к которому мы и обратились; он вернет число символов в текущем объекте; - записываем результат работы нашей цепочки вызовов в локальную переменную
lengthOfName
.
Для обращения к объекту не всегда необходимо использовать имя его ссылки. Например, мы можем создать объект и тут же передать его в какой-то метод, при этом не сохраняя адрес этого объекта в коде: System.out.println(new Human())
. И к его членам мы тоже можем обратиться. Рассмотрим в коде:
public class Main {
public static void main(String[] args) {
System.out.println(new Human()); // на экране будет что-то вроде: Human@1b6d3586
System.out.println("Human.name: " + new Human().name); // на экране: Human.name: Oldman
System.out.println("Human.age: " + new Human().age); // на экране: Human.age: 99
}
}
Как видно из этого примера, мы создали несколько объектов и ни одной ссылки на них. Так стоит поступать только и только тогда, когда мы точно знаем, что не станем повторно использовать один и тот же объект.
Что такое Human@1b6d3586
— это то, как JVM видит внутри себя конкретный объект класса Human
. У каждого нового объекта будет свой адрес в памяти JVM на подобии Human@1b6d3586
.
В данном случае, у каждого нового объекта класса Human
всегда поле name
будет содержать "Oldman"
, а age
— 99. Потому что изначально в классе были "зашиты" эти параметры, для удобства примеров кода. В рабочем коде предопределять значения рекомендуется только КОНСТАНТАМ.
Жизненный цикл любого класса в Java
- дизайн класса (design) — предварительная проектировка, до написания кода. Например, в UML-диаграмме;
Предварительное описание классов и его членов — является хорошей практикой!
- имплементация класса (реализация класса в редакторе) — описываем класс в виде кода в файле с расширением
*.java
;
- использование класса — это создание объекта на его основе.
Жизненный цикл instances (объектов) класса
- Создание (creation) в памяти нового instance, реального объекта, который создается на базе класса, например, так:
new Human()
. Но лучше сразу объявить ссылку и передать ей этот объект:Human human = new Human();
. - Жизнь (living)/использование instance. Любые манипуляции с объектом, например,
human.age = 12;
илиSystem.out.println(human.age);
.
Доступ к полям объекта осуществляется через точку. Точка раскрывает объект и позволят добраться к полям этого объекта.
- releasing (освобождать, отпускать, сбрасывать) — отвязываем текущий объект от ссылки, например, так
human = null;
или такhuman = new Human();
. Суть в том, что ссылкаhuman
больше не ссылается на тот же объект; - уничтожение (removing) — объект удаляется из памяти мусорщиком, если на него не ссылается ни одной ссылки в текущей программе.
Итак, у класса есть три этапа жизненного цикла: design, implementation, usage. А у объектов их четыре: creation, living, releasing, removing.
Память и объекты
Мы еще будем детально рассматривать stack и heap в грядущих уроках про методы. А пока взглянем на это в общих чертах для общего понимания. Ведь объекты эти существуют не в вакууме.
JVM разделяет свою память на stack & heap(куча). Стек служит для обеспечения работы методов. У каждой программы, у каждого потока свой стек. В стеке создаются фреймы вызываемых методов. А куча служит для хранения и доступа к объектам (одна куча на всю программу). Для простоты понимания:
- Стек удобно рассматривать как стопку монет, в которой каждая монета — это запущенный метод. Так называемый "фрейм метода". Пока не отработает верхняя монета — остальные недоступны. Работает стек по принципу LIFO. Повторюсь: детальнее рассмотрим это в другом уроке.
- Куча хоть и говорящее название, но эту область памяти JVM лучше воспринимать как библиотеку или склад, в которой каждая книжка имеет свой адрес.
Примитивы хранятся как в стеке, так и в хипе. Это зависит от места их объявления: в методе или на уровне класса. А объекты ссылочных типов хранятся только в хипе.
Рассмотрим такой пример:
class Human {
String name;
int age;
}
public class Main {
public static void main(String[] args) { // Метод main специальный. Он нужен для запуска программы и не обязан быть в каждом классе.
int someNumber = 999999; // Этот примитив хранится в фрейме метода main, а фрейм метода — в стеке.
Human human; // создали ссылку в стеке метода main. Она пока ссылается на `null`.
human = new Human(); // ссылке присвоили адрес созданного в хипе объекта.
human.age = 12; // Заполняем поле класса (использование).
human.name = "Testor"; // Заполняем поле класса (использование).
human = null; // Отвязали ссылку от объекта. Объект будет уничтожен мусорщиком (когда-нибудь), чем высвободит память, которую занимал.
}
}
someNumber
— эта ссылка находится в фрейме метода main
, а фрейм в стеке. И память под значение 999999
тоже выделена в стеке, в фрейме текущего метода.
human
— эта ссылка находится в стеке метода main
. О стеке методов, повторюсь, мы узнаем больше в следующих уроках. И указывает human
на адрес в хипе, где находится экземпляр (инстанс) класса Human
.
Сам объект human
содержит в себе еще две ссылки: name
типа String
и age
типа int
. В строчках human.age = 12;
и human.name = "Testor";
мы присваиваем этим ссылкам конкретные объекты.
В хипе это выглядит так:
- Для
int age
значение хранится в пределах объектаhuman
, а сам объект в хипе. - Для
String name
значение хранится где-то в хипе, так же как иhuman
. Может даже в соседнем регистре памяти JVM. А в пределах объектаhuman
просто записан адрес объекта в полеname
. Поскольку типString
является ссылочным типом, а не примитивом.
Рекомендация
Перед тем как пойти далее, решать тесты, поэкспериментируйте с кодом в примерах этого урока. Посмотрите что будет с программой, если поменять местами инициализацию, оставить поля без значений, вывести несколько разных объектов на экран и т.п.
Именно эксперименты над кодом позволяют компенсировать недопонимание чего-либо в программировании. И в работе это приходиться делать частенько.
Полезные ссылки
- NullPointerException
Как понять NullPointerException
Дополнительная информация про стек и хип.
Для более глубокого понимания, того как объекты занимают память, рекомендую прочитать вот эту статью.