Передача в метод ссылочных типов и типов значений

Рассмотрим передачу ссылочного типа в метод и передачу типа значения в метод.

Для начала передадим в метод тип значения.

public class LinkValInMethodExample { // Определим метод, в который передается числовой параметр, // к которому будет прибавляться единица. static void doStuff(int y) { y = y + 1; } public static void main(String[] args) { // Сначала передадим в метод тип значения int x = 5; doStuff(x); // Когда мы передаем тип значения в метод doStuff, // мы передаем не саму переменную x, а ее КОПИЮ. System.out.println(x); // Поэтому x все еще равно 5, а не 6, // так как в методе doStuff происходили действия не с самой x, // а с ее копией. } }

Вывод:

Теперь передадим в метод объект ссылочного типа и изменим его в этом методе.

//Создадим простейший класс для демонстрации //передачи в метод ссылки на объект. class Animal { public String eats; public int noOfLegs; } public class LinkValInMethodExample { //Определим метод, в который передаётся объект //и в нем он меняется. (Точнее передаётся ссылка на объект). static void doStuff(Animal Y) { Y.eats = “grass”; Y.noOfLegs = 4; } public static void main(String[] args) { //Теперь же когда мы передаем ссылочный тип //мы передаем в метод ссылку на объект, который //мы создали строчкой кода ниже и через эту ссылку //метод работает с этим созданным объектом Animal x = new Animal(); x.eats = “meat”; x.noOfLegs = 2; doStuff(x); // Передаем копию ссылки (правильно говорить: // мы передаем копию ссылки, то есть мы передаем не саму ссылку x, // а ее копию. То есть когда мы в метод передаем x, то создается // новая ссылка, которая ссылается на тот же объект, что и x, // и через нее уже работает метод с данным объектом. // На созданный объект, который содержит // два поля со значениями “meat” и 2, // метод doStuff использует её и может изменять объект // прямо в методе. System.out.println(x.eats); System.out.println(x.noOfLegs); // Как можно увидеть в консоли: x.eats = “grass”, а x.noOfLegs = 4, // так как в методе происходила // работа с объектом через копию ссылки x. } }

Вывод:

Ссылочные типы и типы значений в Java

В Java есть два типа данныхссылочные типы и типы значений.

int, char ,float и т.д это типы значений. Это простые типы. Это просто число либо символ, либо дробное число и т.д.  Это не классы (ссылочные типы), которые могут состоять из многих полей типов значений:

public class SomeClass { int num; char letter; float floatingNum; }

То есть классы могут содержать простые типы типа int,char,float. То есть они сложны так как классы состоят из простых типов значений или также могут содержать в себе другие сложные типы.

Так вот почему же классы ссылочные, а типы значений нет?

Дело в том, что когда мы создаем объект на основе класса с помощью слова new, то в памяти выделяется под него участок и там хранятся значения полей этого объекта.

То есть созданный объект имеет свое состояние в этой области памяти и на эту область памяти можно ссылаться. Можно сказать, что объект это и есть эта область памяти, поэтому можно говорить, что происходит ссылка на объект.

Типы значений же это простые типы они хранят само значение в себе, а не ссылку на участок памяти в котором храниться много разных значений.

Подробнее о ссылках описано в примере далее.

class SomeClass { int num; char letter; double floatingNum; SomeClass(int num, char letter, double floatingNum) { // Не по теме данного урока! Но это важно знать. this.num = num; // this это ссылка на текущий объект класса. // this на русском это ЭТОТ. То есть ссылка на ЭТОТ // объект класса. То есть если например мы создаем объект // на основе класса SomeClass, то в конструкторе // используется поле num именно ЭТОГО объекта, имеется // ввиду именно того, который мы создаем. // Обычно this используется чтобы не происходило // путаницы с параметрами, так как, как видим, поля класса // и параметры имеют одинаковые имена. // То есть присваиваем полю num объекта класса SomeClass, // значение параметра num переданного в конструктор. this.letter = letter; this.floatingNum = floatingNum; // можно было бы переименовать поля класса или параметры // тогда можно было бы обойтись без this. Но так лучше // так как не нужно придумывать разные имена для полей класса // и параметров. } } public class Types { public static void main(String[] args) { // Продемонстрируем пример, чтобы понять что значит ссылаться. // Создаем два объекта класса SomeClass // Под каждый объект с помощью new // выделяется отдельный участок памяти, // в котором будет храниться его текущее состояние, // то есть текущие значения его полей. // То есть сейчас в одном участке памяти // хранятся значения первого объекта 9, ‘f’, 1.45, // в другом участке второго объекта 2, ‘k’, 6.11. SomeClass someClass1 = new SomeClass(9, ‘f’, 1.45); SomeClass someClass2 = new SomeClass(2, ‘k’, 6.11); // someClass1 это имя, по которому мы можем // обращаться к первому объекту, то есть к первому // участку памяти, то есть someClass1 это ссылка на // первый объект, а someClass2 это имя, по которому // мы можем обращаться ко второму объекту, // то есть ко второму участку памяти, то есть someClass2 // это ссылка на второй объект. // Присваиваем первый объект второму. someClass1 = someClass2; // Что же произойдет? Большинство сразу подумает, что // в участке памяти первого объекта просто поменяются // значения полей, хранящиеся в нем, на те, которые // находятся в участке памяти второго объекта. // НО ЭТО НЕ ТАК. На самом деле имя, по которому // можно было обращаться к первому объекту, // просто перестанет ссылаться на свой участок памяти. // То есть участок памяти первого объекта // начнет ссылаться на участок памяти второго объекта. // Теперь someClass1 и someClass2 — это // один и тот же объект в памяти (второй объект). // Просто манипулировать этим объектом в памяти // теперь можно через разные имена. // И если теперь мы, например, будем менять значение // какого-то поля через имя someClass1: someClass1.num = 10; // К измененному полю мы также сможем // обратиться через второе имя объекта. System.out.println(someClass2.num); // увидим в консоли 10 // Поскольку мы изменили объект, у которого по сути есть // два имени someClass1 и someClass2, // имя не важно, мы можем через любое из них // с ним работать. // Думаю, теперь понятно, почему они ссылочные. } }

Вывод:

Теперь то же самое только с типами значений.

public class Types { public static void main(String[] args) { //Типы значений же это простые типы //они хранят само значение в себе, //а не ссылку на участок памяти. //По аналогии с тем что сверху только с простыми типами. int A=4, B=12; A = B; A = 6; System.out.println(B); //B все еще 12 в отличии от A, которая содержит 6 //так как сначала мы просто изменили значение в A //на значение которое в B, //а потом изменили значение в A на значение 6. //На значение в B действия с A никак не влияют. } }

Вывод:

Статические классы в Java

Статический класс это всегда внутренний класс. Статического не внутреннего класса не бывает.

Обычный внутренний класс и статический внутренний класс часто сравнивают.

Главное отличие статического от обычного внутреннего в том что не нужно создавать экземпляр внешнего класса для обращения к его элементам, но обращаться внутренний статический класс может только к статическим элементам внешнего класса.

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

Он работает лишь со статическими элементами внешнего класса то есть с элементами, которые общие для всех обьектов внешнего класса, и поэтому связь не сильная, поскольку статические элементы не зависят от обьекта класса, то есть необязательно создавать обьект внешнего класса для использования обьектом внутреннего статического класса статических членов обьекта внешнего класса.

Например есть класс Торт Наолеон.

У него есть внутри какая-то статическая переменная, например колличество масла, и внутренний статический класс Рецепт Торта Наполеон.

В отличие от мотора, который выполняет важные функции в машине и без которого машина не поедет и от которого нет смысла без машины для которой он существует рецепт торта наполеон очевидно может существовать отдельно от самого торта наполеон, но тем не менее он всё равно имеет смысловую связь с тортами наполеон, ведь торты наполеон готовяться по этому рецепту. Поэтому Рецепт торта наполеон есть смысл описать как внутренний статический класс класса Торт Наполеон.

И соответственно обьект внутреннего статического класса Рецепт торта наполеон будет иметь лишь общую связь со всеми обьектами класса торт наполеон, поскольку будет иметь связь только со статическими (общими) элементами класса торт наполеон.

Поясним на примере:

class CakeNapoleon { // класс торт Наполеон private static int butter = 600; public void printRecipe() { // Как видим ниже, внешний класс // не должен создавать объект внутреннего класса. // Можно обращаться к методам внутреннего // напрямую через внутренний класс RecipeOfNapoleon. RecipeOfNapoleon.showRecipe(200); } // Внутренний статический класс рецепт Наполеона static class RecipeOfNapoleon { public static void showRecipe(int Salt) { // Используется статическое поле butter // внешнего класса. // К нестатическим членам внешнего класса доступа нет. System.out.println(“Recipe of Napoleon – ” + Salt + ” of salt ” + “butter” of butter and so on”); } } } class StaticClass { public static void main(String[] args) { // Создаётся экземпляр // внутреннего статического класса RecipeOfNapoleon // без создания объекта внешнего класса CakeNapoleon. CakeNapoleon.RecipeOfNapoleon recipeOfNapol = new CakeNapoleon.RecipeOfNapoleon(); recipeOfNapol.showRecipe(100); } }

Вывод:

Внутренние классы в Java

Внутренний класскласс внутри другого класса.

Некоторая доп. функциональность, которая относиться только к внешнему классу.

Search Icon

Класс внутри другого класса имеет смысл создавать когда внутренний и внешний класс имеют сильную смысловую связь.

Допустим, мы создаём класс Car (машина), и нам нужно описать двигатель этой машины. Логично, что двигатель тесно связан с машиной и не имеет смысла как самостоятельный объект без неё. В таком случае можно определить класс двигателя как внутренний класс внутри класса Car.

Внутренний класс имеет доступ ко всем членам внешнего класса, включая приватные поля. Это ещё больше подчёркивает их взаимосвязь: объект двигателя (внутреннего класса) не существует без объекта машины (внешнего класса).

Соответственно, сначала создаётся объект внешнего класса — машины, а уже на его основе создаётся объект внутреннего класса — двигателя. Это отражает логическую зависимость и облегчает доступ двигателя к данным машины.

Поясним на примере:

class Car { private int carVarible = 100; void print() { //Если во внешнем классе нам нужно //пользоваться внутренним классом //то нужно создать объект внутреннего класса. Motor motor = new Motor(); motor.MotorMethod(); } //Внутренний класс. class Motor { public void MotorMethod() { //Внутренний же класс может обращаться к //элементам внешнего без создания экземпляра System.out.println(carVarible); } } } class InnerClass { public static void main(String[] args) { Car car = new Car(); //Создание внутреннего класса. //Как видим его нельзя создать //без объекта внешнего. Car.Motor motor = car.new Motor(); motor.MotorMethod(); } }

Вывод:

Статические поля в Java

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

Такие поля и методы можно создать и они называются статическими.

Объявляются с помощью ключевого слова static.

Поясним на примере:

import java.io.*; public class StaticVarAndMethod { //статическая переменная static int staticVar; //обычная переменная int NotStatic; public static void main(String args[]) { //создадим пару объектов класса StaticVarAndMethod StaticVarAndMethod staticVAndM = new StaticVarAndMethod(); StaticVarAndMethod staticVAndM1 = new StaticVarAndMethod(); //Переменная staticVar общая //для всех объектов StaticVarAndMethod, //поэтому изменение этой переменной //в одном объекте приведет к её изменению в другом. staticVAndM.staticVar = 100; staticVAndM1.staticVar = 85; //В объекте staticVAndM поле staticVar содержит 85, //поэтому нижняя строчка выведет 85. System.out.println(staticVAndM.staticVar); //В объекте staticVAndM1 поле staticVar тоже содержит 85, //поэтому нижняя строчка выведет 85. System.out.println(staticVAndM1.staticVar); //То есть последнее изменение staticVar повлияло //на staticVar в обоих объектах, так как staticVar //это одно и тоже поле, но оно доступно обоим объектам. //Переменная NotStatic не является общей //для всех объектов StaticVarAndMethod, //поэтому изменение этой переменной //в одном объекте не приведет к её изменению в другом. staticVAndM.NotStatic = 100; staticVAndM1.NotStatic = 87; //В объекте staticVAndM поле NotStatic содержит 100, //поэтому нижняя строчка выведет 100. System.out.println(staticVAndM.NotStatic); //В объекте staticVAndM1 поле NotStatic уже содержит 87, //поэтому нижняя строчка выведет 87. System.out.println(staticVAndM1.NotStatic); //Это не статическое поле, поэтому //у каждого объекта свое личное поле NotStatic. } }

Вывод:


Также бывают статические методы.

Статическими методами понятное дело выполняются какие-то действия, которые затронут все объекты класса и поэтому логично что статический метод может обращаться только к статическим полям класса.

Search Icon

Статический метод не зависит от объекта, а значит его можно вызвать без создания объекта класса в котором этот метод находиться.

Поясним на примере:

public class StaticVarAndMethod { // Статическая переменная static int staticVar; int NotStatic; // Статический метод public static void deposit(int amount) throws ArrayIndexOutOfBoundsException { // Обращаемся к статическому полю staticVar staticVar += amount; } public static void main(String args[]) { StaticVarAndMethod staticVAndM = new StaticVarAndMethod(); StaticVarAndMethod staticVAndM1 = new StaticVarAndMethod(); // Как видим ниже, вызываем deposit через класс, // а не через какой-либо объект. StaticVarAndMethod.deposit(3); // Добавим 3 к staticVar System.out.println(staticVAndM.staticVar); System.out.println(staticVAndM1.staticVar); // В обоих объектах staticVar теперь 3 } }

Вывод:

Интерфейсы в Java

Интерфейсопределяет поведение классов.

В нем могут быть только поля и абстрактные методы (методы без реализаций, как мы помним).

Интерфейс создается с использованием ключевого слова interface.

Класс может реализовать интерфейс используя ключевое слово implements.

Search Icon

Также класс реализующий интерфейс обязан реализовать все его абстрактные методы.        

В интерфейсе не может  быть не абстрактных методов. То есть интерфейс – это каркас и классы создаются по этому каркасу. То есть интерфейс говорит классу – эй ты, класс, раз уж ты решил меня заимплементировать, в тебе обязательно должны быть поля с вот такими названиями, которые имеют вот такое содержимое и также в тебе должны быть методы с вот такими названиями.

Поясним на примере:

//интерфейс с именем Flyable interface Flyable { //поля String species = “d”; int age = 10; //и абстрактный метод public void birdVoice(); //abstract уже не пишется так как //это интерфейс, а не абстр. класс } //Пускай теперь Flyable заимплементят //(лучше так говорить реализуют) два класса. //От интерфейса они будут наследовать поля //и должны реализовать все //его абстрактные методы. class Parrot implements Flyable { //реализуем абстрактный метод public void birdVoice(){ System.out.println(“RRAAAR!”); } }; class Sparrow implements Flyable { //реализуем абстрактный метод public void birdVoice(){ System.out.println(“Chick-Chirik”); } }; class InterfaceExample { //Сразу как пример использования //интерфейсов приведем пример полиморфизма //с интерфейсами. Приведем пример //передачи в метод любого объекта //класс которого реализует Flyable. //Создадим такой метод. public static void birdVoiceMethod(Flyable flyable){ flyable.birdVoice(); } //Как видим в метод можно передать Flyable, //а не конкретный Parrot или Sparrow. //Значит что можем туда передать любой объект //класс которого реализует Flyable. //Например birdVoiceMethod(parrot); public static void main(String[] args) { Parrot parrot = new Parrot(); Sparrow sparrow = new Sparrow(); //выводим разные реализации parrot.birdVoice(); sparrow.birdVoice(); //передаем объект попугая(он реализует Flyable) birdVoiceMethod(parrot); //передаем объект воробья(он тоже реализует Flyable) birdVoiceMethod(sparrow); } }

Вывод:


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

Абстрактные классы и интерфейсы часто сравнивают. Типа зачем интерфейсы если есть абстрактные классы? Или наоборот.

Ключевое отличие абстрактного класса от интерфейса в том, что у абстрактного класса есть состояние. То есть у него есть поля у которых есть состояние, то есть значения этих полей можно изменять, то есть у абстрактного класса по сути есть экземпляр(объект), а экземпляры наследников абстрактного класса имеют доступ к полями экземпляра абстрактного класса, который они наследуют.

Search Icon

Хотя важно заметить, что сам объект абстрактного класса напрямую создать нельзя, он как бы создается когда мы создаем объект его наследника.

А у интерфейса нет экземпляра и не может быть, так как интерфейс это просто поведение или каркас, которого должен придерживаться реализующий его класс. Экземплярами могут быть только реализующие этот интерфейс классы.

Еще раз. Абстрактный класс это недопиленный класс, объект(экземпляр) которого имеет состояние, а интерфейс просто предоставляет поведение и в этом основная разница.

Как уже было сказано, поля абстрактного класса можно менять.

Продемонстрируем на примере:

abstract class Animal { String name = “Barsik”; } class Cat extends Animal { } class AbstractClass { public static void main(String[] args) { Animal interfeceMutation = new Cat(); interfeceMutation.name = “Kuzya”; System.out.println(interfeceMutation.name); } }

Вывод:

Поля же интерфейса менять нельзя. Их можно только извлекать.

Как можно увидеть ниже скомпилировать код в котором меняеться поле интерфейса не получиться.

interface Animal { String name = “Barsik”; } class Cat implements Animal { } class InterfaceClass { public static void main(String[] args) { Animal interfeceMutation = new Cat(); interfeceMutation.name = “Kuzya”; System.out.println(interfeceMutation.name); } }

Вывод:

Абстрактный класс в Java

Абстрактный классобщий концепт чего-либо.

Это класс, который содержит в себе нереализованные методы (называются абстрактными методами), которые наследники должны реализовать.

Под ‘нереализованные’ имеется ввиду, что такие методы только объявляются в абстрактном классе, то есть у них нет тела, то есть кода внутри {}.

Абстрактные методы помечаются ключевым словом abstract.

Приведем пример абстрактного класса. Например птица. Птица это общий концепт для любой птицы, будь-то воробей или попугай.

Абстрактный класс птицы может иметь не только абстрактные методы, а и методы с реализацией.

Например.

Любая птица имеет крылья. Махание крыльями примерно ОДИНАКОВОЕ у всех птиц (ну по крайней мере представим так). Соответственно можно создать в абстрактном классе не абстрактный, а обычный метод с реализацией для махания крыльями и все классы птиц, которые наследуют от абстрактного класса Птица будут по умолчанию пользоваться методом для махания крыльями из абстрактного класса.

Абстрактный же метод в абстрактном классе Птица будет иметь смысл определить в таком случае:

Любая птица издает какие-то звуки. Но в этом уже случае почти все птицы издают РАЗНЫЕ звуки. Значит в абстрактном классе Птица должен быть абстрактный метод для издавания звуков, который наследующие от этого абстрактного класса птицы обязаны переопределить.

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

Из примеров абстрактных классов можно еще привести, например, Форма (форму реализует например куб и треугольник), средство передвижения (реализует машина или велосипед)

Search Icon

Воспринимайте абстрактный класс как недопиленный класс, который наследники должны допилить по своему посредством реализации абстрактных методов этого абстрактного класса. Недопиленный он потому что в нем всё же присутствуют реализации (пример метода махания крыльями), но в нем и хватает абстрактных методов, которые должны допилить наследники.

Поясним на примере:

abstract class Bird { //поля и абстрактные методы private String species; private int age; //Метод махания крыльями с реализацией по умолчанию. //Махание крыльями примерно одинаковое у птиц //(ну по крайней мере представим так) //поэтому для всех наследников класса Bird //пусть будет один общий метод, //которым они будут пользоваться. //То есть большинству наследников не придеться //переопределять wingsFlapMechanic public void wingsFlapFlap() { System.out.println(“Flap Flap Flap …”); } //не реализованный метод, который //обязан реализовать наследники //поскольку все птицы издают разные звуки //(ну почти все наверное). public abstract void pirdVoice(); //То есть если мы точно знаем что //у всех наследников будет //своя реализация метода //делаем его абстрактным. //просто геттеры сеттеры. public String getSpecies() { return species; } public void setSpecies(String species) { this.species = species; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } class Parrot extends Bird { //реализуем абстрактный метод public void pirdVoice() { System.out.println(“RRAAAR!”); } } class Sparrow extends Bird { //реализуем абстрактный метод public void pirdVoice() { System.out.println(“Chick-Chirik”); } } public class AbstractExample { public static void main(String[] args) { Parrot parrot = new Parrot(); Sparrow sparrow = new Sparrow(); //Пускай теперь эти разные //птицы помахают крыльями. parrot.wingsFlapFlap(); sparrow.wingsFlapFlap(); //Махают одинаково как видим //теперь пусть споют parrot.pirdVoice(); sparrow.pirdVoice(); //как видим у них разный голос } }

Вывод:

Полиморфизм в Java

Полиморфизм — возможность использовать одно и то же имя для разных реализаций методов или конструкторов.

В Java полиморфизм реализуется с помощью перегрузки (overloading) и переопределения (overriding).

Перегрузка позволяет в пределах одного класса создавать несколько методов (или конструкторов) с одинаковым именем, а отличаться они будут колличеством или типом параметров.

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

Рассмотрим пример перегрузки конструкторов:

class Animal { private String eats; private int noOfLegs; //Конструктор без параметров //(называется конструктором по умолчанию). public Animal(){} //Конструктор с одним параметром //задает значение только полю eats. public Animal(String food){ this.eats = food; } //Конструктор с несколькими параметрами //задает значения обоим полям. public Animal(String food, int legs){ this.eats = food; this.noOfLegs = legs; } //сеттеры и геттеры public String getEats() { return eats; } public void setEats(String eats) { this.eats = eats; } public int getNoOfLegs() { return noOfLegs; } public void setNoOfLegs(int noOfLegs) { this.noOfLegs = noOfLegs; } } class Polimorphism { public static void main(String[] args) { //Какой же из конструкторов //Animal, которые мы определили будет использован //при создании объекта животного? //Ведь у них же всех одинаковые имена. //На самом деле, можно просто выбрать //нужный конструктор передав в него //нужное количество параметров. //То есть, как видим, в строке кода ниже //вызывается конструктор с одним параметром. Animal animal = new Animal(“Grass”); //Значит будет использован конструктор //public Animal(String food), ведь в конструктор //передаётся всего один параметр. //В выводе увидим, что строка Grass успешно //записалась в поле объекта animal. System.out.println(animal.getEats()); } }

Вывод:

Даже несмотря на то, что у всех конструкторов одинаковое имя Animal, компилятор различает их по параметрам. Это и есть пример перегрузки конструктора, которая является одной из форм полиморфизма.

Однако помним, что полиморфизм может применяться не только к конструкторам, но и к методам.


Переопределение метода родителя в классе-наследнике

Под полиморфизмом также понимается переопределение метода родительского класса в классе-наследнике.

Например, ниже в классе Animal (Животное) определён метод move() (Движение), который просто сообщает, что животное может двигаться. Его реализация — вывод строки "The animal moves." — является универсальной и подходит для любого животного. Такой метод вполне может использоваться без изменений в классах-наследниках.

Однако в некоторых случаях желательно уточнить поведение метода. Например, если мы создадим класс Cat (Кот), который наследуется от Animal, то он унаследует и метод move(). Но в контексте кошки может быть полезно уточнить поведение этого метода, поскольку, разумеется, куда лучше, если move() будет описывать именно движения, характерные для кошки — например, "The cat gracefully walks and jumps." — а не просто общее "The animal moves."

В таких случаях метод move() можно переопределить в классе-наследнике, чтобы сделать поведение более конкретным и реалистичным.

Это и есть проявление полиморфизма: один и тот же метод, но разная реализация в разных классах.

Рассмотрим пример:

class Animal { private String eats; private int noOfLegs; public Animal(){} public Animal(String food){ this.eats = food; } public Animal(String food, int legs){ this.eats = food; this.noOfLegs = legs; } public String getEats() { return eats; } public void setEats(String eats) { this.eats = eats; } public int getNoOfLegs() { return noOfLegs; } public void setNoOfLegs(int noOfLegs) { this.noOfLegs = noOfLegs; } // Метод с базовой реализацией, который, // желательно, чтобы наследники переопределили. public void move() { System.out.println(“The animal moves.”); } } class Cat extends Animal{ private String name; private String color; Cat(){} Cat (String catName, String catColor) { name = catName; color = catColor; System.out.println(color); } public String getName(){ return name; } public void setName(String catName){ name = catName; } public String getColor(){ return color; } public void setColor(String catColor){ color = catColor; } public void move(){ //Переопределяем метод move. //Теперь движения кошки будут выводится как: //”The cat gracefully walks and jumps.”. System.out.println(“The cat gracefully walks and jumps.”); } } class Polimorphism2 { public static void main(String[] args) { Cat cat = new Cat(); // Воспользуемся методом move. cat.move(); // Если бы метод move не был переопределён, // на консоль вывелось бы “The animal moves.” // Но для кошки корректнее — “The cat gracefully // walks and jumps.” } }

Вывод:

Каждое животное, наследующее класс Animal, может по-своему переопределить метод move(). Так, у птицы это может быть “летает”, у собаки — “бежит”, и так далее.

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

Наследование в Java

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

Например, в программе ниже создаётся класс Animal (Животное).

Затем создаётся класс Cat (Кот), который с помощью ключевого слова extends наследует Animal, потому что кот — это частный случай животного, то есть кот является животным.

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

При наследовании класса Animal, класс Cat получает доступ напрямую ко всем полям и методам класса Animal.

Например, если у класса Animal есть поля: количество ног, тип питания и т.д., то класс Cat, наследующий Animal, также будет обладать этими полями. И если мы создадим объект кота в методе main, то сможем обращаться к этим полям через объект кота — как будто они определены непосредственно в классе Cat. Таким образом, можно сказать, что создаётся единый объект, состоящий из данных класса Animal и класса Cat.

Рассмотрим пример:

// Это класс Животное class Animal { // У него два поля: тип питания и количество ног private String eats; private int noOfLegs; public Animal(){} public Animal(String food, int legs){ this.eats = food; this.noOfLegs = legs; } //геттеры и сеттеры public String getEats() { return eats; } public void setEats(String eats) { this.eats = eats; } public int getNoOfLegs() { return noOfLegs; } public void setNoOfLegs(int noOfLegs) { this.noOfLegs = noOfLegs; } } //Наследование реализуется с помощью //ключевого слова extends. //Cat наследует Animal. class Cat extends Animal{ private String name; private String color; Cat(){} Cat (String catName, String catColor) { name = catName; color = catColor; System.out.println(color); } public String getName(){ return name; } public void setName(String catName){ name = catName; } public String getColor(){ return color; } public void setColor(String catColor){ color = catColor; } void sayMeow(){ System.out.println(“Meow”); } } class Inheritance { public static void main(String[] args){ Cat somecat = new Cat(); //Через объект класса Cat //имеем доступ к методам //setNoOfLegs и getNoOfLegs класса Animal //благодаря наследованию. somecat.setNoOfLegs(4); System.out.println(somecat.getNoOfLegs()); } }

Вывод:

Основное преимущество наследования очевидно — уменьшение количества кода за счет того, что довольно большая часть кода может быть вынесена в класс родитель, а наследники этот код могут просто использовать, так сказать, “подключаясь” с помощью extends к классу родителю.

Кроме того, наследование помогает логично организовать структуру программы — от общего к частному — и упрощает сопровождение: изменения в коде родителя автоматически отражаются во всех наследниках.

Свойства в ООП (getters, setters)

В Java поля класса никогда не следует определять как public, то есть они не должны быть доступны напрямую. Вместо этого поля должны быть private, а доступ к ним должен обеспечиваться с помощью специальных методов – геттеров и сеттеров.

геттер – это public метод через который мы получаем значение private поля.

сеттер – это public метод через который мы задаем значение private полю.

То есть мы работаем с private полем не напрямую (напрямую это так – somecat.name = “Barsik”;), а через public методы класса, то есть методы, которые доступны не только в пределах класса.

Search Icon

Определяется один геттер и один сеттер на каждое private поле класса.

Поясним на примере:

class Cat { // Пусть оба поля будут private private String name; private String color; Cat(String catName, String catColor) { name = catName; color = catColor; // К color можно обращаться только // в пределах класса кошки и ниоткуда больше // например, в конструкторе класса кота System.out.println(color); } // Как видим, для поля name был создан // геттер и сеттер (два публичных метода ниже). public String getName() { return name; } public void setName(String catName) { name = catName; } // Для color тоже создаем геттер и сеттер. public String getColor() { return color; } public void setColor(String catColor) { color = catColor; } void sayMeow() { System.out.println(“Meow”); } } class Props { public static void main(String[] args) { Cat somecat = new Cat(“Kuzya”, “White”); // Поскольку напрямую к полям мы обратиться // не можем (вот так somecat.color = “White”), // используем сеттер setColor для записи значения // в поле color объекта somecat. // setColor публичный метод, поэтому // можем к нему обратиться напрямую. somecat.setColor(“Black”); // Геттер тоже публичный метод, // поэтому тоже используем его напямую // для извлечения значения приватного поля color // из объекта somecat. System.out.println(somecat.getColor()); } }

Вывод:


Зачем нужны геттеры, сеттеры?

На первый взгляд может показаться, что проще объявить поле public и обращаться к нему напрямую через объект. Зачем усложнять?

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

Допустим, если в классе Cat есть поле name, которое public, то напрямую через объект этого класса можно задать значение этого поля. Например “З” (вот так – somecat.name = “З”;).

Ну кто называет кошку одним символом?Никто. Поэтому в сеттере можно задать ограничение, чтобы имя кошки содержало минимум два символа.

Поясним на примере:

class Cat{ private String name; private String color; public String getName(){ return name; } public void setName(String catName){ //Задаем ограничение. //Если длина переданного в метод имени //больше единицы, if(catName.length()<1) { //то записываем переданное имя в поле name. name = catName; } else { //В ином случае //выведем сообщение чтобы ввели //больше чем один символ System.out.println("Cat name have to contain" + "more than 1 character."); } } public String getColor(){ return color; } public void setColor(String catColor){ color = catColor; } void sayMeow(){ System.out.println("Meow"); } } class Props2 { public static void main(String[] args) { Cat somecat = new Cat(); somecat.setName("P");//В name ничего не запишется //и будет выведено сообщение: //"Cat name have to contain more than 1 character." //В результате строчкой кода ниже будет выведено, //что поле name объекта somecat пустое (null), //так как у нас не получилось записать туда "P". System.out.println(somecat.getName()); } }

Вывод: