Код
Java
#База знаний

Классы и объекты в Java

Java — объектно-ориентированный язык, а значит, программы состоят из объектов и классов. Разбираемся, что это такое.

Если в коде программы на объектно-ориентированном языке нужно отразить сущность каких-то объектов, используется понятие класса.

Возьмём пример из реального мира. У многих, вероятно, есть кошка, собака или хомячок, а у кого-то могут быть даже коровы, гуси, овцы. Любое из этих существ (объектов) можно охарактеризовать словами «домашнее животное» и у каждого есть свой набор атрибутов: вес, кличка, свой тип (корова, гусь, овца, собака и так далее). А ещё они, очевидно, могут есть и передвигаться.

Класс — это шаблонная конструкция, которая позволяет описать в программе объект, его свойства (атрибуты или поля класса) и поведение (методы класса).

Каждый класс имеет своё имя, чтобы в будущем к нему можно было обратиться. Чтобы создать класс на 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 несложно, но есть несколько важных правил-примечаний, на которые нужно обратить внимание:

  1. Классы создаются в файлах с расширением .java. Главный класс должен называться так же, как имя файла. Например, в файле Main.java — класс Main.
  2. Разные классы в одном проекте могут иметь одинаковые имена, для этого им надо располагаться в разных пакетах (package).
  3. Нельзя создать метод или объявить переменную за пределами классов.
  4. Каждый раз мы начинаем программу со статического метода main, который имеет возвращаемый тип int, а в параметрах у него указана переменная args типа String[] (массив строк).
  5. Статические поля нужно использовать правильно. Например, можно с помощью статического поля сделать счётчик количества созданных объектов — он описывает именно класс, в котором расположен, а не сами объекты данного класса. Но нет смысла делать статическим поле, описывающее объект, если не можете придумать, как использовать информацию из этого поля в другом классе.

Изучайте IT на практике — бесплатно

Курсы за 2990 0 р.

Я не знаю, с чего начать
Научитесь: Профессия Java-разработчик Узнать больше
Понравилась статья?
Да

Пользуясь нашим сайтом, вы соглашаетесь с тем, что мы используем cookies 🍪

Ссылка скопирована