Herencia de interfaz en Java con ejemplos

La herencia es un pilar importante de OOPs (Programación Orientada a Objetos) . Es el mecanismo en java por el cual una clase puede heredar las características (campos y métodos) de otra clase. Al igual que una clase , una interfaz puede tener métodos y variables, pero los métodos declarados en una interfaz son abstractos por defecto (solo firma de método, sin cuerpo).

En este artículo, entenderemos cómo se usa el concepto de herencia en la interfaz.

Una interfaz es un conjunto de especificaciones o declaraciones que definen lo que puede hacer una clase sin especificar cómo lo hará. La interfaz es siempre abstracta. Una clase concreta debe implementar todos los métodos abstractos especificados en la interfaz. Java no admite el concepto de herencias múltiples para evitar el problema del diamante que se encuentra en C++ sin usar una clase base virtual. Sin embargo, Java admite la herencia de múltiples interfaces donde una interfaz se extiende a más de una superinterfaz. La siguiente es la sintaxis utilizada para extender múltiples interfaces en Java:

access_specifier interface subinterfaceName extiende superinterface1, superinterface2, …… {

// Cuerpo
}

El siguiente es un ejemplo que implementa las herencias múltiples en las interfaces:

Java

// Java program to demonstrate
// the multiple inheritance
// in interface
  
// Interface to implement the
// addition and subtraction methods
interface Add_Sub {
    public void add(double x, double y);
    public void subtract(double x, double y);
}
  
// Interface to implement the
// multiplication and division
interface Mul_Div {
    public void multiply(double x, double y);
    public void divide(double x, double y);
}
  
// Calculator interface which extends
// both the above defined interfaces
interface Calculator extends Add_Sub, Mul_Div {
    public void printResult(double result);
}
  
// Calculator class which
// implements the above
// interface
public class MyCalculator implements Calculator {
  
    // Implementing the addition
    // method
    public void add(double x, double y)
    {
        double result = x + y;
        printResult(result);
    }
  
    // Implementing the subtraction
    // method
    public void subtract(double x, double y)
    {
        double result = x - y;
        printResult(result);
    }
  
    // Implementing the multiplication
    // method
    public void multiply(double x, double y)
    {
        double result = x * y;
        printResult(result);
    }
  
    // Implementing the division
    // method
    public void divide(double x, double y)
    {
        double result = x / y;
        printResult(result);
    }
  
    // Implementing a method
    // to print the result
    public void printResult(double result)
    {
        System.out.println(
            "The result is : " + result);
    }
  
    // Driver code
    public static void main(String args[])
    {
  
        // Creating the object of
        // the calculator
        MyCalculator c = new MyCalculator();
        c.add(5, 10);
        c.subtract(35, 15);
        c.multiply(6, 9);
        c.divide(45, 6);
    }
}
Producción:

The result is : 15.0
The result is : 20.0
The result is : 54.0
The result is : 7.5

Relación superinterfaz-subinterfaz: una variable de referencia de tipo de subinterfaz se puede asignar a una variable de referencia de tipo de superinterfaz. Consideremos un ejemplo en el que tenemos una interfaz llamada animal que se extiende por una interfaz mamífero. Tenemos una clase llamada caballo que implementa la interfaz animal y una clase llamada humano que implementa mamífero. Cuando asignamos el mamífero a un animal, compila sin ningún error porque un mamífero también es un animal. Eso es:

// Animal Interface
public interface Animal {
    // body
}
  
// Mammal interface which extends
// the Animal interface
public interface Mammal extends Animal {
    // body
}
  
// Horse which implements the
// Animal
class Horse implements Animal {
    // body
}
  
// Human which implements the
// mammal
class Human implements Mammal {
    // body
}
  
public class GFG {
    public static void main(String args[])
    {
        Animal a = new Horse();
        Mammal m = new Human();
  
        // This compiles without any error
        // because mammal is also an animal,
        // subtype reference variable m
        // is assigned to the super type
        // reference variable  a
        a = m;
    }
}

¿Cómo afecta la herencia múltiple a las variables?

Hay dos casos posibles al heredar las variables definidas en una interfaz a otras. Están:

  • Caso 1: una subinterfaz hereda todas las variables constantes de la superinterfaz. Si una subinterfaz declara una variable constante con el mismo nombre que las constantes heredadas, la nueva variable constante oculta la variable heredada independientemente del tipo. Por ejemplo:

    Java

    // Java program to demonstrate the
    // inheritance of the variables
      
    // Defining an interface X
    // with some variables
    interface X {
        int VALUE = 100;
        int h = 10;
    }
      
    // Defining the interface Y
    // with the same variables
    // as the interface X
    interface Y extends X {
        int VALUE = 200;
        String h = "Hello World";
        int sub = VALUE - X.VALUE;
    }
      
    // A class which implements the
    // above interface
    public class GFG implements Y {
      
        // Printing the values of the
        // variables
        public static void main(String args[])
        {
            System.out.println("X.VALUE = " + X.VALUE);
            System.out.println("Y.VALUE = " + Y.VALUE);
            System.out.println("sub = " + sub);
            System.out.println("X.h = " + X.h);
            System.out.println("h = " + h);
        }
    }
    Producción:

    X.VALUE = 100
    Y.VALUE = 200
    sub = 100
    X.h = 10
    h = Hello World
    
  • Caso 2: cuando una subinterfaz amplía las interfaces que tienen las variables constantes con el mismo nombre, Java da un error de compilación porque el compilador no puede decidir qué variable constante heredar. Por ejemplo:

    Java

    // Java program to demonstrate the
    // case when the constant variable
    // with the same name is defined
      
    // Implementing an interface
    // X with a variable A
    interface X {
        int A = 10;
    }
      
    // Implementing an interface
    // Y with the same variable
    interface Y {
        String A = "hi";
    }
      
    // Implementing an interface which
    // extends both the above interfaces
    interface Z extends X, Y {
        // body
    }
      
    // Creating a class which implements
    // the above interface
    public class GFG implements Z {
        public static void main(String args[])
        {
      
            // Shows compile time error if
            // the backslash is removed
            // System.out.println(Z.A);
            System.out.println(X.A);
            System.out.println(Y.A);
        }
    }
    Producción:

    10
    hi
    

¿Cómo afectan las herencias múltiples a los métodos y formas de manejar los conflictos?

Todos los métodos abstractos y predeterminados de una superinterfaz son heredados por la subinterfaz. Cuando una subinterfaz se extiende a más de una interfaz, surge un conflicto predeterminado-predeterminado o un conflicto abstracto-predeterminado . Estos conflictos son manejados por cualquiera de las siguientes reglas:

  1. Anule el método en conflicto con un método abstracto.
  2. Anule el método en conflicto con un método predeterminado y proporcione una nueva implementación.
  3. Anule el método en conflicto con un método predeterminado y llame al método predeterminado de la superinterfaz inmediata.

Conflicto predeterminado-predeterminado: el conflicto predeterminado-predeterminado surge cuando dos interfaces implementan los mismos métodos predeterminados con diferentes operaciones y el compilador no sabe qué tarea debe realizarse cuando ambas interfaces se amplían con una tercera interfaz. Por ejemplo:

// Interface one
interface one {
  
// A default method
default void
    print()
    {
        System.out.println("one");
    }
}
  
// Interface two
interface two {
  
// Another default method
// with the same name
default void
    print()
    {
        System.out.println("two");
    }
}

Podemos aplicar las reglas discutidas anteriormente para resolver el conflicto anterior. Las siguientes son las formas de manejar el conflicto:

  1. Regla 1: reemplaza el método en conflicto con un método abstracto. Por ejemplo:

    interface four extends one, two {
      
        // Implementing another method
        void print();
    }
  2. Regla 2: anule el método en conflicto con un método predeterminado y proporcione una nueva implementación. Por ejemplo:

    interface four extends one, two {
      
    // Declare another method and
    // provide an implementation
    default void
        print()
        {
            System.out.println("four");
        }
    }
  3. Regla 3: invalide el método en conflicto con un método predeterminado y llame al método predeterminado de la superinterfaz inmediata. Por ejemplo:

    interface four extends one, two {
      
    // Override the method with a
    // default method and give the
    // signature of what to perform
    default void
        print()
        {
            two.super.print();
            System.out.println("-four");
        }
    }

Conflicto abstracto-predeterminado: El conflicto abstracto-predeterminado surge cuando una interfaz implementa el método predeterminado y otra interfaz define un método abstracto con el mismo nombre. Al implementar ambas interfaces, debemos proporcionar una implementación del método, pero los métodos predeterminados no necesitan una implementación. Incluso en este caso, el compilador no sabe qué método ejecutar. Por ejemplo:

// Implementing the interface
// one with a default method
interface one {
default void
    print()
    {
        System.out.println("one");
    }
}
  
// Implementing another interface
// with an abstract method of the
// same name
interface three {
    void print();
}

Podemos aplicar las reglas discutidas anteriormente para resolver el conflicto anterior. Las siguientes son las formas de manejar el conflicto:

  1. Regla 1: reemplaza el método en conflicto con un método abstracto. Por ejemplo:

    interface five extends one, three {
        void print();
    }
    }
  2. Regla 2: anule el método en conflicto con un método predeterminado y proporcione una nueva implementación. Por ejemplo:

    interface five extends one, three {
    default void
        print()
        {
            System.out.println("five");
        }
    }
  3. Regla 3: invalide el método en conflicto con un método predeterminado y llame al método predeterminado de la superinterfaz inmediata. Por ejemplo:

    interface five extends one, three {
    default void
        print()
        {
            one.super.print();
            System.out.println("-four");
        }
    }

Anulando métodos estáticos heredados en herencia de interfaz:

En la herencia de interfaz, los métodos estáticos no se modifican durante la ejecución y no se heredan. Por lo tanto, no pueden ser anulados. Sin embargo, si una clase implementa múltiples interfaces sin tener una relación principal-secundario al proporcionar los métodos con la misma firma y la clase no anula esos métodos, se produce un conflicto. Por ejemplo:

// Interface A with an
// abstract method
interface A {
    void m();
}
  
// Interface B which doesn't
// implement the above interface
// and have the same abstract method
interface B {
    void m()
    {
        System.out.println("In B");
    }
}
  
// An Abstract class with the
// normal method M
class abstract C {
    public void m()
    {
        System.out.println("In C");
    }
}
public class test extends C
    implements A, B {
  
    public static void main(String args[])
    {
        // Creating an object of test
        test t = new test();
  
        // Here, a conflict occurs as to
        // which method needs to be called
        t.m();
    }
}

Para superar este problema hay tres reglas:

  1. Regla 1: la superclase tiene mayor prioridad que las interfaces. Esto significa que se llama al método de la superclase.
  2. Regla 2: las interfaces derivadas tienen mayor prioridad que las superinterfaces en la jerarquía de herencia. Si I1 tiene un método m1() e I2 extiende I1 y anula m1(), entonces I2 es la versión más específica y tiene mayor prioridad.
  3. Regla 3: la clase debe anular el método según sea necesario. Por ejemplo:

    public class test extends C
        implements A, B {
      
        // Overriding the conflicting
        // method
        public void m()
        {
            System.out.println("test");
        }
        public static void main(String args[])
        {
            test t = new test();
            t.m();
        }
    }

Para concluir, aunque las interfaces admiten múltiples herencias, existen pocas limitaciones y conflictos que deben manejarse.

Publicación traducida automáticamente

Artículo escrito por Shreyasi_Chakraborty y traducido por Barcelona Geeks. The original can be accessed here. Licence: CCBY-SA

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *