Классы. Объектно-ориентированное программирование
Java является объектно-ориентированным языком, поэтому такие понятия как «класс» и «объект» играют в нем ключевую роль. Любую программу на Java можно представить как набор взаимодействующих между собой объектов.
Шаблоном или описанием объекта является класс , а объект представляет экземпляр этого класса. Можно еще провести следующую аналогию. У нас у всех есть некоторое представление о человеке — наличие двух рук, двух ног, головы, туловища и т.д. Есть некоторый шаблон — этот шаблон можно назвать классом. Реально же существующий человек (фактически экземпляр данного класса) является объектом этого класса.
Класс определяется с помощью ключевого слова сlass :
class Person
В данном случае класс называется Person. После названия класса идут фигурные скобки, между которыми помещается тело класса — то есть его поля и методы.
Любой объект может обладать двумя основными характеристиками: состояние — некоторые данные, которые хранит объект, и поведение — действия, которые может совершать объект.
Для хранения состояния объекта в классе применяются поля или переменные класса. Для определения поведения объекта в классе применяются методы. Например, класс Person, который представляет человека, мог бы иметь следующее определение:
class Person < String name; // имя int age; // возраст void displayInfo()< System.out.printf("Name: %s \tAge: %d\n", name, age); >>
В классе Person определены два поля: name представляет имя человека, а age — его возраст. И также определен метод displayInfo, который ничего не возвращает и просто выводит эти данные на консоль.
Теперь используем данный класс. Для этого определим следующую программу:
public class Program < public static void main(String[] args) < Person tom; >> class Person < String name; // имя int age; // возраст void displayInfo()< System.out.printf("Name: %s \tAge: %d\n", name, age); >>
Как правило, классы определяются в разных файлах. В данном случае для простоты мы определяем два класса в одном файле. Стоит отметить, что в этом случае только один класс может иметь модификатор public (в данном случае это класс Program), а сам файл кода должен называться по имени этого класса, то есть в данном случае файл должен называться Program.java.
Класс представляет новый тип, поэтому мы можем определять переменные, которые представляют данный тип. Так, здесь в методе main определена переменная tom , которая представляет класс Person. Но пока эта переменная не указывает ни на какой объект и по умолчанию она имеет значение null . По большому счету мы ее пока не можем использовать, поэтому вначале необходимо создать объект класса Person.
Конструкторы
Кроме обычных методов классы могут определять специальные методы, которые называются конструкторами . Конструкторы вызываются при создании нового объекта данного класса. Конструкторы выполняют инициализацию объекта.
Если в классе не определено ни одного конструктора, то для этого класса автоматически создается конструктор без параметров.
Выше определенный класс Person не имеет никаких конструкторов. Поэтому для него автоматически создается конструктор по умолчанию, который мы можем использовать для создания объекта Person. В частности, создадим один объект:
public class Program < public static void main(String[] args) < Person tom = new Person(); // создание объекта tom.displayInfo(); // изменяем имя и возраст tom.name = "Tom"; tom.age = 34; tom.displayInfo(); >> class Person < String name; // имя int age; // возраст void displayInfo()< System.out.printf("Name: %s \tAge: %d\n", name, age); >>
Для создания объекта Person используется выражение new Person() . Оператор new выделяет память для объекта Person. И затем вызывается конструктор по умолчанию, который не принимает никаких параметров. В итоге после выполнения данного выражения в памяти будет выделен участок, где будут храниться все данные объекта Person. А переменная tom получит ссылку на созданный объект.
Если конструктор не инициализирует значения переменных объекта, то они получают значения по умолчанию. Для переменных числовых типов это число 0, а для типа string и классов — это значение null (то есть фактически отсутствие значения).
После создания объекта мы можем обратиться к переменным объекта Person через переменную tom и установить или получить их значения, например, tom.name = «Tom» .
В итоге мы увидим на консоли:
Name: null Age: 0 Name: Tom Age: 34
Если необходимо, чтобы при создании объекта производилась какая-то логика, например, чтобы поля класса получали какие-то определенные значения, то можно определить в классе свои конструкторы. Например:
public class Program < public static void main(String[] args) < Person bob = new Person(); // вызов первого конструктора без параметров bob.displayInfo(); Person tom = new Person("Tom"); // вызов второго конструктора с одним параметром tom.displayInfo(); Person sam = new Person("Sam", 25); // вызов третьего конструктора с двумя параметрами sam.displayInfo(); >> class Person < String name; // имя int age; // возраст Person() < name = "Undefined"; age = 18; >Person(String n) < name = n; age = 18; >Person(String n, int a) < name = n; age = a; >void displayInfo() < System.out.printf("Name: %s \tAge: %d\n", name, age); >>
Теперь в классе определено три коструктора, каждый из которых принимает различное количество параметров и устанавливает значения полей класса.
Консольный вывод программы:
Name: Undefined Age: 18 Name: Tom Age: 18 Name: Sam Age: 25
Ключевое слово this
Ключевое слово this представляет ссылку на текущий экземпляр класса. Через это ключевое слово мы можем обращаться к переменным, методам объекта, а также вызывать его конструкторы. Например:
public class Program < public static void main(String[] args) < Person undef = new Person(); undef.displayInfo(); Person tom = new Person("Tom"); tom.displayInfo(); Person sam = new Person("Sam", 25); sam.displayInfo(); >> class Person < String name; // имя int age; // возраст Person() < this("Undefined", 18); >Person(String name) < this(name, 18); >Person(String name, int age) < this.name = name; this.age = age; >void displayInfo() < System.out.printf("Name: %s \tAge: %d\n", name, age); >>
В третьем конструкторе параметры называются так же, как и поля класса. И чтобы разграничить поля и параметры, применяется ключевое слово this:
this.name = name;
Так, в данном случае указываем, что значение параметра name присваивается полю name.
Кроме того, у нас три конструктора, которые выполняют идентичные действия: устанавливают поля name и age. Чтобы избежать повторов, с помощью this можно вызвать один из конструкторов класса и передать для его параметров необходимые значения:
Person(String name)
В итоге результат программы будет тот же, что и в предыдущем примере.
Инициализаторы
Кроме конструктора начальную инициализацию объекта вполне можно было проводить с помощью инициализатора объекта. Инициализатор выполняется до любого конструктора. То есть в инициализатор мы можем поместить код, общий для всех конструкторов:
public class Program < public static void main(String[] args) < Person undef = new Person(); undef.displayInfo(); Person tom = new Person("Tom"); tom.displayInfo(); >> class Person < String name; // имя int age; // возраст /*начало блока инициализатора*/ < name = "Undefined"; age = 18; >/*конец блока инициализатора*/ Person() < >Person(String name) < this.name = name; >Person(String name, int age) < this.name = name; this.age = age; >void displayInfo() < System.out.printf("Name: %s \tAge: %d\n", name, age); >>
Name: Undefined Age: 18 Name: Tom Age: 18
Классы и объекты в Java
Java — объектно-ориентированный язык, а значит, программы состоят из объектов и классов. Разбираемся, что это такое.



Артём Авдеев
Java-разработчик, преподаёт в Skillbox, осваивает машинное обучение.
Если в коде программы на объектно-ориентированном языке нужно отразить сущность каких-то объектов, используется понятие класса.
Возьмём пример из реального мира. У многих, вероятно, есть кошка, собака или хомячок, а у кого-то могут быть даже коровы, гуси, овцы. Любое из этих существ (объектов) можно охарактеризовать словами «домашнее животное» и у каждого есть свой набор атрибутов: вес, кличка, свой тип (корова, гусь, овца, собака и так далее). А ещё они, очевидно, могут есть и передвигаться.
Класс — это шаблонная конструкция, которая позволяет описать в программе объект, его свойства (атрибуты или поля класса) и поведение (методы класса).
Каждый класс имеет своё имя, чтобы в будущем к нему можно было обратиться. Чтобы создать класс на Java, необходимо написать слово class, дать ему название и поставить фигурные скобки:
class Pet
Имя класса в нашем примере — Pet.
Параметры класса
Мы можем создавать поля класса, каждое из которых имеет свой тип.
Поле класса — это переменная, которая описывает какое-либо из свойств данного класса.
Для наших домашних питомцев и полями класса будут вес, кличка и принадлежность к определённому типу (коровы, гуси, собаки и так далее). Очевидно, что здесь вес — это числовая переменная, а кличка и тип — строки символов. Тогда мы можем написать:
class Pet < int weight; String name; String type; >
Переменные weight, name и type — поля нашего класса Pet, то есть свойства, которые описывают объект этого класса. Таких полей может быть сколько угодно, каждое имеет свой тип, как обычная переменная.
Мы уже пару раз упомянули словосочетание «объект класса». Так говорят, потому что любой объект является экземпляром какого-либо класса. Здесь действует простая аналогия: класс — это как бы чертёж, который описывает объект, его устройство, а объект — реализация чертежа, его материальное воплощение.
Давайте запрограммируем первый объект класса Pet. Пусть это будет кот (type) с кличкой (name) Барсик и весом (weight) 10 (измерение в килограммах).
Сперва необходимо создать переменную типа Pet:
Pet pet;
Наш объект pet выглядит как обычная переменная, но в качестве типа указан класс Pet, и в данный момент в нём ничего нет. Инициализируем объект — воспользуемся такой синтаксической конструкцией:
Pet pet = new Pet();
Мы ставим знак равно, пишем ключевое слово new, имя нашего класса и круглые скобки. Принято говорить, что здесь мы вызываем конструктор класса Pet. Пока просто запомним это — о конструкторах и о том, как их использовать, будет рассказано в отдельной статье.
Теперь у нас появилась переменная pet типа Pet, в которой содержится объект класса Pet. Ранее в этом классе мы объявили поля, к которым можно обратиться и занести в них значения.
Чтобы получить доступ к какому-либо полю нашего класса Pet, нужно специальным образом обратиться к переменной pet — поставить точку и вызвать необходимое поле. Например, вот так:
Pet pet = new Pet(); pet.type = "Кот"; pet.name = "Барсик"; pet.weight = 10;
Теперь во всех трёх полях есть по значению, а мы можем получить их из программы, если потребуется, — например, распечатать в консоль:
Pet pet = new Pet(); pet.type = "Кот"; pet.name = "Барсик"; pet.weight = 10; System.out.println( "Домашнее животное: " + pet.type + "\nКличка: " + pet.name + "\nВес: " + pet.weight); --OUTPUT> Домашнее животное: Кот --OUTPUT> Кличка: Барсик --OUTPUT> Вес: 10
Изменить значение в любом из полей класса также несложно. Пусть наш кот Барсик слегка потолстеет — добавим к его весу 1 кг:
Pet pet = new Pet(); pet.type = "Кот"; pet.name = "Барсик"; pet.weight = 10; System.out.println( "Домашнее животное: " + pet.type + "\nКличка: " + pet.name + "\nВес: " + pet.weight); pet.weight = pet.weight + 1; System.out.println( "Домашнее животное: " + pet.type + "\nКличка: " + pet.name + "\nВес: " + pet.weight); --OUTPUT> Домашнее животное: Кот --OUTPUT> Кличка: Барсик --OUTPUT> Вес: 10 --OUTPUT> Домашнее животное: Кот --OUTPUT> Кличка: Барсик --OUTPUT> Вес: 11
Как видим, мы просто изменили вес в поле weight, а при выводе получили уже другое значение.
Методы класса
В начале статьи я упомянул, что наши домашние животные могут перемещаться и есть. В отличие от параметров вроде веса и клички, это уже не свойства объекта, а его функции. В классе эти функции обозначают как методы.
Метод класса — это блок кода, состоящий из ряда инструкций, который можно вызывать по его имени. Он обязательно содержит возвращаемый тип, название, аргументы и тело метода.
Синтаксис метода в Java:
возвращаемыйТип названиеМетода(аргументы) < //code return значение; >
Строка возвращаемыйТип показывает, какого типа данные вернёт метод. Например, если в качестве возвращаемого типа мы поставим тип String, то метод должен будет вернуть строку, а если int — целое число.
Чтобы вернуть значение из метода, используется специальное слово return. Если мы хотим, чтобы метод ничего не возвращал, то вместо возвращаемого типа нужно использовать специальное слово void.
Аргументы — то, что нужно передать в метод при его вызове. Мы можем указать сколько угодно параметров через запятую либо не указывать ни одного.
Для примера напишем простейший метод с именем sum (пока что не в нашем классе Pet), который складывает два переданных числа и возвращает их результат:
int sum(int a, int b) < int c = a + b; return c; >
Возвращаемый тип метода int, он указан перед именем sum. Далее идут два аргумента a и b, у обоих также указан тип int. Важно помнить, что возвращаемый тип и тип переменных не обязательно должны совпадать.
Аргументы метода работают как обычные переменные — за пределами метода к ним никак нельзя получить доступ. Внутри метода мы складываем значения из переменных a и b, записываем полученное значение в переменную c. После этого мы возвращаем значение переменной c — только оно доступно вне метода.
nt d = sum(1, 2); System.out.println(d); --OUTPUT> 3
Мы передали в метод sum два значения 1 и 2, а на выходе получили результат их сложения 3. Также можно создать метод, который принимает значение типа String, а возвращает длину этой строки:
int lengthOfString(String str) < return str.length(); >
В этом случае у нас возвращаемый типа int, а параметр str — типа String.
Попробуем использовать этот метод:
String t = "Any field"; int d = lengthOfString(t); System.out.println(d); --OUTPUT> 9
Также мы можем создать метод, который ничего не возвращает, а просто печатает переданное слово в консоль:
void printString(String str) < System.out.println(str); >
Либо метод, который ничего не принимает на вход, а просто печатает «Привет!»:
void sayHello() < System.out.println("Привет!"); >
В методах, которые ничего не возвращают, слово return можно опустить.
Обратите внимание, что return полностью прекращает выполнение метода:
void printIfMoreFive(int num) < if (num 5) < return; > System.out.println("Привет"); >
Теперь попробуем вызвать этот метод, передав в него число 3:
printIfMoreFive(3); --OUTPUT>
В этом случае мы ничего не увидим в консоли, так как 3 меньше 5, а значит, отработает блок if и произойдёт выход из метода с помощью слова return.
Но если передадим 6, увидим нашу надпись «Привет!»:
printIfMoreFive(6); --OUTPUT> Привет!
Методы в классах
Теперь, когда мы разобрались, что такое методы, давайте создадим два метода — eat и run — в классе Pet.
Пусть первый из них принимает на вход параметр типа int и увеличивает на это значение поле weight (сколько скушал питомец, на столько и потолстел). А после этого печатает в консоль «Я поел» и возвращает новый вес.
Второй из методов run пусть уменьшает вес на 1, но только если он больше 5, и печатает в консоль: «Я бегу». Иначе, если вес меньше или равен 5: «Я не могу бежать».
class Pet < int weight; String name; String type; int eat(int amount) < weight += amount; System.out.println("Я поел"); return weight; > void run( ) < if (weight 5) < System.out.println("Я не могу бежать"); return; > weight--; System.out.println("Я бегу"); > >
Теперь мы можем вызвать эти методы у объектов класса Pet. Чтобы это сделать, нужно обратиться к объекту, поставить точку и таким способом вызвать необходимый метод.
Pet pet = new Pet(); pet.type = "Кот"; pet.name = "Барсик"; pet.weight = 10; System.out.println( "Домашнее животное: " + pet.type + "\nКличка: " + pet.name + "\nВес: " + pet.weight); System.out.println("Кот поел и теперь весит " + pet.eat(2) + " кг"); for (int i = 0; i < 8; i++) < pet.run(); >System.out.println( "Домашнее животное: " + pet.type + "\nКличка: " + pet.name + "\nВес: " + pet.weight); --OUTPUT> Домашнее животное: Кот --OUTPUT> Кличка: Барсик --OUTPUT> Вес: 10 --OUTPUT> Я поел --OUTPUT> Кот поел и теперь весит 12 кг --OUTPUT> Я бегу --OUTPUT> Я бегу --OUTPUT> Я бегу --OUTPUT> Я бегу --OUTPUT> Я бегу --OUTPUT> Я бегу --OUTPUT> Я бегу --OUTPUT> Я не могу бежать --OUTPUT> Домашнее животное: Кот --OUTPUT> Кличка: Барсик --OUTPUT> Вес: 5
Иногда в каком-то методе требуется создать параметр, у которого имя совпадает с именем поля класса. В таких случаях, чтобы обратиться внутри метода именно к полю класса, а не к параметру нашего метода, используется ключевое слово this.
Для иллюстрации этого создадим метод, setName, который будет устанавливать переданное значение в поле name, а затем сообщать в консоль, что нашего питомца теперь зовут по-другому.
class Pet < int weight; String name; String type; int eat(int amount) < weight += amount; System.out.println("Я поел"); return weight; > void run( ) < if (weight 5) < System.out.println("Я не могу бежать"); return; > weight--; System.out.println("Я бегу"); > void setName(String name) < this.name = name; System.out.println("Теперь это домашнее животное зовут " + this.name); > >
В результате с помощью this.name мы обращаемся к полю name и заносим в него значение из параметра метода name.
Также мы можем вызывать один метод вслед за другим. Давайте сделаем так, чтобы метод eat возвращал текущее животное с помощью this.
class Pet < int weight; String name; String type; int amountOfFood; Pet eat(int amount) < weight += amount; System.out.println("Я поел"); amountOfFood += amount; return this; > void run( ) < if (weight 5) < System.out.println("Я не могу бежать"); return; > weight--; System.out.println("Я бегу"); > >
Теперь мы можем написать так:
Pet pet = new Pet(); pet.eat(1).eat(1).run();
Здесь мы дважды вызываем метод eat у одного и того же объекта класса (кота), а следом за ним вызываем метод run. И все эти вызовы делаются последовательно в одной строке, через точку.
Статические поля и методы
С помощью специального слова static мы можем создать статические поля и методы. Эти поля и методы описывают уже не объект класса, а сам класс. То есть они вызываются по имени класса, а их значение — общее для всех объектов данного класса.
Например, мы хотим посчитать, сколько еды съели все домашние животные. Введём поле amountOfAllFood типа int и добавим к нему слово static. А также введём нестатическое поле amountOfFood. Изменять данные поля мы будем в методе eat.
class Pet < int weight; String name; String type; int amountOfFood; static int amountOfAllFood; int eat(int amount) < weight += amount; System.out.println("Я поел"); amountOfFood += amount; amountOfAllFood += amount; return weight; > void run( ) < if (weight 5) < System.out.println("Я не могу бежать"); return; > weight--; System.out.println("Я бегу"); > void setName(String name) < this.name = name; System.out.println("Теперь это домашнее животное зовут " + this.name); > >
Теперь попробуем создать двух животных, и пусть каждое из них поест.
Pet cat = new Pet(); cat.type = "Кот"; cat.name = "Барсик"; cat.weight = 10; System.out.println( "Домашнее животное: " + cat.type + "\nКличка: " + cat.name + "\nВес: " + cat.weight + "\nСколько животное съело: " + cat.amountOfFood); Pet dog = new Pet(); dog.type = "Собака"; dog.name = "Тузик"; dog.weight = 15; System.out.println( "Домашнее животное: " + dog.type + "\nКличка: " + dog.name + "\nВес: " + dog.weight + "\nСколько животное съело: " + dog.amountOfFood); System.out.println("Общее количество съеденной еды: " + Pet.amountOfAllFood); cat.eat(4); dog.eat(6); System.out.println( "Домашнее животное: " + cat.type + "\nКличка: " + cat.name + "\nВес: " + cat.weight + "\nСколько животное съело: " + cat.amountOfFood); System.out.println( "Домашнее животное: " + dog.type + "\nКличка: " + dog.name + "\nВес: " + dog.weight + "\nСколько животное съело: " + dog.amountOfFood); System.out.println("Общее количество съеденной еды: " + Pet.amountOfAllFood); --OUTPUT> Домашнее животное: Кот --OUTPUT> Кличка: Барсик --OUTPUT> Вес: 10 --OUTPUT> Сколько животное съело: 0 --OUTPUT> Домашнее животное: Собака --OUTPUT> Кличка: Тузик --OUTPUT> Вес: 15 --OUTPUT> Сколько животное съело: 0 --OUTPUT> Общее количество съеденной еды: 0 --OUTPUT> Я поел --OUTPUT> Я поел --OUTPUT> Домашнее животное: Кот --OUTPUT> Кличка: Барсик --OUTPUT> Вес: 14 --OUTPUT> Сколько животное съело: 4 --OUTPUT> Домашнее животное: Собака --OUTPUT> Кличка: Тузик --OUTPUT> Вес: 21 --OUTPUT> Сколько животное съело: 6 --OUTPUT> Общее количество съеденной еды: 10
Как видите, к полю amountOfAllFood мы обращаемся уже не через объект, а по имени класса, и в этом поле хранится общее количество съеденной еды. Зато в поле amountOfFood у каждого животного — именно своё количество съеденной еды.
Мы можем обратиться к полю amountOfAllFood и через объект — результат будет тот же. Но принято обращаться именно через имя класса:
Pet cat = new Pet(); cat.type = "Кот"; cat.name = "Барсик"; cat.weight = 10; System.out.println( "Домашнее животное: " + cat.type + "\nКличка: " + cat.name + "\nВес: " + cat.weight + "\nСколько животное съело: " + cat.amountOfFood); Pet dog = new Pet(); dog.type = "Собака"; dog.name = "Тузик"; dog.weight = 15; System.out.println( "Домашнее животное: " + dog.type + "\nКличка: " + dog.name + "\nВес: " + dog.weight + "\nСколько животное съело: " + dog.amountOfFood); System.out.println("Общее количество съеденной еды: " + cat.amountOfAllFood); cat.eat(4); dog.eat(6); System.out.println( "Домашнее животное: " + cat.type + "\nКличка: " + cat.name + "\nВес: " + cat.weight + "\nСколько животное съело: " + cat.amountOfFood); System.out.println( "Домашнее животное: " + dog.type + "\nКличка: " + dog.name + "\nВес: " + dog.weight + "\nСколько животное съело: " + dog.amountOfFood); System.out.println("Общее количество съеденной еды: " + dog.amountOfAllFood);
Как вы могли заметить, в нашем примере постоянно дублируется код с выводом информации об объекте. Давайте вынесем его в отдельный метод в классе Pet:
class Pet < int weight; String name; String type; int amountOfFood; static int amountOfAllFood; int eat(int amount) < weight += amount; System.out.println("Я поел"); amountOfFood += amount; amountOfAllFood += amount; return weight; > void run( ) < if (weight 5) < System.out.println("Я не могу бежать"); return; > weight--; System.out.println("Я бегу"); > void setName(String name) < this.name = name; System.out.println("Теперь это домашнее животное зовут " + this.name); > void printInfo( ) < System.out.println( "Домашнее животное: " + type + "\nКличка: " + name + "\nВес: " + weight + "\nСколько животное съело: " + amountOfFood); > >
Теперь нам достаточно лишь обратиться к методу printInfo через объект, о котором мы хотим получить информацию.
Pet cat = new Pet(); cat.type = "Кот"; cat.name = "Барсик"; cat.weight = 10; cat.printInfo();
Но у нас есть ещё строка с выводом общего количества еды. Можем ли мы поместить её в метод printInfo? Да, оказывается, можем:
class Pet < int weight; String name; String type; int amountOfFood; static int amountOfAllFood; int eat(int amount) < weight += amount; System.out.println("Я поел"); amountOfFood += amount; amountOfAllFood += amount; return weight; > void run( ) < if (weight 5) < System.out.println("Я не могу бежать"); return; > weight--; System.out.println("Я бегу"); > void setName(String name) < this.name = name; System.out.println("Теперь это домашнее животное зовут " + this.name); > void printInfo( ) < System.out.println( "Домашнее животное: " + type + "\nКличка: " + name + "\nВес: " + weight + "\nСколько животное съело: " + amountOfFood); System.out.println("Общее количество съеденной еды: " + amountOfAllFood); > >
Всё хорошо, но теперь при каждом вызове printInfo у нас будет печататься информация об общем количестве еды. Чтобы этого избежать, лучше вынести строку в отдельный статический метод:
class Pet < int weight; String name; String type; int amountOfFood; static int amountOfAllFood; int eat(int amount) < weight += amount; System.out.println("Я поел"); amountOfFood += amount; amountOfAllFood += amount; return weight; > void run( ) < if (weight 5) < System.out.println("Я не могу бежать"); return; > weight--; System.out.println("Я бегу"); > void setName(String name) < this.name = name; System.out.println("Теперь это домашнее животное зовут " + this.name); > void printInfo( ) < System.out.println( "Домашнее животное: " + type + "\nКличка: " + name + "\nВес: " + weight + "\nСколько животное съело: " + amountOfFood); > static void printStaticInfo( ) < System.out.println("Общее количество съеденной еды: " + amountOfAllFood); > >
У статического метода printStaticInfo также нет никаких отличий от обычного метода, но он относится к классу, а не к объекту данного класса. Вызываем его через обращение к классу:
Pet cat = new Pet(); cat.type = "Кот"; cat.name = "Барсик"; cat.weight = 10; cat.printInfo(); Pet dog = new Pet(); dog.type = "Собака"; dog.name = "Тузик"; dog.weight = 15; dog.printInfo(); Pet.printStaticInfo(); cat.eat(4); dog.eat(6); cat.printInfo(); dog.printInfo(); Pet.printStaticInfo();
Важно заметить, что из метода printStaticInfo мы можем обратиться только к статическим полям. Это происходит потому, что нестатические поля существуют в рамках конкретного объекта класса, а статический метод — в рамках всего класса.
Но можно добавить параметр типа Pet в данный метод — тогда у этого параметра мы будем вызывать необходимые поля. Например, так:
static void printStaticInfo(Pet pet) < System.out.println( "Домашнее животное: " + pet.type + "\nКличка: " + pet.name + "\nВес: " + pet.weight + "\nСколько животное съело: " + pet.amountOfFood); System.out.println("Общее количество съеденной еды: " + amountOfAllFood); >
Pet cat = new Pet(); cat.type = "Кот"; cat.name = "Барсик"; cat.weight = 10; Pet dog = new Pet(); dog.type = "Собака"; dog.name = "Тузик"; dog.weight = 15; Pet.printStaticInfo(cat); Pet.printStaticInfo(dog); cat.eat(4); dog.eat(6); Pet.printStaticInfo(cat); Pet.printStaticInfo(dog);
Подытожим
- что такое классы, из чего они состоят;
- как создавать объекты класса;
- что такое методы, какая у них сигнатура и как их вызывать;
- как использовать статические поля и методы классов.
Важные примечания
Работать с классами и объектами в языке Java несложно, но есть несколько важных правил-примечаний, на которые нужно обратить внимание:
- Классы создаются в файлах с расширением .java. Главный класс должен называться так же, как имя файла. Например, в файле Main.java — класс Main.
- Разные классы в одном проекте могут иметь одинаковые имена, для этого им надо располагаться в разных пакетах (package).
- Нельзя создать метод или объявить переменную за пределами классов.
- Каждый раз мы начинаем программу со статического метода main, который имеет возвращаемый тип int, а в параметрах у него указана переменная args типа String[] (массив строк).
- Статические поля нужно использовать правильно. Например, можно с помощью статического поля сделать счётчик количества созданных объектов — он описывает именно класс, в котором расположен, а не сами объекты данного класса. Но нет смысла делать статическим поле, описывающее объект, если не можете придумать, как использовать информацию из этого поля в другом классе.
Классы и объекты
Java — это объектно-ориентированный язык, поэтому код в ваших программах будет состоять из объектов и классов.
Классы
Java позволяет создавать классы, которые представляют объекты из реального мира. Например, можно создать класс Car (автомобиль) или Animal (животное) и задать им различные свойства. Для класса Car логично создать такие свойства как двери, колёса, лобовое стекло и т.д. Имея класс Car, можно создать новые классы Легковушки, Грузовики, Автобусы, которые будут иметь все свойства класса Car, а также свои собственные свойства. У класса Animal соответственно можно задать свойства Лапы, Хвост, а затем создать наш любимый класс Cat, у которого будет ещё дополнительное свойство Усы. Иными словами, классы могут наследовать свойства от других классов. Родительский класс называется суперклассом. Внутри классов могут быть объявлены поля и методы.
Для объявления класса служит ключевое слово class. Вспомним стандартную строчку кода из Android-проекта:
public class MainActivity extends Activity < // код внутри класса >
Упрощённая общая форма для класса может иметь следующий вид:
class ИмяКласса < тип переменная_экземпляра1; тип имяМетода(список параметров)< // тело метода >>
В Java принято начинать имена класса с большой буквы. В классе могут быть несколько переменных и методов. Переменные, определённые внутри класса (не метода), называются переменными экземпляра или полями (fields). Код пишется внутри класса. Методы и переменные внутри класса являются членами класса.
Объекты
Новый объект (или экземпляр) создаётся из существующего класса при помощи ключевого слова new:
Cat barsik = new Cat(); // создали кота из класса Cat
В большинстве случаев вы будете использовать такой способ. Пусть вас не удивляет, что приходится дважды использовать слово Cat, оно имеет разный смысл.
Слева от оператора присваивания = определяется имя переменной и его тип Cat. В правой части выражения происходит выделение памяти для нового экземпляра класса Cat и инициализируется экземпляр. Оператор присваивания присваивает переменной ссылку на только что созданный объект. Имена объектов не нужно начинать с большой буквы, как у класса. Так вы будете различать, где класс, а где экземпляр класса. Если имя экземпляра класса состоит из нескольких слов, то используется верблюжья нотация, когда все первые буквы слов, кроме первой, пишутся с большой — superBlackCat.
Если вы помните, при объявлении примитивных типов мы указывали нужный тип в самом начале.
int catAge;
Поэтому код Cat barsik также определяет его тип. Он не всегда может совпадать с именем класса.
Pet barsik = new Cat();
В этом примере используется тип класса домашних любимцев Pet, а обращаемся к классу котов Cat.
Простой пример создания класса Box (коробка для кота):
class Box < int width; // ширина коробки int height; // высота коробки int depth; // глубина коробки >
При таком варианте Java автоматически присвоит переменным значения по умолчанию. Например, для int это будет значение 0. Но не всегда значения по умолчанию подойдут в вашем классе. Если вы создали переменную для описания количества лап у кота, то логично сразу присвоить значение 4. Поэтому считается хорошей практикой сразу присваивать нужные значения полям класса, не полагаясь на систему.
Вам нужно создать отдельный файл Box.java, в который следует вставить код, описанный выше. О том, как создавать новый файл для класса я не буду здесь расписывать.
Сам класс — это просто шаблон, заготовка. Чтобы ваше приложение могло использовать данный шаблон, нужно создать на его основе объект при помощи ключевого слова new:
Box catBox = new Box; // создали реальный объект с именем catBox на основе шаблона Box
Красивая получилась коробочка.
Объект catBox, объявленный в коде вашей программы, сразу займёт часть памяти на устройстве. При этом объект будет содержать собственные копии переменных экземпляра width, height, depth. Для доступа к этим переменным используется точка (.). Если мы хотим присвоить значение переменной width, то после создания объекта класса можете написать код:
catBox.width = 400; // ширина коробки для кота 400 миллиметров
Если мы хотим вычислить объём коробки, то нужно перемножить все значения размеров коробки:
Box catBox = new Box(); catBox.width = 400; catBox.height = 200; catBox.depth = 250; int volume = catBox.width * catBox.height * catBox.depth; mInfoTextView.setText("Объём коробки: " + volume);
Каждый объект содержит собственные копии переменных экземпляра. Вы можете создать несколько объектов на основе класса Box и присваивать разные значения для размеров коробки. При этом изменения переменных экземпляра одного объекта никак не влияют на переменные экземпляра другого объекта. Давайте объявим два объекта класса Box:
Box bigBox = new Box(); // большая коробка Box smallBox = new Box(); // маленькая коробка int volume; // присвоим значения переменным для большой коробки bigBox.width = 400; bigBox.height = 200; bigBox.depth = 250; // присвоим значения переменным для маленькой коробки smallBox.width = 200; smallBox.height = 100; smallBox.depth = 150; // вычисляем объём первой коробки volume = bigBox.width * bigBox.height * bigBox.depth; mInfoTextView.setText("Объём большой коробки: " + volume + "\n"); // вычисляем объём маленькой коробки volume = smallBox.width * smallBox.height * smallBox.depth; mInfoTextView.append("Объём маленькой коробки: " + volume);
Когда мы используем конструкцию типа Box bigBox = new Box();, то в одной строке выполняем сразу два действия — объявляем переменную типа класса и резервируем память под объект. Можно разбить конструкцию на отдельные части:
Box bigBox; // объявляем ссылку на объект bigBox = new Box(); // резервируем память для объекта Box
Обычно такую конструкцию из двух строк кода не используют на практике, если нет особых причин.
Когда мы используем ключевое слово new и указываем имя класса, то после имени ставим круглые скобки, которые указывают на конструктор класса. О них поговорим позже.
Ключевое слово final
Поле может быть объявлено как final (финальное). Это позволяет предотвратить изменение содержимого переменной, по сути, это становится константой. Финальное поле должно быть инициализировано во время его первого объявления.
final int FILE_OPEN = 1;
Теперь можно пользоваться переменной FILE_OPEN так, как если бы она была константой, без риска изменения их значений. Принято записывать имена заглавными буквами.
Кроме полей, final можно использовать для параметров метода (препятствует изменению в пределах метода) и у локальных переменных (препятствует присвоению ей значения более одного раза).
Также слово final можно применять к методам, чтобы предотвратить его переопределение.
class Cat < final void meow() < System.out.println("Мяу"); >> class Kittent extends Cat < // Этот метод создать не получится void meow() < System.out.println("Да хоть гав-гав, всё равно не заведётся"); >>
Ещё один вариант использования ключевого слова final — предотвращение наследования класса. При этом неявно всего методы класса также становятся финальными. Поэтому нельзя одновременно объявить класс абстрактным и финальным, поскольку абстрактный класс является лишь шаблоном и только его подклассы реализуют методы.
final class Tail < // . >// Следующий класс недопустим class BigTail extends Tail < // Ошибка! Класс Хвост нельзя переопределять. >
Ключевое слово instanceof — Проверка принадлежности к классу
Иногда требуется проверить, к какому классу принадлежит объект. Это можно сделать при помощи ключевого слова instanceof. Это булев оператор, и выражение foo instanceof Foo истинно, если объект foo принадлежит классу Foo или его наследнику, или реализует интерфейс Foo (или, в общем виде, наследует класс, который реализует интерфейс, который наследует Foo).
Возьмём пример с рыбками, которые знакомы котам не понаслышке. Пусть у нас есть родительский класс Fish и у него есть унаследованные подклассы SaltwaterFish и FreshwaterFish. Мы можем протестировать, относится ли заданный объект к классу или подклассу по имени
SaltwaterFish nemo = new SaltwaterFish(); if(nemo instanceof Fish) < // рыбка Немо относится к классу Fish // это может быть класс Fish (родительский класс) или подкласс типа // SaltwaterFish или FreshwaterFish. if(nemo instanceof SaltwaterFish) < // Немо - это морская рыбка! >>
Данная проверка удобна во многих случаях. В Android очень много классов, которые происходят от класса View — TextView, CheckBox, Button, имеющие свои собственные наборы свойств. И если имеется метод с параметром View, то при помощи instanceof можно разделить логику кода:
void checkforTextView(View view) < if(view instanceof TextView) < // Код для элемента TextView >else < // Для других элементов View >>
import — Импорт класса
Оператор import сообщает компилятору Java, где найти классы, на которые ссылается код. Любой сложный объект использует другие объекты для выполнения тех или иных функций, и оператор импорта позволяет сообщить о них компилятору Java. Оператор импорта обычно выглядит так:
import ClassNameToImport;
За ключевым словом import следуют класс, который нужно импортировать, и точка с запятой. Имя класса должно быть полным, то есть включать свой пакет. Чтобы импортировать все классы из пакета, после имени пакета можно поместить .*.
В Android Studio импорт класса происходит автоматически при наборе кода. Также это срабатывает и при вставке кода. Если имена классов совпадают, то студия может запросить помощь. Тогда вам нужно вручную указать нужное полное имя класса.
Импорт позволяет избежать долгого набора имени класса. Без импорта нам пришлось бы писать все классы в коде программы полностью.
ru.alexanderklimov.MyClass.Cat.sayMeow(); java.lang.System.out.println("Мяу");
Статический импорт
Существует ещё статический импорт, применяемый для импорта статических членов класса или интерфейса. Это позволяет сократить количество кода. Например, есть статические методы Math.pow(), Math.sqrt(). Для вычислений сложных формул с использованием математических методов, код становится перегружен. К примеру, вычислим гипотенузу.
hypot = Math.sqrt(Math.pow(side1, 2) + Math.pow(side2, 2));
В данном случае без указания класса не обойтись, так как методы статические. Чтобы не набирать имена классов, их можно импортировать следующим образом:
import static java.lang.Math.sqrt; import static java.lang.Math.pow; . hypot = sqrt(pow(side1, 2) + pow(side2, 2));
После импорта уже нет необходимости указывать имя класса.
Второй допустимый вариант, позволяющий сделать видимыми все статические методы класса:
import static java.lang.Math.*;
В этом случае вам не нужно импортировать отдельные методы. Но такой подход в Android не рекомендуется, так как требует больше памяти.
Класс Class
На первый взгляд, класс Class звучит как «масло масляное». Тем не менее, класс с таким именем существует и он очень полезен.
Программно получить имя класса
Иногда из программы нужно получить имя используемого класса. Для этого есть специальные методы getClass().getName() и другие родственные методы. Допустим, нам нужно узнать имя класса кнопки, на которую мы нажимаем в программе.
public void onClick(View view) < String className = view.getClass().getName(); String simpleName = view.getClass().getSimpleName(); String canonicalName = view.getClass().getCanonicalName(); if (canonicalName == null) < canonicalName = "null"; >String s = "Имя класса: " + className + "\n" + "SimpleName: " + simpleName + "\n" + "CanonicalName: " + canonicalName + "\n"; mInfoTextView.setText(s); >
Имя класса: android.support.v7.widget.AppCompatButton SimpleName: AppCompatButton CanonicalName: android.support.v7.widget.AppCompatButton
getSimpleName() возвращает только имя класса без пакета, другие методы вернут полное название.
Если нужно узнать имя класса активности, то достаточно кода:
// подставьте имя вашей активности String className = MainActivity.class.getName();
Если вам известно имя класса, то можете получить сам класс:
try < // получим объект Class ClassmyClass = Class.forName("ru.alexanderklimov.test.MainActivity"); mInfoTextView.setText(myClass.getName()); // выводим в TextView Intent intent = new Intent(this, myClass); startActivity(intent); > catch (ClassNotFoundException e) < // TODO Auto-generated catch block e.printStackTrace(); >
Метод getSuperclass() возвращает имя суперкласса. Остальные несколько десятков методов не столь популярны.
Создание объектов в Java
Создание объектов в Java и связывание их с переменными чаще всего выглядит так:
Second second = new Second();
В правой части с помощью ключевого слова new создается объект от класса Second. Созданный объект присваивается переменной second, чей тип указывается перед именем переменной. В случае наличия наследственных связей тип переменной может не совпадать с типом создаваемого объекта.
public class Main public static void main(String[] args) Second second = new Second(); > >
class Second Second() System.out.println("Object created"); > >
В классе Second есть только один метод – это конструктор. В данном случае, когда создается объект, на экран будет выведено сообщение.
Когда мы используем библиотечные классы Java, например String, то создаем объекты также. Выражение
String str = "Hello";
String str = new String("Hello");
Переменные можно сначала объявлять, а потом присваивать им значения:
String str; str = "Hello";
В Java переменные объектных типов являются ссылочными, то есть хранят не сам объект, а ссылку на него. Отсюда следует, что 1) методы классов меняют переданные им в качестве аргументов объекты, и 2) на один объект может ссылаться множество переменных.
Переменной-ссылке можно присвоить значение null. Такая переменная начинает ссылаться на адрес 0. Виртуальная машина Java воспринимает указание на этот адрес как отсутствие объекта.
Обнуление ссылочной переменной ведет к уничтожению хранимого в памяти объекта только в том случае, если нет других ссылок-переменных на него.
В Java не все объекты создаются с помощью классов. Существуют примитивные типы: пять целочисленных (byte, char, short, int, long), два вещественных (float и double) и булев тип (boolean). При объявлении переменных этих типов используется маленькая буква в названии типа. Например:
int number; double pi = 3.14;
Если переменная примитивного типа передается в функцию в качестве аргумента, то никакая ссылка на объект туда не передается, происходит копирование значения переменной. Другими словами, действия внутри метода не изменяют значения переданных переменных.
В Java все объекты преобразуются к строковому типу при выводе на экран. При этом для объектов неявно вызывается метод toString родительского класса Object, от которого наследуются все классы. В случае необходимости метод можно вызывать явно. Следующие две строки равносильны:
System.out.println(second); System.out.println(second.toString());
Метод toString() класса Object возвращает строку, состоящую из имени класса и шестнадцатеричного адреса объекта в памяти. То есть по умолчанию вывод будет примерно такой:
Second@1540e19d
Понятно, что можно переопределить метода toString() в дочернем классе:
@Override public String toString() return "It's the second object"; >
В случае примитивных типов явное преобразование к строке происходит через вызов метода аналогичного типу класса:
int b = 100; String s = Integer.toString(b); // String s = b.toString(); - ошибка
В Java есть классы Integer, Double и др. Однако использование примитивных типов выгодней с точки зрения экономии памяти.
X Скрыть Наверх
Программирование на Java. Курс