Pull to refresh
827.97
OTUS
Цифровые навыки от ведущих экспертов

Все о переопределении в Java

Reading time 14 min
Views 63K
Original author: Madhusudana Singana
Всем доброго!

У нас на этой неделе практически юбилей — стартует пятая группа "Разработчик Java", а это значит, что мы снова делимся всякими полезностями.

Поехали.

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

Дочерний класс может переопределить методы экземпляра своего родительского класса. Это называется переопределением метода. Сигнатура (тип возврата, тип параметров, количество параметров и порядок параметров) должна быть такой же, какой была определена в родительском классе. Переопределение метода выполняется для достижения полиморфизма во время выполнения программы.



Что такое полиморфизм?

Полиморфизм позволяет вам определить один интерфейс и иметь для него несколько реализаций. Это один из основных принципов объектно-ориентированного программирования. Слово «полиморфизм» буквально означает «состояние наличия многих форм» или «способность принимать разные формы». При применении к объектно-ориентированным языкам программирования, таким как Java, он описывает способность языка обрабатывать объекты разных типов и классов через единый, однородный интерфейс.

Что такое полиморфизм времени выполнения (или отправка динамического метода?)

Переопределенный метод вызывается в соответствии с объектом, которому принадлежит метод, а не по типу ссылки.

В чём польза полиморфизма времени выполнения?

Статический или динамический полиморфизм?

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

Использование Override

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

Кроме того, начиная с Java 1.6 вы можете воспользоваться Override с теми же целями, чтобы отметить, когда метод реализует интерфейс.

Правила динамического полиморфизма

Изменение сигнатуры метода

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

package com.me.overriding;
class Parent_params {
    void method(String a, String b, int v, float d) {
        System.out.println("parent");
    }
    void method2(String a, String b, int v) {
        System.out.println("parent");
    }
    void method3(String a, int v) {
        System.out.println("parent");
    }
}
class Child_Params extends Parent_params {
    //Если мы хотим переопределить дочерний класс, 
    //вам нужно иметь такое же количество параметров, что и родительский
    //НЕПРАВИЛЬНО...
    /*@Override
    void method(String a){
    System.out.println("child");
    }*/
    //Порядок параметров должен также совпадать с родительским
    //НЕПРАВИЛЬНО
    /*@Override
    void method(String a,float d,int a,String b){
    System.out.println("parent");
    }*/
    //Override
    @Override
    void method(String a, String b, int v, float d) {
        System.out.println("child");
    }
    //Перегрузка
    //Мы можем определить метод таким образом
    void method(String a, String b, int v) {
        System.out.println("child");
    }
    void method2(String a, int b, String v) {
        System.out.println("child");
    }
    void method3(int v, String a) {
        System.out.println("child");
    }
}
public class Override_Params {
    public static void main(String[] args) {
        String temp = "Monday";
        Child_Params child_Params = new Child_Params();
        /*Parent_params params=new Child_Params();
        child_Params=(Child_Params) params;
        child_Params.method(temp,temp,2);*/
        Parent_params params2 = new Parent_params();
        child_Params = (Child_Params) params2;
        child_Params.method(temp, temp, 2);
        /*up vote
        3
        down vote
        accepted
        Создав объект, вы не можете изменить его тип. 
        Вот почему вы не можете привести тип Animal к типу Dog.
        Однако, если вы создаете объект подкласса, 
        вы можете сохранить ссылку на него в переменной типа суперкласса,
        и позже вы можете применить его к типу подкласса.
        Это будет работать : 
        Animal a = new Dog ();
        Dog d = (Dog) a;*/
    }
}

Тип возврата метода

Типы возвращаемых данных могут варьироваться в зависимости от методов, которые переопределяют друг друга, если типы возврата являются ссылочными типами. Java поддерживает ковариантные возвращения — специализацию типа возврата к подтипу. Объявление метода d1 с типом возврата R1 заменит возвращаемое значение метода d2 с возвращаемым типом R2 тогда и только тогда, когда выполняются следующие условия:

  • Если R1 void, то R2 является void.
  • Если R1 является примитивным типом, то R2 идентичен R1.
  • Если R1 является ссылочным типом, то: R1 является либо подтипом R2, либо R1 может быть преобразован в подтип R2 путем unchecked преобразования или
  • R1 = | R2 |

package com.me.overriding;
class Parent1 {
    public void method(String string) {
        System.out.println("Parent   :" + string);
    }
    public void method2(String string) {
        System.out.println("Parent   :" + string);
    }
}
class Child1 extends Parent1 {
    //Тип возврата несовместим с Parent1.method (String)
    @Override
    public int method(String string) {
        System.out.println("Child   :" + string);
    }
}
public class OverrideAllMethods {
    public static void main(String[] args) {
        Child1 child1 = new Child1();
        child1.method("Me");
    }
}

Ковариантный тип возврата

Возвращение коварианта означает, что при переопределении метода возвращаемый тип переопределяющего метода разрешен как подтип возвращаемого типа переопределенного метода.

Чтобы прояснить это с помощью примера, общим случаем является Object.clone (), который объявляется для возврата типа объекта. Вы можете переопределить это в своем классе следующим образом:


package com.me.overriding;
class ParentCustomClass {
    public Object m1() {
        System.out.println(" parent m1()");
        return null;
    }
    public ParentCustomClass m2() {
        System.out.println(" parent m2()");
        return null;
    }
}
class ChildtCustomClass extends ParentCustomClass {
    @Override
    public String m1() {
        System.out.println("child m1()");
        return null;
    }
    @Override
    public ChildtCustomClass m2() {
        System.out.println(" child m2()");
        return null;
    }
}
public class CoVarientTypes {
    public static void main(String[] args) {
        ParentCustomClass class1 = new ChildtCustomClass();
        class1.m1();
        class1.m2();
        /*Объявление метода d1 с типом возврата R1 является тип-возврата-заменяемым                          
        для другого метода d2 с типом возврата R2, тогда и только тогда, когда  
        выполняются следующие условия:
        Если R1 является примитивным типом, то R2 идентичен R1.
        Если R1 недействителен, то R2 является недействительным.
        Если R1 является примитивным типом, то R2 идентичен R1.
        Если R1 является ссылочным типом, тогда:
        R1 является либо подтипом R2, либо R1 может быть преобразован
        в подтип R2 путем беспрепятственного преобразования или R1 = |R2|*/
    }
}

Переопределение статического метода (или) Связывание метода

package com.me.overriding;
class ParentStatic {
    public static void method(String string) {
        System.out.println("Parent   :" + string);
    }
    public void method2(String string) {
        System.out.println("Parent   :" + string);
    }
}
class ChildStatic extends ParentStatic {
    public static void method(String string) {
        System.out.println("Child   :" + string);
    }
}
public class StaticMethodOverriding {
    public static void main(String[] args) {
        /*1)Для методов класса (или статических методов) вызывается метод в 
        соответствии с типом ссылки, которое означает, что вызов метода
        определяется во время компиляции
        2) Для методов экземпляра (или нестатических методов), метод вызывается в 
        соответствии с типом передаваемого объекта, а не по типу ссылки,
        что означает, что вызовы метода разрешаются во время выполнения. */
        ChildStatic.method("Me");
        ParentStatic parentStatic = new ChildStatic();
        parentStatic.method("Me");
    }
}

Связывание статических переменных

package com.me.overriding;
class Dad {
    private static final String me = "dad";
    protected String getMe() {
        return me;
    }
    public void printMe() {
        System.out.println(getMe());
    }
}
class Son extends Dad {
    private static final String me = "son";
    @Override
    protected String getMe() {
        return me;
    }
}
class StaticVariableOverriding {
    public static void main(String[] args) {
        Dad dad = new Son();
        dad.printMe();
    }
}

Final и private методы

package com.me.overriding;
class Parent_2 {
   public final void m1() {
       System.out.println("m1()");
   }
   private void m2() {
       System.out.println("m2() parent");
   }
}
// Невозможно переопределить конечный метод из Parent_2
class Child_2 extends Parent_2 {
   public fianl void m1() {
       System.out.println("m1()");
   }
   private void m2() {
       System.out.println("m2() of child");
   }
}
public class FinalandprivateOverriden {
   public static void main(String[] args) {
       Parent_2 child_2 = new Child_2();
       child_2.m1();
      // Метод m2 () из типа Parent_2 не отображается
       child_2.m2();
   }
}hild_2.m2();
    }
}

Переопределение уровней доступа

package com.me.overriding;
class Parent_Access {
    protected void method(String a, String b, int v, float d) {
        System.out.println("parent");
    }
}
class Child_Access extends Parent_Access {
    //Уровни доступа не могут быть ограничительными
    /*private void method(String a,String b,int v,float d){
    System.out.println("parent");
    }*/
    public void method(String a, String b, int v, float d) {
        System.out.println("parent");
    }
}
public class Override_AccessLevels {
    public static void main(String[] args) {
    }
}

Переопределение с super()

package com.me.overriding;
class SuperParent {
    public void m1() {
        System.out.println(" super m1()");
    }
    protected void m2() {
        System.out.println(" super m2()");
    }
    private void m3() {
        System.out.println(" super m3()");
    }
}
class SuperChild extends SuperParent {
    public void m1() {
        super.m1();
        super.m2();
        System.out.println("m1()");
    }
    protected void m2() {
        System.out.println("m2()");
    }
    private void m3() {
        System.out.println(" m3()");
    }
}
public class OverridingWithSuper {
    public static void main(String[] args) {
        SuperParent parent = new SuperChild();
        parent.m1();
        parent.m2();
    }
}

Переопределение с абстракцией

package com.me.overriding;
interface Interface {
   public void m1();
}

/*Если абстрактный класс реализует вышеуказанный интерфейс,
то он не требует, чтобы подкласс определял метод move ()
как показано в следующем классе AbstractDog:*/

abstract class AbstractClass implements Interface {
   public abstract void m2();
   public void m3() {
       System.out.println("m3()");
   }
}

/*//Но если конкретный (не абстрактный) класс является подклассом класса AbstractDog или интерфейса Animal, тогда он должен переопределить все унаследованные абстрактные методы, как показано ниже:*/

class ConcreteClass extends AbstractClass {
   @Override
   public void m1() {
        // TODO Автоматически сгенерированный метод заглушки
       System.out.println("m1()");
   }
   @Override
   public void m2() {
        // TODO Автоматически сгенерированный метод заглушки
       System.out.println("m2()");
   }
   @Override
   public void m3() {
        // TODO Автоматически сгенерированный метод заглушки
       System.out.println("m3()");
   }
}
public class OverridingWithAbstraction {
   public static void main(String[] args) {
       ConcreteClass class1 = new ConcreteClass();
       class1.m1();
       class1.m2();
       class1.m3();
   }
}

Переопределение с исключениями

package com.me.overriding;
import java.io.IOException;
import java.nio.file.DirectoryIteratorException;
class ExceptionParent {
   public void m1() throws IOException {
       System.out.println("m1()");
   }
}

// Исключение несовместимо с предложением throws в ExceptionParent.m1 ()
// Это должно быть выборочное или суженное или непроверенное исключение
// CE
/* класс ExceptionChild расширяет ExceptionParent {
public void m1 () throws Exception {
System.out.println ( "m1 ()");
}*/
// CE
// IllegalArgumentException не проверяется
/* класс ExceptionChild расширяет ExceptionParent {
public void m1 () throws Exception, IllegalArgumentException {
System.out.println ( "m1 ()");
}*/
// OK bccz неконтролируемого исключения
/* класс ExceptionChild расширяет ExceptionParent {
public void m1 () throws IllegalArgumentException {
System.out.println ( "m1 ()");
}*/
/*// CE bcz of Checked
класс ExceptionChild расширяет ExceptionParent {
public void m1 () throws IOException, InterruptedException {
System.out.println ( "m1 ()");
}*/

class ExceptionChild extends ExceptionParent {
   public void m1() throws IOException {
       System.out.println("m1()");
   }
}
public class OverridingExceptions {
   public static void main(String[] args) throws IOException {
       ExceptionParent exceptionParent = new ExceptionChild();
       exceptionParent.m1();
   }
}

Переопределение из внутренних приватных классов

package com.me.overriding;
/* Java-программа, чтобы продемонстрировать, можем ли мы переопределить 
частный метод внешнего класса внутри его внутреннего класса */
public class OverrideprivateInnerclass {
    private String msg = "GeeksforGeeks";
    private void fun() {
        System.out.println("Outer fun()");
    }
    class Inner extends OverrideprivateInnerclass {
        private void fun() {
            System.out.println("Accessing Private Member of Outer: " + msg);
        }
    }
    public static void main(String args[]) {
        // Чтобы создать экземпляр класса Inner, нам нужен Outer 
        // экземпляр класса. Итак, сначала создайте экземпляр класса Outer, а затем
        // внутренний экземпляр класса.
        OverrideprivateInnerclass o = new OverrideprivateInnerclass();
        Inner i = o.new Inner();
        // Это вызовет функцию Inner, цель этого вызова -
        // показать, что к приватным членам Outer можно получить доступ в Inner.
        i.fun();
        // o.fun() calls Outer's fun (No run-time polymorphism).
        o = i;
        o.fun();
    }
}

Переопределение и перегрузка

package com.me.overriding;
class ParentStatic_1 {
    public void method(String string) {
        System.out.println("Parent   :" + string);
    }
    public void method2(String string) {
        System.out.println("Parent   :" + string);
    }
}
class ChildStatic_1 extends ParentStatic_1 {
    public static void method(String string, int b) {
        System.out.println("Child   :" + string);
    }
    public static void method2(String string, int b, float c) {
        System.out.println("Child   :" + string);
    }
}
public class OverrideAndOverloadMethods {
    public static void main(String[] args) {
        //В подклассе (или производном классе) мы можем перегрузить методы,
        //унаследованные от суперкласса. Такие перегруженные методы не скрывают 
        //и не переопределяют методы суперкласса - это новые методы, 
        //уникальные для подкласса.
        ParentStatic_1 parentStatic = new ChildStatic_1();
        parentStatic.method("Me");
    }
}

Многоуровневое переопределение

package com.me.overriding;
class ParentStatic_2 {
    public void method(String string) {
        System.out.println("Parent   :" + string);
    }
    public void method2(String string) {
        System.out.println("Parent   :" + string);
    }
}
class ChildStatic_2 extends ParentStatic_2 {
    public void method(String string) {
        System.out.println("Child  -1 :" + string);
    }
}
class ChildStatic_3 extends ChildStatic_2 {
    public void method(String string) {
        System.out.println("Child -2  :" + string);
    }
}
class ChildStatic_4 extends ChildStatic_3 {
    public void method(String string) {
        System.out.println("Child -3  :" + string);
    }
}
public class MultilevelOverriding {
    public static void main(String[] args) {
        //Нам не нужно переписывать все методы, которые есть в суперклассе.
        ParentStatic_2 parentStatic_2 = new ChildStatic_3();
        parentStatic_2.method("hey........!");
    }
}

Переопределение методов экземпляра против статических методов

package com.me.overriding;
class ParentStatic2 {
    public static void method(String string) {
        System.out.println("Parent   :" + string);
    }
    public void method2(String string) {
        System.out.println("Parent   :" + string);
    }
}
class ChildStatic2 extends ParentStatic {
    //Этот статический метод не может скрыть метод экземпляра из ParentStatic
    public void method(String string) {
        System.out.println("Child   :" + string);
    }
    //Этот статический метод не может скрыть метод экземпляра из ParentStatic
    public static void method2(String string) {
        System.out.println("Child   :" + string);
    }
}
public class InstanceVsStaticOverriding {
    public static void main(String[] args) {
        //Метод экземпляра не может переопределить статический метод,
        //и статический метод не может скрыть метод экземпляра.
        ParentStatic2 parentStatic = new ChildStatic2();
        parentStatic.method("Me");
    }
}

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

package com.me.overriding;
class B
{
     int a=10;
     public void print()
     {
         System.out.println("inside B super class");
     }
}
 class C extends B
 {
     int a=20;
     public void print()
     {
         System.out.println("inside C sub class");
     }
 }
public class InstanceVariableOverriding  {
    public static void main(String[] args) {
        B b=new C();
        b.print();//it will print inside c sub class
        System.out.println(b.a);//it will print super class variable value=10
      /*  
        Почему переменная экземпляра суперкласса не переопределяется в методе  
        подкласса, см. мой код ниже ...
        Потому что переменные экземпляра не могут быть переопределены в Java.
        В Java могут быть переопределены только методы.
        Когда вы объявляете поле с тем же именем, что и существующее поле в 
        суперклассе, новое поле скрывает существующее поле. Существующее поле из 
        суперкласса все еще присутствует в подклассе и может даже использоваться 
        ... с учетом обычных правил доступа к Java. */
    }
}

Переопределение конструктора

package com.me.overriding;
class One {
    public One() { // Конструктор суперкласса
    }
    One(int a) { // Перегрузка конструктора суперкласса
    }
}
class Two extends One {
    /* {
      //One() {    // это метод не конструктор
         // потому что имя не должно совпадать с именем класса
     }*/
    Two() { // конструктор подкласса
    }
    Two(int b) { // перегрузка конструктора подкласса
    }
}
public class ConstructorOverriding {
    /*Это невозможно. Переопределение конструктора невозможно на Java.
    Это связано с тем, что конструктор выглядит как метод, но с именем класса
    и без возвращаемого значения.
    Переопределение означает что то, что мы объявили в классе Super, именно это мы
    и должны объявить в Sub-классе. Имя суперкласса и имена подкласса различны.
    Если вы попытаетесь написать Super Class Constructor в Sub-классе, тогда
    Sub-класс будет рассматривать это как метод не конструктор, потому что имя не 
    должно совпадать с именем Sub-класса. И это даст ошибку компиляции, что методы
    не имеют возвращаемого значения. Поэтому мы должны объявить как void, только 
    тогда она будет компилироваться. */

}

Конструктор с super()

package com.me.overriding;
class Superclass {
   public Superclass(int x) {
       System.out.println("000000");
   }
   public Superclass() {
       System.out.println("  ty");
   }
   public Superclass(String y) {}
}
class Subclass extends Superclass {
   public Subclass() {
      // super (5); // цепочка к Superclass(int) конструктору
   }
}
public class ConstructorWithSuper {
   public static void main(String[] args) {
       new Subclass();
   }
}

Переопределение другого и того же пакетов

package com.me.overridingtest;
import com.me.overriding.PackageParent;
public class Child extends PackageParent {
    @Override
    public void m1() {
        System.out.println("m1()");
    }
    @Override
    protected void m2() {
        System.out.println("m2()");
    }
    //Методы default разрешены только в интерфейсе
    /*default void m3(){
    System.out.println("m3()");
    }*/
    //Подкласс в другом пакете может переопределять только не final
    //методы объявленные как public или protected.
    @Override
    private void m4() {
        System.out.println("m1()");
    }
}
package com.me.overriding;
import com.me.overridingtest.Parent;
public class OtherPackageOverride {
    public static void main(String[] args) {
        Parent parent = new com.me.overridingtest.Child();
        parent.m1();
        parent.m2();
    }
}
package com.me.overriding;
public class PackageParent {
    public void m1() {
        System.out.println("m1()");
    }
    protected void m2() {
        System.out.println("m2()");
    }
    //Методы default разрешены только в интерфейсе
    /*default void m3(){
    System.out.println("m3()");
    }*/
    private void m4() {
        System.out.println("m1()");
    }
}

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

// Правила ребенок-родитель с отношением IS-A:
package com.me.overriding;
class Parent {
   public void method(String string) {
       System.out.println("Parent   :" + string);
   }
   public void method2(String string) {
       System.out.println("Parent   :" + string);
   }
}
class Child extends Parent {
   @Override
   public void method(String string) {
       System.out.println("Child   :" + string);
   }
}
public class ChildParenRule {
   public static void main(String[] args) {
       // Все хорошо, он распечатает
       // Parent: Me
       // Parent parent=new Parent();
       // parent.method("Me");
       // Если мы прокомментируем метод в Parent и удалим переопределение в дочернем
       // Он бросает CE
       // если мы отбросим его к ребенку, получим RE
       // Исключение в потоке "main"  
       // java.lang.ClassCastException: com.me.overriding.Parent
       // не может быть добавлен в com.me.overriding.Child
       // at com.me.overriding.ChildParenRule.main (ChildParenRule.java:19)
       // Parent parent=new Child();
       // parent.method("Me");
       // Вызов дочернего метода Метод, если не найдены вызовы
       // Метод родительского класса
       // Child child = new Child ();
       // child.method("Me ");
       // Хотя тип ссылки - это переопределение родительского элемента, 
       // происходит на RE
       // Он будет принимать в качестве дочернего объекта и вызывает метод child
       // если нет, он будет искать метод родительского класса
       // Parent parent=new Child();
       // parent.method("Me");
       // CE как несоответствие типа: невозможно преобразовать из родителя в ребенка
       // Мы не можем преобразовать родительский элемент в дочерний класс
       // Исключение в потоке "main" java.lang.Error: 
       // нерешенные проблемы компиляции:
       // Ошибка синтаксиса, вставьте «VariableDeclarators» для завершения 
       // LocalVariableDeclaration
       // Ошибка синтаксиса, вставить «;» для завершения 
       // LocalVariableDeclarationStatement
       // at com.me.overriding.ChildParenRule.main (ChildParenRule.java:53)
       // Child child = (Child) new Parent ();
       // child.method("Me ");
       //
       /* 6
       down vote
       Родительский класс не должен знать о дочерних классах.
       Parent parent=new Parent();
       ((Child)parent).method("Me");*/

   }
}

Методы экземпляра предпочтительнее default методов интерфейса.

package com.me.overriding;
// Методы экземпляров предпочтительнее, чем методы интерфейса по умолчанию.
// Рассмотрим следующие классы и интерфейсы:

class Horse1 {
   public String identifyMyself() {
       return "I am a horse.";
   }
}
interface Flyer {
   default public String identifyMyself() {
       return "I am able to fly.";
   }
}
interface Mythical {
   default public String identifyMyself() {
       return "I am a mythical creature.";
   }
}
public class Pegasus extends Horse1 implements Flyer, Mythical {
   public static void main(String...args) {
       Pegasus myApp = new Pegasus();
       System.out.println(myApp.identifyMyself());
   }
}
// Метод Pegasus.identifyMyself возвращает строку I am a horse.

Программы, содержащие комментарии для иллюстрации использования и последствий при переопределении, могут иметь некоторые CE и RE.

THE END

Как всегда будем рады видеть ваши комментарии или вопросы.
Tags:
Hubs:
+14
Comments 5
Comments Comments 5

Articles

Information

Website
otus.ru
Registered
Founded
Employees
101–200 employees
Location
Россия
Representative
OTUS