Что такое ООП?
Объектно-ориентированное программирование (ООП) — методология программирования, основанная на представлении программы в виде совокупности объектов, каждый из которых является экземпляром определенного класса, а классы образуют иерархию наследования.
- объектно-ориентированное программирование использует в качестве основных логических конструктивных элементов объекты, а не алгоритмы;
- каждый объект является экземпляром определенного класса
- классы образуют иерархии.
Согласно парадигме ООП программа состоит из объектов, обменивающихся сообщениями. Объекты могут обладать состоянием, единственный способ изменить состояние объекта — послать ему сообщение, в ответ на которое, объект может изменить собственное состояние.
Какие преимущества у ООП?
Преимущества ООП:
- Возможность легкой модификации (при грамотном анализе и проектировании)
- Возможность отката при наличии версий
- Более легкая расширяемость
- «Более естественная» декомпозиция (разделение целого на части) программного обеспечения, которая существенно облегчает его разработку «слабая связанность кода».
- Сокращение количества межмодульных вызовов и уменьшение объемов информации, передаваемой между модулями.
- Увеличивается показатель повторного использования кода.
Какие недостатки у ООП?
Недостатки ООП:
- Требуется другая квалификация
- Резко увеличивается время на анализ и проектирование систем
- Увеличение времени выполнения
- Размер кода увеличивается
- Неэффективно с точки зрения памяти (мертвый код — тот, который не используется)
- Сложность распределения работ на начальном этапе
- Себестоимость больше
- Скорость
Назовите основные принципы ООП?
Принципы:
Что такое инкапсуляция?
Инкапсуляция — это свойство системы, позволяющее объединить данные и методы, работающие с ними, в классе и скрыть детали реализации от пользователя, открыв только то, что необходимо при последующем использовании.
В JAVA реализуется с помощью модификаторов доступа и геттеров/сеттеров.
Модификаторы доступа (Access modifiers):
публичный, общедоступный класс или член класса. Поля и методы, объявленные с модификатором public, видны другим классам из текущего пакета и из внешних пакетов.
закрытый класс или член класса, противоположность модификатору public. Закрытый класс или член класса доступен только из кода в том же классе.
такой класс или член класса доступен из любого места в текущем классе или пакете или в производных классах, даже если они находятся в других пакетах.
default (package visible)
отсутствие модификатора у поля или метода класса предполагает применение к нему модификатора по умолчанию. Такие поля или методы видны всем классам в текущем пакете.
Геттеры и Сеттеры (Getters and Setters)
Цель инкапсуляции — уйти от зависимости внешнего интерфейса класса (то, что могут использовать другие классы) от реализации. Чтобы малейшее изменение в классе не влекло за собой изменение внешнего поведения класса.
Состояние объекта – это значение всех его полей. Объект не должен изменяться чем то из вне, кроме своих методов. Убрать возможность случайного/умышленного изменения объекта.
Пример – кондиционер (берем пульт включаем холод — запускаются разные процессы, которые не показываются пользователю), пользователю на пульте показывается температура воздуха на выходе, остальное все скрыто.
Что такое наследование?
Наследование — свойство системы, позволяющее описать новый класс на основе уже существующего с частично или полностью заимствующейся функциональностью.
Наследование форма отношения «is a» (является).
Класс наследника является классом предка, но класс предка не является классом наследника: собака – наследник животного, но животное не наследник собаки. Наследник – более «узкий» класс.
Пример: кондиционер наследуется от холодильной техники.
Есть одиночное и множественное наследование (класс наследуется от нескольких классов).
Проблема – ромбовидное наследование: у одного базового класса есть два наследника, а у наследника этих двух классов свой наследник, какая реализация попадет в последний – ХЗ.
В Java множественное наследование ограничено.
public final class
Класс, от которого производится наследование, называется предком, базовым или родительским. Новый класс – потомком, наследником или производным классом.
class Employee extends Person <>
Делегирование – один класс вызывает другой класс, не связанный наследованием.
Что такое полиморфизм?
Полиморфизм — это свойство системы использовать объекты с одинаковым интерфейсом без информации о типе и внутренней структуре объекта.
Пример: интерфейс «охлаждать» имеет место в технике, холодное пиво)) и т.п.
Все if –ы можно заменить на полиморфизм (способ рефакторинга).
Позволяет уменьшать размер программы.
Преимуществом полиморфизма является то, что он помогает снижать сложность программ, разрешая использование одного и того же интерфейса для задания единого набора действий. Выбор же конкретного действия, в зависимости от ситуации, возлагается на компилятор языка программирования.
Отсюда следует ключевая особенность полиморфизма — использование объекта производного класса, вместо объекта базового (потомки могут изменять родительское поведение, даже если обращение к ним будет производиться по ссылке родительского типа).
Unit ref = new Refrigerator(«ref», 24);
Полиморфная переменная — это переменная, которая может принимать значения разных типов.
Полиморфная функция — функция у которой хотя бы один аргумент является полиморфной переменной.
Выделяют два вида полиморфных функций:
-
ad hoc — функция ведет себя по разному для разных типов аргументов.
(например, функция draw() — рисует по разному фигуры разных типов);
(например, функция add() — одинаково кладет в контейнер элементы разных типов).
Абстрагирование – это способ выделить набор общих характеристик объекта, исключая из рассмотрения частные и незначимые.
Соответственно, абстракция – это набор всех таких характеристик.
(телефон звонил в 19 веке и в 21, функция выделена из разных сложных процессов).
Объект — Класс — Абстрактный класс — Интерфейс(макс. уровень абстракции).
Что такое ассоциация, агрегация и композиция?
Ассоциация обозначает связь между объектами.
Композиция и агрегация — частные случаи ассоциации «часть-целое».
Агрегация предполагает, что объекты связаны взаимоотношением «part-of» (часть) .
Композиция более строгий вариант агрегации.
Дополнительно к требованию «part-of» накладывается условие, что экземпляр «части» может входить только в одно целое (или никуда не входить), в то время как в случае агрегации экземпляр «части» может входить в несколько целых.
Например, книга состоит из страниц, и мы не можем вырвать страницу из книги и вложить в другую книгу. Страницы четко привязаны к конкретной книге, поэтому это композиция. В тоже время мы можем взять и перенести книгу из одной библиотеки в другую — это уже агрегация.
«Является» подразумевает наследование.
«Имеет» подразумевает ассоциацию (агрегацию или композицию).
Расскажите про раннее (статическое) и позднее (динамическое) связывание.
Связывание — присоединение вызова метода к телу метода.
Раннее (статическое) связывание — проводится компилятором (компоновщиком) перед запуском программы.
Например, перегрузка методов; приватные, статические и final методы.
Позднее (динамическое) связывание — непосредственно во время выполнения программы (runtime), в зависимости от типа объекта.
Например, переопределение методов. Компилятор не знает тип объекта, но механизм вызова методов определяет его и вызывает соответствующее тело метода.
Для всех методов Java используется механизм позднего (динамического) связывания, если только метод не был объявлен как final (приватные методы являются final по умолчанию).
public class Main <
public static void main(String[] args) <
Insurance current = new CarInsurance();
// Динамическое связывание на основе объекта потому что метод premium() нестатический
int premium = current.premium();
// Статическое связывание на основе класса, метод category() статический (принадлежит классу)
String category = current.category();
public static final int LOW = 100;
public int premium()
public static String category()
class CarInsurance extends Insurance <
public static final int HIGH = 200;
public int premium()
public static String category()
REST (Representational state transfer) – это стиль архитектуры программного обеспечения для распределенных систем, таких как World Wide Web, который, как правило, используется для построения веб-служб.
Системы, поддерживающие REST, называются RESTful-системами.
В общем случае REST является очень простым интерфейсом управления информацией без использования каких-то дополнительных внутренних прослоек. Каждая единица информации однозначно определяется глобальным идентификатором, таким как URL. Каждая URL в свою очередь имеет строго заданный формат.
Отсутствие дополнительных внутренних прослоек означает передачу данных в том же виде, что и сами данные. Т.е. мы не заворачиваем данные во что либо.
Каждая единица информации однозначно определяется URL – это значит, что URL по сути является первичным ключом для единицы данных.
Как происходит управление информацией сервиса – это целиком и полностью основывается на протоколе передачи данных. Наиболее распространенный протокол конечно же HTTP.
Архитектура REST очень проста в плане использования. По виду пришедшего запроса сразу можно определить, что он делает, не разбираясь в форматах (в отличие от SOAP, XML-RPC). Данные передаются без применения дополнительных слоев, поэтому REST считается менее ресурсоемким, поскольку не надо парсить запрос чтоб понять что он должен сделать и не надо переводить данные из одного формата в другой.
Lesson: Object-Oriented Programming Concepts
If you've never used an object-oriented programming language before, you'll need to learn a few basic concepts before you can begin writing any code. This lesson will introduce you to objects, classes, inheritance, interfaces, and packages. Each discussion focuses on how these concepts relate to the real world, while simultaneously providing an introduction to the syntax of the Java programming language.
What Is an Object?
An object is a software bundle of related state and behavior. Software objects are often used to model the real-world objects that you find in everyday life. This lesson explains how state and behavior are represented within an object, introduces the concept of data encapsulation, and explains the benefits of designing your software in this manner.
What Is a Class?
A class is a blueprint or prototype from which objects are created. This section defines a class that models the state and behavior of a real-world object. It intentionally focuses on the basics, showing how even a simple class can cleanly model state and behavior.
What Is Inheritance?
Inheritance provides a powerful and natural mechanism for organizing and structuring your software. This section explains how classes inherit state and behavior from their superclasses, and explains how to derive one class from another using the simple syntax provided by the Java programming language.
What Is an Interface?
An interface is a contract between a class and the outside world. When a class implements an interface, it promises to provide the behavior published by that interface. This section defines a simple interface and explains the necessary changes for any class that implements it.
What Is a Package?
A package is a namespace for organizing classes and interfaces in a logical manner. Placing your code into packages makes large software projects easier to manage. This section explains why this is useful, and introduces you to the Application Programming Interface (API) provided by the Java platform.
Questions and Exercises: Object-Oriented Programming Concepts
Use the questions and exercises presented in this section to test your understanding of objects, classes, inheritance, interfaces, and packages.
Object-Oriented Programming in Java — Java OOPs Concepts
![]()
Object-Oriented Programming is a programming style that is associated with the concepts like class, object, Inheritance, Encapsulation, Abstraction, Polymorphism. Most popular programming languages like Java, C++, C#, Ruby, etc. follow an object-oriented programming paradigm. In this blog, I will talk about object-oriented programming concepts in Java. An object-based application in Java is based on declaring classes, creating objects from them, and interacting between these objects. In this blog, we will understand the below core concepts of Object-oriented Programming in the following sequence:
- Inheritance
- Encapsulation
- Abstraction
- Polymorphism
Let’s get started with the first Object Oriented Programming concept i.e. Inheritance.
Inheritance
In OOP, computer programs are designed in such a way where everything is an object that interact with one another. Inheritance is one such concept where the properties of one class can be inherited by the other. It helps to reuse the code and establish a relationship between different classes.
As we can see in the image, a child inherits the properties from his father. Similarly, in Java, there are two classes:
1. Parent class ( Super or Base class)
2. Child class (Subclass or Derived class )
A class that inherits the properties is known as Child Class whereas a class whose properties are inherited is known as Parent class.
Inheritance is further classified into 4 types:
So let’s begin with the first type of inheritance i.e. Single Inheritance:
Single Inheritance:
In single inheritance, one class inherits the properties of another. It enables a derived class to inherit the properties and behavior of a single parent class. This will, in turn, enable code re-usability as well as add new features to the existing code.
Here, Class A is your parent class and Class B is your child class which inherits the properties and behavior of the parent class.
Let’s see the syntax for single inheritance:
Multilevel Inheritance:
When a class is derived from a class which is also derived from another class, i.e. a class having more than one parent class but at different levels, such type of inheritance is called Multilevel Inheritance.
If we talk about the flowchart, class B inherits the properties and behavior of class A and class C inherits the properties of class B. Here A is the parent class for B and class B is the parent class for C. So in this case class C implicitly inherits the properties and methods of class A along with Class B. That’s what is multilevel inheritance.
Let’s see the syntax for multilevel inheritance in Java:
Hierarchical Inheritance:
When a class has more than one child classes (subclasses) or in other words, more than one child classes have the same parent class, then such kind of inheritance is known as hierarchical.
If we talk about the flowchart, Class B and C are the child classes which are inheriting from the parent class i.e Class A.
Let’s see the syntax for hierarchical inheritance in Java:
Hybrid Inheritance:
Hybrid inheritance is a combination of multiple inheritance and multilevel inheritance. Since multiple inheritances is not supported in Java as it leads to ambiguity, so this type of inheritance can only be achieved through the use of the interfaces.
If we talk about the flowchart, class A is a parent class for class B and C, whereas Class B and C are the parent class of D which is the only child class of B and C.
Now we have learned about inheritance and their different types. Let’s switch to another object-oriented programming concept i.e Encapsulation.
Encapsulation
Encapsulation is a mechanism where you bind your data and code together as a single unit. It also means to hide your data in order to make it safe from any modification. What does this mean? The best way to understand encapsulation is to look at the example of a medical capsule, where the drug is always safe inside the capsule. Similarly, through encapsulation, the methods and variables of a class are well hidden and safe.
We can achieve encapsulation in Java by:
- Declaring the variables of a class as private.
- Providing public setter and getter methods to modify and view the variables values.
Let us look at the code below to get a better understanding of encapsulation:
Let us try to understand the above code. I have created a class Employee which has a private variable name. We have then created a getter and setter methods through which we can get and set the name of an employee. Through these methods, any class which wishes to access the name variable has to do it using these getter and setter methods.
Let’s move forward to our third Object-oriented programming concept i.e. Abstraction.
Abstraction
Abstraction refers to the quality of dealing with ideas rather than events. It basically deals with hiding the details and showing the essential things to the user. If you look at the image here, whenever we get a call, we get an option to either pick it up or just reject it. But in reality, there is a lot of code that runs in the background. So you don’t know the internal processing of how a call is generated, that’s the beauty of abstraction. Therefore, abstraction helps to reduce complexity. You can achieve abstraction in two ways:
a) Abstract Class
Let’s understand these concepts in more detail.
Abstract class:
Abstract class in Java contains the ‘abstract’ keyword. Now, what does the abstract keyword mean? If a class is declared abstract, it cannot be instantiated, which means you cannot create an object of an abstract class. Also, an abstract class can contain abstract as well as concrete methods.
Note: You can achieve 0–100% abstraction using an abstract class.
To use an abstract class, you have to inherit it from another class where you have to provide implementations for the abstract methods there itself, else it will also become an abstract class.
Let’s look at the syntax of an abstract class:
Interface:
An interface in Java is a blueprint of a class or you can say it is a collection of abstract methods and static constants. In an interface, each method is public and abstract but it does not contain any constructor. Along with abstraction, the interface also helps to achieve multiple inheritances in Java.
Note: You can achieve 100% abstraction using interfaces.
So an interface basically is a group of related methods with empty bodies. Let us understand interfaces better by taking an example of a ‘ParentCar’ interface with its related methods.
These methods need to be present for every car, right? But their working is going to be different.
Let’s say you are working with a manual car, there you have to increment the gear one by one, but if you are working with an automatic car, that time your system decides how to change gear with respect to speed. Therefore, not all my subclasses have the same logic written for change gear. The same case is for speedup, now let’s say when you press an accelerator, it speeds up at the rate of 10kms or 15kms. But suppose, someone else is driving a supercar, where it increments by 30kms or 50kms. Again the logic varies. Similarly for applybrakes, where one person may have powerful brakes, other may not.
Since all the functionalities are common with all my subclasses, I have created an interface ‘ParentCar’ where all the functions are present. After that, I will create a child class which implements this interface, where the definition to all these methods varies.
Next, let’s look into the functionality as to how you can implement this interface.
So to implement this interface, the name of your class would change to any particular brand of a Car, let’s say I’ll take an “Audi”. To implement the class interface, I will use the ‘implement’ keyword as seen below:
Here as you can see, I have provided functionalities to the different methods I have declared in my interface class. Implementing an interface allows a class to become more formal about the behavior it promises to provide. You can create another class as well, say for example BMW class which can inherit the same interface ‘car’ with different functionalities.
So I hope you guys are clear with the interface and how you can achieve abstraction using it.
Finally, the last Object-oriented programming concept is Polymorphism.
Polymorphism
Polymorphism means taking many forms, where ‘poly’ means many and ‘morph’ means forms. It is the ability of a variable, function or object to take on multiple forms. In other words, polymorphism allows you define one interface or method and have multiple implementations.
Let’s understand this by taking a real-life example and how this concept fits into Object-oriented programming.
Let’s consider this real-world scenario in cricket, we know that there are different types of bowlers i.e. Fast bowlers, Medium pace bowlers and spinners. As you can see in the above figure, there is a parent class- BowlerClass and it has three child classes: FastPacer, MediumPacer, and Spinner. Bowler class has bowlingMethod() where all the child classes are inheriting this method. As we all know that a fast bowler will be going to bowl differently as compared to medium pacer and spinner in terms of bowling speed, long run up and way of bowling, etc. Similarly, a medium pacer’s implementation of bowlingMethod() is also going to be different as compared to other bowlers. And the same happens with spinner class.
The point of the above discussion is simply that the same name tends to multiple forms. All the three classes above inherited the bowlingMethod() but their implementation is totally different from one another.
Polymorphism in Java is of two types:
- Runtime polymorphism
- Compile time polymorphism
Runtime Polymorphism:
In Java, runtime polymorphism refers to a process in which a call to an overridden method is resolved at runtime rather than at compile-time. In this, a reference variable is used to call an overridden method of a superclass at runtime. Method overriding is an example of runtime polymorphism. Let us look the following code to understand how the method overriding works:
Compile Time Polymorphism:
In Java, compile-time polymorphism refers to a process in which a call to an overloaded method is resolved at compile time rather than at runtime. Method overloading is an example of compile time polymorphism. Method Overloading is a feature that allows a class to have two or more methods having the same name but the arguments passed to the methods are different.
Unlike method overriding, arguments can differ in:
- Number of parameters passed to a method
- Datatype of parameters
- The sequence of datatypes when passed to a method.
Let us look at the following code to understand how the method overloading works:
I hope you guys are clear with all the object-oriented programming concepts that we have discussed above i.e inheritance, encapsulation, abstraction, and polymorphism. Now you can make your Java application more secure, simple and re-usable using Java OOPs concepts.
With this, we come to an end to this blog. If you wish to check out more articles on the market’s most trending technologies like Artificial Intelligence, DevOps, Ethical Hacking, then you can refer to Edureka’s official site.
Do look out for other articles in this series which will explain the various other aspects of Java.
Урок 8 — Объектно-ориентированное программирование в Java
В этом уроке мы познакомимся с объектно-ориентированным программированием (ООП) на языке Java. Начиная с простейшего проекта в нашей программе встречались классы, но мы пользовались ими несознательно. Сейчас мы познакомимся с терминологией и основными синтаксическими конструкциями ООП. Более подробно, хоть и без привязки к языку Java, тема ООП рассмотрена в нашем учебнике — в частности, из него вы можете узнать как использовать ООП правильно, но чтобы понять материал учебника вам нужно освоить этот урок.
Исходный код примеров и проект для среды Eclipse можно взять в репозитории.
1 Терминология ООП
Ключевое понятие ООП — это Класс, часто говорят что классы объединяют код и данные вместе. Это значит, что создавая новый класс мы создаем новый тип данных, который содержить поля (переменные) и методы (функции). Например, можно создать класс Собака , содержащий поля — сытость, громкость, кличка и функции — лечь(), сесть() и лаять() .
Следующее важное понятие — Объект (экземпляр класса), он соответствует переменной, типом которой является ваш Класс. То есть переменную типа Int — тоже можно назвать объектом. Для нашего примера объектами будут являться Жучка, Дружок и другие экземпляры Собак .
Также ООП предоставляет программисту ряд дополнительных механизмов и принципов — мы ими пока что не пользовались и они могут быть не понятны, тем не менее, читая этот урок — возвращайтесь к следующим определениям:
- наследование — механизм позволяющий описать новый класс на основе уже существующего. Новый класс называют дочерним классом, потомком, подклассом или производным классом по отношению к родительскому или базовому классу. Множественное наследование не допускается;
- полиморфизм — механизм позволяющий изменить реализацию методов, т.е. поведение объекта в дочерних классах;
- абстракция — возможность объявить метод, без его конкретной реализации. Подобные классы называются абстрактными;
- инкапсуляция — сокрытие реализации класса от его пользователя. Другими словами, пользователь имеет доступ только к открытым членам класса. К закрытым членам имеет доступ только сам объект;
- интерфейсы — особый вид абстрактных классов. Они не могут иметь изменяемых членов данных. Класс может реализовывать сколь угодно много интерфейсов. Таким образом, интерфейс формирует поведенческую линию для объектов, не принадлежащих одной иерархии классов.
2 Определение класса
Класс описывается в файле с исходным кодом, при этом в Java имя файла должно совпадать с одним из классов определенных в нем. Остальные классы являются локальными (не доступными извне). Помимо этого, классы группируются в пакеты (пространства имен), например, набор классов отвечающий за работу по сети может быть помещен в пакет Network . Сам по себе класс также задает пространство имен, разрешается одни классы вкладывать в другие (почувствуйте разницу между вложением друг в друга объектов).
Рассмотрим пример простого класса:
Запустите этот простой пример у себя. Дальше мы размеремся с конструкциями, которые вам сейчас не понятны. Наш класс Dog имеет 3 метода, два из которых являются конструкторами, разберемся с ними.
3 Конструкторы класса и другие методы
Конструктор — особый метод для инициализации объекта, который ммеет тоже имя, что и класс. Тип возвращаемого значения не указывается. Конструкторы имеют только одно назначение — создать экземпляр класса, как здесь:
Dog druzhok = new Dog("druzhok");
Члены класса (поля и методы) могут иметь следующие права доступа:
- public — доступны кому угодно, как в нашем случае — конструкторы класса и метод speak , которые и вызываются из Main , то есть достпны ему;
- protected — доступны только классам наследникам (рассматриваются дальше подробнее), если мы установим такой спецификатор для метода speak — то вызвать его из Main будет нельзя, но если мы создадим класс наследник от Dog — то ему этот метод будет доступен;
- private — доступны только внутри класса;
- пакетный уровень доступа — элемент считается с пакетным уровнем доступа, если не указан ни один из модификаторов доступа. В таком случае элемент доступен классу в котором объявлен и другим классам в том же пакете, но не доступен классам, в том числе и наследникам, находящимся в других пакетах. Таким образом данный уровень видимости является более строгим чем protected.
Права доступа — это инструмент обеспечения инкапсуляции, а значит и построения хорошей абстракции. Это сложная тема, связанная с хорошим ООП и чистым кодом. Не пытайтесь понять это сейчас, вернитесь к приведенным ссылкам после прочтения текущего урока до конца.
Методы могут возвращать любой правильный тип, или ничего не возвращать (тогда возвращаемый тип описывается как void — например, у нашего speak() ). Конструкторы же не имеют возвращаемого типа, они не могут возвращать даже тип void .
Конструкторы и методы используют ключевое слово this совершенно по-разному. Метод использует this чтобы получить ссылку на экземпляр класса выполняющего этот метод. Конструкторы используют this чтобы сослаться на другой конструктор в этом же классе, но с другим списком параметров. Если констурктор использует ключевое слово this , то оно должно быть в первой строке, игнорирование этого правила приведет к ошибке компилятора. Переписать наши конструкторы можно так:
Статические методы не используют this ,т.к. они не принадлежат экземпляру класса, поэтому this некуда ссылаться. Статические методы принадлежат классу как целому, но никак не экземпляру класса.
4 Наследование и полиморфизм
Наследование — механизм расширения ваших классов. Допустим, помимо нашего класса Dog , нам потребовались ездовые собаки ( RidingDog ), обладающие скоростью и выносливостью (нужны дополнительные поля). Мы можем реализовать это по разному:
- Создать новый класс RidingDog , поместив в него кличку, громкость и все другие поля и методы класса Dog, а затем добавить туда speed и strength . При этом объем кода сильно растет, а его нужно поддерживать. Мало того, когда мы скопировали исходный код класса Dog , то мы скопировали и ошибки, которые в нем могли быть. В чуть более сложных проектах — это совсем не жизнеспособное решение.
- Добавить в класс Dog новые поля и методы, но оставить их незаполненными для существующих собак, а для ездовых — заполнять. Это тоже плохое решение, так как приводит к путанице. Завтра нам понадобится охотничья собака, а потом — собака поводырь или кинологическая собака. В лучшем случае — мы получим неуправляемый класс Dog , в худшем — у одной собаки функция find должна работать так, а у другой — иначе.
- Использовать механизм наследования, который предназначен как раз для таких случаев и решает проблемы двух предыдущих пунктов. Между классами существуют отношения, так вот наследование — задает отношение «является». Кинологическая собака является собакой — поэтому тут можно и нужно использовать наследование.
Создадим класс RidingDog (в файле RidingDog.java ):
В класс Main добавим еще одну собаку:
В консоль будет дважды выведено "gaw gaw" .
Мы создали класс-наследник, как отмечалось выше, ему будут доступны поля базового класса ( Dog ), помечены как protected . Для этого тут использованы два новых ключевых слова:
- extends задает родительский класс новому создаваемому классу. У каждого класса может быть только один базовый — множественное наследование запрещено. По умолчанию любой класс наследуется от класса Object из пакета java.lang , то есть у нашего Dog тоже есть базовый класс.
- super используется для инициализации родительского класса — в этом случае вызов родительского конструктора должен быть первой строкой в дочернем конструкторе (как в нашем случае). В более общем случае задает ссылку на родительский класс, чтобы понять это — добавьте в класс RidingDog следующий метод:
Последний пример демонстрирует еще одну важнейшую особенность классов — возможность определить новое поведение методов с таким же именем в классах-наследниках, при этом это поведение может основываться на поведении базового класса — как раз за счет использования super . Аннотация @Override указывает, что далее мы собираемся переопределять метод базового класса. При этом, если в базовом классе не окажется метода с аналогичной сигнатурой, то мы получим предупреждение компилятора о том, что хотя мы и собирались что-то переопределить, по факту этого не произошло.
Венцом всего этого является полиморфизм. В самом деле, вдруг нам потребовалось хранить всех собак (и обычных, и беговых) в одном массиве, но ведь у них разные типы данных, что делать? Как создать такой массив? — Как демонстрирует, приведенный ниже пример, мы без проблем можем создать объект с типом базового класса, а фактически создать на его месте объект класса-наследника (даже если этот объект является элементом массива). Мало того, при вызове у этого объекта метода, переопределенного в классе-наследнике, будет вызван метод наследника:
Возможность работы с клаасами наследниками через набор открытых методов базового класса и называется полиморфизмом.
5 Интерфейсы в Java
Как отмечалось выше, у класса в Java может быть только один класс-наследник, однако часто нужно больше, ведь наследование отражает отношение «является», однако иногда хочется это нарушить — на помощь приходят интерфейсы, так как множественное наследование интерфейсов разрешено.
В общем случае, интерфейс — это набор открытых методов. Наследование от интерфейса означает обязательство класса-наследника по реализации всех этих методов. Чтобы понять кому это нужно и научиться осознанно этим пользоваться — посмотрите наш учебник, а сейчас лишь представим, что наши собаки являются элементами компьютерной игры. В играх часто персонаж может собрать монетки (увеличивают счет), сердечки (увеличивают количество здоровья) и так далее, пусть в нашей игре персонажи должны уметь брать рупор (увеличивает громкость).
В файле GameItem.java опишем этот интерфейс, примерно также как раньше поступали с классом, но не требуется указывать спецификатор доступа для методов (они всегда публичные):
Наследование от такого интерфейса обязывает класс реализовать метод take_horn . В интерфейсе могут также содержаться поля, они всегда являются константными. Наследование от интерфейса задается ключевым словом implements — например implements A, B, C . Реализовать интерфейс GameItem в классе Dog можно так:
Иногда вам может встретиться пустой интерфейс (т.е. не содержащий ни единого метода) — их используют в качестве пометок для определения, поддерживает ли класс какие-либо возможности. Например, классы с интерфейсом Link имеют разный внутренний доступ к элементам: произвольный ( ArrayList ) или последовательный ( LinkedList ). Чтобы дать возможность понять какой доступ используется, классы с произвольным доступом реализуют дополнительно пустой интерфейс RandomAccess .
Примеры стандартных интерфейсов Java:
- Cloneable — интерфейс метка, указывает, что вызов метода clone класса Object легален;
- Comparable/Comparator — интерфейс для сравнения объектов изнутри/из вне;
- Formatable — для представления объекта в виде форматированного текста;
- Iterator, Iterable — интерфейс для обхода элементов и интерфейс как обходить элементы;
- Runnable/Callable — описание задачи потока в виде процедуры/функции;
- Serializable — для платформонезавимой записи/чтения объекта из потока ввода/вывода;
6 Атрибуты final и static
- у членов данных означает, что их значение запрещено менять или другими словами определяет константы. Если финальный член данных не инициализирован при определении, то его значение необходимо указать в конструкторах. Такая альтернатива переменным членам данных может ускорить работу приложения.
- можно применить ко всему классу для запрещения наследования от него. Такой класс называется завершенным (например, класс String ).
- у методов означает отмену их виртуальности (полиморфизма) — возможности переопределения в дочерних классах. Использование этого атрибута может ускорить работу приложения (как inline в С++). Объекты у которых все члены данных имеют атрибут final называются завершенными (пример, все тот же String
Атрибут static указывает, что член является общим для всех экземпляров класса. Естественно статические методы могут использовать только статические члены. Доступ к статическим членам происходит через имя класса.
Статические финальные члены данных часто используются для эмуляции перечислений.
7 Пакеты Java
Пакеты являются своего рода областью имен и служат для группировки классов. Если класс на диске определяется файлом, то пакет директорией.
Ключевое слово package указывает в каком пакете должен определяться класс. И если файл класса находится в директории не соответствующей имени пакета, будет выведено сообщение об ошибке.
Если класс определяется с атрибутом public, то он будет доступен для других пакетов.
Для использования классов из других пакетов необходимо указать полный путь к классу,
при этом вместо слешей точка.
Наиболее частые классы можно импортировать ключевым словом import. Импорт располагается до определения класса.
Если необходимо импортировать все классы пакета, то вместо имени класса указывается звездочка. import javax.swing.* Продвинутые среды как Eclipse автоматически добавляют строку импорта для используемого класса.
8 Код генерируемый компилятором
Начинающий Java программист может быть озадачен когда компилятор автоматически дополнит исходный код конструкторами. В тех случаях, когда вы пишете код класса не содержащий конструкторов, компилятор автоматически дополнит его конструктором без аргументов. Т.е. если вы напишете:
public class Example <>
это эквивалентно написанию:
Компилятор автоматически дополняет код не содержащий вызовов super , добавляя вызов super с несколькими параметрами или вообще без параметров в первую строку конструктора. Таким образом, если вы напишете
это будет идентично написанию:
Наблюдательный новичок может быть удивлен тому, как вышеописанная программа может вызывать конструктор предка, когда TestConstructor не наследует ни одного класса. Ответом является то, что Java наследует класс Object в том случае, когда явно не указан класс предок. Компилятор автоматически добавляет конструктор без аргументов, если явно не описан конструктор, и автоматически дополняет вызовом super без аргументов, в случае отсутствия явного вызова super . Таким образом, следующие два примера являются функционально эквивалентными:
public class Example <>
9 Практика наследования
Ниже рассмотрен пример очень простого класса — тысячи таких пишут начинающие программисты. Я рекомендую прочитать рассуждения по поводу этого класса и не повторять ошибок.
Рассмотрите определение класса Point . Какие проблемы могут возникнуть при создании производного класса? Как можно избавиться от этих проблем?
Решение:
На рисунке приведен исходный код класса точка, при этом:
- поля x, y являются публичными;
- класс не имеет закрытых методов;
- интерфейс класса (набор открытых методов) позволяет:
- создать точку (Point),
- скопировать ее (getLocation),
- переместить (задать новые координаты — move, setLocation).
Явные проблемы — дублирование кода:
- методы setLocation(int, int) и move выполняют одно и тоже. Один из этих методов стоит удалить — скорее всего move (название менее удачное).
- Метод setLocation(int, int) и setLocation(Point) выполняют одно и тоже. Один из них стоит выразить через другой. Например:
Кроме этой проблемы, есть ряд других, но не таких очевидных (о вариантах исправления можно рассуждать):
1. Возможно, стоит изменить имя класса — использовать Location2D вместо Point . Тогда вместо методов setLocation, getLocation можно будет использовать get и set .
2. Интерфейс класса позволяет создать точку и задать значения ее полей. Однако сделать это можно и не используя фукнции, а явно обращаясь к полям. Отсюда — 2 варианта исправления кода (с каждым из них можно спорить):
2.1 закрыть поля x, y . Тогда задание и изменений их значений будет происходить только через интерфейс класса (открытые методы);
2.2 удалить все функции getLocation, setLocation, x(), y(), а к полям обращаться явно:Порассуждаем над этими двумя вариантами. Что нам дает первый из них? — к полям мы можем обращаться только через методы. Если эти методы лишь изменяют координаты — то никакой выгоды мы не получим, но синтаксически код, использующий нашу точку будет хуже, например:
Очевидно, что первый вариант выглядит лучше. Кроме того, сам класс Точки в этом случае элементарный. Работать такой код будет быстрее (так как не будет издержек на вызов функции). Так зачем эти методы? Что они дают?:
а) Класс должен задавать абстракцию за счет сокрытия деталей реализации. Однако, метод getLocation(int, int) раскрывает эту абстракцию — человек, пользующийся нашим классом все равно знает как класс устроен внутри, несмотря на то, что мы закрыли поля x, y.
б) Методы могут выполнять дополнительную работу — например, логгирование или валидацию данных. Однако, в данном кокретном случае сложно себе представить такую конструкцию.
в) Методы, в отличии от полей можно переопределить в классах-наследниках. Для нашего примера — это могла бы быть ЦветнаяТочка или Position3D , например. Однако, цветная точка, скорее всего должна была бы дополнительно возвращать свеой цвет, ей нет смысла переопределяеть методы Location . Трехмерная точка тоже не должна переопределять методы setLocation(int, int). Мало того, эти методы будут ей мешать. Очевидно ли что делает следующий код? — думаю нет:
Исходя из всего этого, класс точки наиболее правильно реализовать так:
Тут не нужны ни классы, ни наследование. Наследование трехмерной точки от двухмерной, возможно (в зависимости от предметной области) нарушает принцип постановки Лисков. Является ли трехмерная точка двухмерной? — Вопрос философский, а исходный код — нет. Можно посмотреть на такой пример:
Окажется, что она является Point2D(1,2) , но почему не Point2D(2,3) или Point2D(1,3) . Ведь убрав одно измерение мы из исходной точки получим не 1 вариант, а 3 — тогда почему программа работает именно так? — Для случаев, чуть сложнее точки ответ на этот вопрос будет еще менее очевиден.
Вложенные классы
В учебнике по ООП отмечается, что класс задает пространство имен. Особенно это ярко проявляется в статических классах, но суть в том, что в один класс можно вложить другой. Это может быть удобно в том, случае, если внутренний класс необходим для использования только внешнему классу и никаким другим классам в программе. Также использование внутренних классов способствует инкапсуляции — сокрытию тех элементов кода, которые по вашему мнению не должны быть видны извне. Объявление внутреннего класса в Java выглядит следующим образом:
Еще одно отличие от обычных классов состоит в том, что мы можем свободно создавать объекты класса Outer. А вот объекты класса Inner создать можно, только имея ссылку на объект внешнего класса. Для того чтобы создать объект внутреннего класса, можно воспользоваться следующей записью:
Тип внутреннего класса в этом случае определяется как Outer.Inner , а создание нового объекта класса Inner , как видите, использует ссылку на объект внешнего класса outer и запись вида .new Inner() .
Что еще дает нам создание внутренних классов. Внутренний класс имеет доступ ко всем полям внешнего класса. Их разрешено использовать во внутреннем классе. Но не наоборот. Например:
Программа выполнится без ошибок. Но в случае выполнения другого примера:
Если вы уберете символы комментария, то компилятор сообщит вам об ошибке. Переменная b объявлена внутри класса Inner как закрытая(private) и недоступна нигде больше, кроме как внутри данного класса.
Локальные классы.
Внутренний класс может быть объявлен также не просто внутри какого-то класса, но даже внутри метода или другой области действия. Например:
Такие классы называют локальными внутренними классами.
Анонимные внутренние классы.
На первый взгляд это может показаться странным, но используемый внутренний класс может и не иметь имени. Как, где и зачем это нужно? Если вы единожды хотите выполнить какую-то работу и совершенно необязательно об этом знать кому-нибудь еще, можно воспользоваться синтаксисом анонимных внутренних классов. Такие классы должны расширять какой-то другой класс или реализовывать интерфейс (но только один!). Например:
Мы создали интерфейс ActionListener , который будет реализован анонимным внутренним классов в методе action() . Возможно, что вы уже не раз встречали подобный код и теперь многое для Вас стало более понятным. Анонимный внутренний класс не может содержать внутри себя конструктор. Это вполне логично, так как класс вообще не имеет никакого имени (на то он и анонимный!).
Итак, в этом уроке мы познакомились с понятием внутреннего класса, объявлением внутренних классов в языке Java, а также рассмотрели отличия в использовании локальных, вложенных и анонимных внутренних классов.
Методы объектов Java
В стандартном классе Object определен ряд специфических методов, которые становятся доступны если вы от Object наследуетесь. Не сразу понятно зачем они нужны и какова цена их использования, разбираемся…
Метод clone() Java
При вызове метода Java-машина создает и возвращает дубликат объекта, у которого вызвали данный метод. Клонирование объекта в классе Object реализовано очень примитивно – при клонировании создается еще один объект и его полям присваиваются значения полей объекта-образца. Если копируемый объект содержит ссылки на другие объекты, то ссылки будут скопированы, дубликаты тех объектов не создаются.
Java-машина не знает, какие объекты можно клонировать, а какие нет, к примеру, файлы клонировать нельзя. Поэтому ответственность о полноценном клонировании полностью ложится на плечи разработчика. Тут все было сделано по аналогии с методом equals() , имеется также своеобразный аналог метода hashCode() – это интерфейс Cloneable .
Интерфейс Cloneable – это интерфейс-маркер. Данный интерфейс не содержит никаких методов. Он используется, чтобы маркировать (помечать) некоторые классы. Если разработчик класса считает, что объекты класса можно клонировать, то он помечает класс этим интерфейсом (имплементирует класс от интерфейса Cloneable ).
Если разработчика не устраивает стандартная реализация метода clone(), он должен написать свою, которая будет создавать дубликат объекта правильным образом. Для этого нам нужно переопределить данный метод.
При вызове метода clone(), Java-машина проверяет, имплементирован ли у объекта интерфейс Cloneable . Если данный интерфейс был имплементирован, то клонирует объект методом clone() , если нет — выкидывает исключение CloneNotSupportedException .
Методы equals() и hashCode
Прежде чем пытаться понять методы equals() и hashCode() , необходимо вспомнить несколько фактов: в Java при сравнении ссылочных переменных сравниваются не сами объекты, а ссылки на объекты, и что все объекты унаследованы от класса Object , который содержит реализацию методов equals() и hashCode() по умолчанию.
Для решения задачи сравнения ссылочных переменных существует стандартное решение – метод equals() . Цель данного метода – определить идентичны ли объекты внутри, сравнив их внутреннее содержание. У класса Object есть своя реализация метода equals , которая просто сравнивает ссылки:
Порой такой реализации бывает не достаточно, поэтому, при необходимости чтобы разные объекты с одинаковым содержимым рассматривались как равные, надо переопределить метод equals() учитывая поля, которые должны участвовать в сравнении объектов. Ведь только разработчик класса знает, какие данные важны, что учитывать при сравнении, а что – нет.
Метод equals() должен удовлетворять следующим свойствам:
- Симметричность: для двух ссылок a и b — если a.equals(b) , то и b.equals(a) .
- Рефлексивность: если a != null , то справедливо a.equals(a) .
- Транзитивность: для трех различных ссылок a, b и c — если a.equals(b) и b.equals(c) , то справедливо выражение a.equals(c) .
У метода equals() есть большой минус – он слишком медленно работает. Для этого был придуман метод hashCode(). Для каждого объекта данный метод возвращает определенное число. Какое именно – это тоже решает разработчик класса, как и в случае с методом equals().
Вместо того чтобы сравнивать объекты, будем сравнивать их hashCode , и только если hashCode-ы равны, сравнивать объекты посредством equals() .
Разработчик, который реализует функцию hashCode() , должен помнить следующее:
1) у двух разных объектов может быть одинаковый hashCode;
2) у одинаковых объектов (с точки зрения equals()) должен быть одинаковый hashCode;
3) хеш-коды должны быть выбраны таким образом, чтобы не было большого количества различных объектов с одинаковыми hashCode . Ситуация, когда у различных объектов хеш-коды совпадают называется коллизией.
Важное замечание: при переопределении метода equals(), обязательно нужно переопределить метод hashCode() , с учетом трех вышеописанных правил (Переопределил equals — переопредели и hashCode ).
Дело в том, что коллекции в Java перед тем как сравнить объекты с помощью equals всегда ищут/сравнивают их с помощью метода hashCode() . И если у одинаковых объектов будут разные hashCode , то объекты будут считаться разными — до сравнения с помощью equals() просто не дойдет.
Правило: переопределяя hashCode(), переопределяй и equals()
Тот вопрос, который неустанно задают на всех собеседованиях. Естественно, ответ на него нужно знать, чтобы защититься от непонятных ошибок в программе.
При написании собственных программ, чтобы избежать недоразумений, следует соблюдать некий контракт между методами hashCode() и equals() :
Одинаковые ( a.equals(b) == true ) объекты должны иметь одинаковый хеш-код.
Разные объекты ( a.equals(b) == false ) объекты могут иметь одинаковый хеш-код, а могут иметь и разный.Мы создаем два объекта Book и контейнер HashMap, в котором мы собираемся хранить книги в качестве ключей, а в качестве значений — количество экземпляров этих книг на складе.
И вот что интересно — книга «Alice in Wonderland» есть в контейнере, но метод get() вместо значения 150 возвращает нам null!
Давайте вызовем метод contains(), чтобы проверить, есть ли такой элемент в HashMap:System.out.println(books.containsKey(new Book("1110987654321", "Alice in Wonderland", "Carroll")));
Вызов вернет false . Но мы же его туда положили, чем тогда вызвана данная проблем?
HashMap — это ассоциативный контейнер, реализующий идею хеш-таблицы. Изнутри он состоит из массива объектов Entry. Для каждого нового элемента рассчитывается его уникальный индекс в массиве, а для этого как раз и используется метод hashCode() .
Поэтому и важно, чтобы для одинаковых объектов он был одинаковым. У двух разных объектов может совпасть значение хеш-кода. Такая ситуация называется коллизией. В Java она решается методом цепочек. Объект Entry на самом деле хранит внутри себя не один элемент, а целый связный список, куда продолжаются добавляться элементы с одинаковым хеш-кодом в случае коллизий.
При вызове метода get() у HashMap поиск ведется линейно в этом списке. Искомый элемент сравнивается с элементами в списке при помощи вызова метода equals().
Теперь вспомним, что hashCode(), если он не переопределен, по умолчанию возвращает случайное значение, обозначающее адрес элемента в памяти. Поэтому хеш-код у всех объектов в примере различен, несмотря на то, что содержимое у них одно и то же.
Теперь не забудьте переопределить метод hashCode() согласно контракту так, чтобы для одинаковых объектов всегда возвращался один и тот же хеш-код, например, используя то же внутреннее значащее состояние объекта, что и в определении метода equals() .
В то время как для разных объектов хеш-код по-прежнему может случайно совпасть. В терминах хеш-таблиц такая ситуация называется коллизией.
В идеале было бы хорошо избегать таких случаев и сделать так, чтобы для разных объектов всегда возвращался разный хеш-код. Но в общем случае это сделать невозможно, поэтому достаточно выбрать просто достаточно хорошую хеш-функцию:
Данный подход один из самых распространенных. Мы просто складываем хеш-коды всех полей объекта с суммарным значение хеша, полученном на предыдущем шаге и умноженным на константу. В итоге каждое значащее поле вносит свой вклад в значение хеша.
Теперь переопределите метод hashCode() в вашем классе Book как в примере выше и перезапустите весь пример. Теперь книга будет найдена в контейнере и на экран выведется значение 150.
Другие материалы
Более подробно по некоторым разделам статьи можно прочитать на следующих страницах: