Система объектно-ориентированного программирования (ООП) – это концепция программирования, основанная на принципах объектов, лежащих в основе вашей программы. ООП лежит в основе Java, и поэтому лучше всего понять его основные принципы, прежде чем вы начнете писать даже простые программы на Java. Поэтому начнем с обсуждения концепции ООП.

Если вы новичок в программировании на Java, я бы порекомендовал вам изучить некоторые основы, такие как класс, объект, метод, модификаторы доступа, интерфейс и т. д., что облегчит и упростит понимание следующей концепции oops.

1. Инкапсуляция:

Процесс группировки/связывания элементов данных (переменных) и соответствующих методов в единое целое называется инкапсуляцией. Если какой-либо компонент следует сокрытию данных и абстракции, то говорят, что компонент инкапсулирован. Принцип инкапсуляции говорит о сокрытии данных за методом. Каждый класс Java является примером инкапсуляции.

Итак, давайте посмотрим, что скрывают эти данные и абстрагируются в деталях.

i) Сокрытие данных: постороннее лицо не может напрямую получить доступ к внутренним данным, эта концепция OOPS называется сокрытием данных. После успешной проверки/аутентификации постороннее лицо может получить доступ к нашим внутренним данным. Основное преимущество - Безопасность.

Как реализовать скрытие данных в Java?

Мы можем реализовать скрытие данных, объявив элементы данных (переменные) как private.

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

public class Account {
// Data Hiding (Outside class cannot access balance as it is private //data member.)
private double balance ;
public double getBalance() {
   Scanner scan = new Scanner(System.in);
   System. out.println("Provide your Username" );
   String Username = scan.nextLine();
   System. out.println("Provide Your password" );
   String Password = scan.nextLine();
if (Username .equals("Adwet" ) && Password.equals( "Deep")) {
balance = 3500000;
} else {
System. out.println("Invalid username or password" );
}
return balance ;
}
}
//Driver Class
public class GetBalance {
  public static void main(String[] args) {
     Account acnt = new Account();
     double d = acnt.getBalance(); //accessing getBalance method, //doing certain validation only we will be able to fetch balance.
     System. out.print("The balance is: " +d );
}
}

ii) Абстракция:

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

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

Пример: используя экран графического интерфейса, банкомат предоставляет нам набор услуг, тогда как внутренняя реализация не выделена.

Преимущество инкапсуляции:безопасность, расширение становится проще, ремонтопригодность, модульность и т. д.

Недостаток инкапсуляции: она увеличивает количество строк кода из-за геттера, метода установки, логики проверки и т. д., и, следовательно, требует много времени, а производительность снижается.

Типично инкапсулированный класс:Каждая переменная, представленная в классе, является закрытой, тогда такой тип класса называется плотно инкапсулированным классом. Если родительский класс не инкапсулирован строго, то ни один дочерний класс не инкапсулирован.

2. Наследование

Наследование также известно как отношение IS-A, согласно которому все методы и переменные, доступные в родительском классе, доступны в дочернем классе, но не наоборот. Используя ключевое слово extends, мы можем реализовать наследование. Пример. В Java класс Object является родительским классом для всех остальных классов.

Определенное правило наследования:

public class DemoInheritance {
public static void main(String[] args) {
// Rule 1. Parent reference can be used to hold Parent object. And able to access only the parent class methods.
Parent p = new Parent();
   p.m1(); // correct
   p.m2(); // Compile time error: The method m2() is undefined for //the type Parent
//Rule 2. Child reference can be use to hold child object and able to access both parent and child class methods.
Child c = new Child();
c.m1();
c.m2();
//Rule 3. Parent reference can hold child class object
Parent po = new Child(); //(correct)
  po.m2(); //but method available in child still we will not be able to access. Compile time error: The method m2() is undefined for the type Parent.
//Rule 4. Child reference can not hold parent class object
Child cc = new Parent(); // Compile Time error: Type mismatch: cannot convert from Parent to Child
}
}
class Parent {
void m1() {
System.out.print("Parent method running" );
}
}
class Child extends Parent {
void m2() {
System. out.print("Child method running" );
}
}

Типы наследования:

Доступны 5 типов концепций наследования.

  1. Единое наследование: один дочерний класс расширяет один родительский класс.
  2. Многоуровневое наследование: один дочерний класс расширяет другой дочерний класс, который расширяет родительский класс.
  3. Иерархическое наследование: несколько дочерних классов расширяют один родительский.
  4. Гибридное наследование: комбинация наследования выше 4.
  5. Множественное наследование: один дочерний класс расширяет несколько родительских классов. Это не поддерживается Java, потому что если два класса содержат два метода с одинаковыми именами, то, когда дочерний класс расширяет оба родительских класса после множественного наследования, возникает неоднозначность, какой метод класса выбрать. Следовательно, Java не поддерживает множественное наследование и гибридное наследование в классе. Эта проблема также называется проблемой алмазного доступа. Однако в случае с интерфейсом в java поддерживается множественное наследование, потому что в случае наследования каждый класс просто содержит объявление, а соответствующий реализованный класс только реализует метод, поэтому двусмысленность исключена.

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

3. Полиморфизм

Поли означает много, морфы означают формы. Множественные формы одного имени называются полиморфизмом. Переопределение и Перегрузка являются примерами полиморфизма.

В Java доступны два типа полиморфизма:

  1. Статический полиморфизм/полиморфизм времени компиляции/раннее связывание: это концепция перегрузки.
  2. Динамический полиморфизм/полиморфизм времени выполнения/позднее связывание: это концепция переопределения.

i) Перегрузка. Методы с одинаковым именем метода, но разным типом аргумента — это концепция перегрузки метода Java. При перегрузке компилятор заботится о разрешении метода (какой метод выполнять) на основе ссылочного типа, а не на основе объекта времени выполнения. Следовательно, это также называется полиморфизмом времени компиляции/ранним связыванием/статическим полиморфизмом.

Parent p = new Child();
p.m1(); // Reference type class that is Parent class m1() gets //executed and not the runtime object class(i.e Child class m1())

Правила чрезмерной загрузки:

Правило 1: при перегрузке, если метод с точным соответствием недоступен, компилятор не выдаст ошибку времени компиляции сразу, он переместит аргумент к следующему типу данных, и если метод совпадения получает затем он обеспечивает этот вывод. Например. В приведенной ниже программе t.m1(‘a’); из типа данных Char в компилятор перейдет к типу int и выполнит метод m1(int i). Эта концепция называется автоматическим продвижением при перегрузке.

Порядок продвижения типа данных:

byte → short → int → long → float → double

char → int → long → float → double

class DemoOverloading {
public void m1() {
System. out.println("No argument method executed." );
}
public void m1(int i) {
System. out.println("Int argument method executed." );
}
public void m1(float f) {
System. out.println("Float argument method executed." );
}
public static void main(String[] args) {
DemoOverloading dd = new DemoOverloading();
dd.m1(); // O/P: No argument method executed.
dd.m1(10); // O/P: Int argument method executed.
dd.m1(323.89f); // O/P: Float argument method executed.
dd.m1( 'a'); // O/P: Int argument method executed. ----------> Here //the compiler promoted char to int
//dd.m1(10.5);  //O/P: The method m1( int) in the type //DemoOverloading is not applicable for the arguments (double). 
//------>if promotion is not possible like here 10.5 is double. //double to next level promotion is not possible hence we are //getting compile time error.
}
}

Правило 2. При перегрузке точное соответствие имеет более высокий приоритет. Если доступны и родительский, и дочерний классы, то первый компилятор проверяет дочерний класс, и если аргумент доступен в дочернем классе, он выполняет дочерний класс. Например. в приведенной ниже программе dd.m1(null). «null» доступен как в классе Object, так и в классе String, но поскольку дочерний класс получает наивысший приоритет, следовательно, выполняется метод m1 (String s). Если дочерний класс недоступен или находится в дочернем классе, аргумент недоступен, тогда выполняется родительский класс.

class DemoOverloading2 {
public void m1(Object o) {
System. out.println(“Object version method executed.” );
}
public void m1(String s) {
System. out.println(“String version method executed.” );
}
public static void main(String[] args) {
DemoOverloading2 dd = new DemoOverloading2();
dd.m1( new Object()); // o/p: Object version method executed.
dd.m1( “Test”); // o/p: String version method executed.
dd.m1( null); // o/p: String version method executed.
//Here String argument method is executed because it is the child //class of object. If child class doesn’t match the argument then //only we will go for parent class
}
}

Правило 3: Если нет связи между аргументами, компилятор не может решить, какой метод выполнять, и выдает ошибку. Пример: String и StringBuffer (оба являются дочерними классами класса Object, но не имеют отношения друг к другу). Следовательно, компилятор получает неоднозначность для dd.m1(null); и выдает ошибку времени компиляции.

class DemoOverloading3 {
public void m1(int i) { // general method 1.0 ver
System. out.println("General method executed." );
} 
public void m1(int... a) { // var argument method, (any number of //int argument is accepted.)
System. out.println("Var argument method executed." );
}
public static void main(String[] args) {
DemoOverloading3 dd = new DemoOverloading3();
dd.m1(12); // o/p: General method executed.
dd.m1(90,89,0); // o/p: Var argument method executed.
dd.m1(); // o/p: Var argument method executed.
}
}

ii) Переопределение: в java все методы, доступные в родительском классе, доступны для дочернего класса, но если дочерний класс не удовлетворен методом, то дочерний класс переопределяет/переписывает метод, доступный в родительском классе, эта концепция называется переопределением. Метод родительского класса, который переопределяется, называется Переопределенный метод, тогда как метод дочернего класса, который переопределяется, называется методом переопределения. В переопределяющем методе разрешение всегда выполняется JVM на основе объекта времени выполнения, а не на основе ссылочного типа. Следовательно, переопределение также считается полиморфизмом времени выполнения/поздним связыванием/динамическим полиморфизмом.

Parent p = new Child();
p.m1(); //Object type class that is Child class m1() gets executed //and not the reference type object class(i.e //Parent class m1())

Правила переопределения:

Правило 1:

Подпись метода должна быть одинаковой. (Имя метода и тип аргумента, а также последовательность типов аргументов должны быть одинаковыми.) Допускается одинаковый или ковариантный тип возвращаемого значения. Ковариантный возвращаемый тип означает, что возвращаемый тип дочернего класса не обязательно должен совпадать с возвращаемым типом родительского класса, его дочерние типы также разрешены.

class Parent1 {
public Object m1() { // same method signature
return null ; // return type null is Object type.
}
}
class Child1 extends Parent1{
public String m1() { // same method signature
return null ; // return type null is String type. String is child //class of Object class.
}

Правило 2:

Частные методы видны/доступны только внутри класса, поэтому частные методы родительского класса не видны дочернему классу. Таким образом, концепция переопределенияне применима к закрытым методам.

Правило 3:

Финальный метод в родительском классе не может быть переопределен.

Правило 4: При переопределении область действия метода не может быть уменьшена, допускается увеличение области действия. Если метод дочернего класса выдает какое-либо проверенное исключение, то обязательно метод родительского класса должен использовать то же проверенное исключение или его родительское исключение. В противном случае мы получим ошибку времени компиляции, говорящую о том, что метод не может быть переопределен. Для непроверенного исключения ограничений нет. Дочерний класс может генерировать любое количество непроверенных исключений.

Правило 5. Методы родительского и дочернего классов должны быть нестатическими, чтобы добиться переопределения методов. Разрешение метода выполняется JVM на основе объекта типа запуска. Примечание. Если методы родительского и дочернего классов являются статическими, это называется скрытием метода. Здесь компилятор отвечает за разрешение метода на основе ссылочного типа.

Правило 6. Метод Var arg — это метод, который может принимать любое количество аргументов. Метод var arg может быть переопределен только другим методом var arg (но не обычным методом).

class Parent2 {
public void m1(int… i) { // same method signature
System. out.println(“Returning var arg method of parent.” );
}
}
class Child2 extends Parent1 {
public void m1(int i) { // Not same method signature, hence it //become overloading and not overriding. And hence it becomes //compile time polymorphism.
System. out.println(“Returning var arg method of parent.” );
}
class Child3 extends Parent1{
public void m1(int… i) { // same method signature as both parent and //child are var argument method, hence it become overriding. Hence //it become run time polymorphism.
System.out.println( “Returning var arg method of parent.”);
}

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

public class DemoOverriding {
public static void main(String[] args) {
Parent1 p = new Parent1();
System. out.println(p .s ); // o/p: Parent
Child1 c = new Child1();
System. out.println(p .s ); // o/p: Child
Parent1 pc = new Child1();
System. out.println(p .s ); // o/p: Parent
}
}
class Parent1 {
String s = “Parent”;
}
class Child1 extends Parent1{
String s = “Child”;
}

Давайте закончим концепцию полиморфизма кратким примечанием о свойствах перегрузки и переопределения.

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

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