Java Language
Interfaces
Buscar..
Introducción
interface
. Las interfaces solo pueden contener constantes, firmas de métodos, métodos predeterminados, métodos estáticos y tipos anidados. Los cuerpos de los métodos existen solo para los métodos predeterminados y los métodos estáticos. Al igual que las clases abstractas, las interfaces no pueden ser instanciadas, solo pueden implementarse por clases o extenderse por otras interfaces. La interfaz es una forma común de lograr una abstracción completa en Java.
Sintaxis
- interfaz pública Foo {void foo (); / * cualquier otro método * /}
- interfaz pública Foo1 extiende Foo {void bar (); / * cualquier otro método * /}
- la clase pública Foo2 implementa Foo, Foo1 {/ * implementación de Foo y Foo1 * /}
Declarar e implementar una interfaz
Declaración de una interfaz usando la palabra clave de la interface
:
public interface Animal {
String getSound(); // Interface methods are public by default
}
Anular anotación
@Override
public String getSound() {
// Code goes here...
}
Esto obliga al compilador a verificar que estamos anulando e impide que el programa defina un nuevo método o arruine la firma del método.
Las interfaces se implementan utilizando la palabra clave implements
.
public class Cat implements Animal {
@Override
public String getSound() {
return "meow";
}
}
public class Dog implements Animal {
@Override
public String getSound() {
return "woof";
}
}
En el ejemplo, las clases Cat
y Dog
deben definir el método getSound()
, ya que los métodos de una interfaz son inherentemente abstractos (con la excepción de los métodos predeterminados).
Usando las interfaces
Animal cat = new Cat();
Animal dog = new Dog();
System.out.println(cat.getSound()); // prints "meow"
System.out.println(dog.getSound()); // prints "woof"
Implementando multiples interfaces
Una clase de Java puede implementar múltiples interfaces.
public interface NoiseMaker {
String noise = "Making Noise"; // interface variables are public static final by default
String makeNoise(); //interface methods are public abstract by default
}
public interface FoodEater {
void eat(Food food);
}
public class Cat implements NoiseMaker, FoodEater {
@Override
public String makeNoise() {
return "meow";
}
@Override
public void eat(Food food) {
System.out.println("meows appreciatively");
}
}
Observe cómo la clase Cat
debe implementar los métodos abstract
heredados en ambas interfaces. Además, observe cómo una clase puede implementar prácticamente tantas interfaces como sea necesario (hay un límite de 65,535 debido a la limitación de JVM ).
NoiseMaker noiseMaker = new Cat(); // Valid
FoodEater foodEater = new Cat(); // Valid
Cat cat = new Cat(); // valid
Cat invalid1 = new NoiseMaker(); // Invalid
Cat invalid2 = new FoodEater(); // Invalid
Nota:
- Todas las variables declaradas en una interfaz son
public static final
- Todos los métodos declarados en una interfaz son
public abstract
(esta declaración es válida solo a través de Java 7. Desde Java 8, se le permite tener métodos en una interfaz, que no tienen por qué ser abstractos; estos métodos se conocen como métodos predeterminados ) - Las interfaces no pueden ser declaradas como
final
- Si más de una interfaz declara un método que tiene una firma idéntica, efectivamente se trata como un solo método y no se puede distinguir de qué método de interfaz se implementa
- Se generará un archivo InterfaceName.class correspondiente para cada interfaz, luego de la compilación
Extendiendo una interfaz
Una interfaz puede extender otra interfaz a través de la extends
palabra clave.
public interface BasicResourceService {
Resource getResource();
}
public interface ExtendedResourceService extends BasicResourceService {
void updateResource(Resource resource);
}
Ahora, una clase que implemente ExtendedResourceService
deberá implementar tanto getResource()
como updateResource()
.
Extendiendo multiples interfaces
A diferencia de las clases, la extends
palabra clave puede utilizarse para ampliar varias interfaces (separados por comas) permitiendo combinaciones de interfaces en una nueva interfaz
public interface BasicResourceService {
Resource getResource();
}
public interface AlternateResourceService {
Resource getAlternateResource();
}
public interface ExtendedResourceService extends BasicResourceService, AlternateResourceService {
Resource updateResource(Resource resource);
}
En este caso, una clase que implemente ExtendedResourceService
deberá implementar getResource()
, getAlternateResource()
y updateResource()
.
Usando interfaces con genéricos
Supongamos que desea definir una interfaz que permita publicar / consumir datos hacia y desde diferentes tipos de canales (por ejemplo, AMQP, JMS, etc.), pero desea poder cambiar los detalles de la implementación ...
Definamos una interfaz de IO básica que puede reutilizarse en múltiples implementaciones:
public interface IO<IncomingType, OutgoingType> {
void publish(OutgoingType data);
IncomingType consume();
IncomingType RPCSubmit(OutgoingType data);
}
Ahora puedo crear una instancia de esa interfaz, pero como no tenemos implementaciones predeterminadas para esos métodos, necesitará una implementación cuando la creamos:
IO<String, String> mockIO = new IO<String, String>() {
private String channel = "somechannel";
@Override
public void publish(String data) {
System.out.println("Publishing " + data + " to " + channel);
}
@Override
public String consume() {
System.out.println("Consuming from " + channel);
return "some useful data";
}
@Override
public String RPCSubmit(String data) {
return "received " + data + " just now ";
}
};
mockIO.consume(); // prints: Consuming from somechannel
mockIO.publish("TestData"); // Publishing TestData to somechannel
System.out.println(mockIO.RPCSubmit("TestData")); // received TestData just now
También podemos hacer algo más útil con esa interfaz, digamos que queremos usarla para envolver algunas funciones básicas de RabbitMQ:
public class RabbitMQ implements IO<String, String> {
private String exchange;
private String queue;
public RabbitMQ(String exchange, String queue){
this.exchange = exchange;
this.queue = queue;
}
@Override
public void publish(String data) {
rabbit.basicPublish(exchange, queue, data.getBytes());
}
@Override
public String consume() {
return rabbit.basicConsume(exchange, queue);
}
@Override
public String RPCSubmit(String data) {
return rabbit.rpcPublish(exchange, queue, data);
}
}
Digamos que quiero usar esta interfaz de E / S ahora como una forma de contar las visitas a mi sitio web desde mi último reinicio del sistema y luego poder mostrar el número total de visitas. Puede hacer algo como esto:
import java.util.concurrent.atomic.AtomicLong;
public class VisitCounter implements IO<Long, Integer> {
private static AtomicLong websiteCounter = new AtomicLong(0);
@Override
public void publish(Integer count) {
websiteCounter.addAndGet(count);
}
@Override
public Long consume() {
return websiteCounter.get();
}
@Override
public Long RPCSubmit(Integer count) {
return websiteCounter.addAndGet(count);
}
}
Ahora vamos a usar el VisitCounter:
VisitCounter counter = new VisitCounter();
// just had 4 visits, yay
counter.publish(4);
// just had another visit, yay
counter.publish(1);
// get data for stats counter
System.out.println(counter.consume()); // prints 5
// show data for stats counter page, but include that as a page view
System.out.println(counter.RPCSubmit(1)); // prints 6
Al implementar varias interfaces, no puede implementar la misma interfaz dos veces. Eso también se aplica a las interfaces genéricas. Por lo tanto, el siguiente código no es válido y generará un error de compilación:
interface Printer<T> {
void print(T value);
}
// Invalid!
class SystemPrinter implements Printer<Double>, Printer<Integer> {
@Override public void print(Double d){ System.out.println("Decimal: " + d); }
@Override public void print(Integer i){ System.out.println("Discrete: " + i); }
}
Utilidad de las interfaces.
Las interfaces pueden ser extremadamente útiles en muchos casos. Por ejemplo, supongamos que tenía una lista de animales y quería recorrer la lista, cada uno imprimiendo el sonido que hacen.
{cat, dog, bird}
Una forma de hacer esto sería usar interfaces. Esto permitiría que se llame al mismo método en todas las clases
public interface Animal {
public String getSound();
}
Cualquier clase que implements Animal
también debe tener un método getSound()
, pero todas pueden tener implementaciones diferentes
public class Dog implements Animal {
public String getSound() {
return "Woof";
}
}
public class Cat implements Animal {
public String getSound() {
return "Meow";
}
}
public class Bird implements Animal{
public String getSound() {
return "Chirp";
}
}
Ahora tenemos tres clases diferentes, cada una de las cuales tiene un método getSound()
. Debido a que todas estas clases implement
la interfaz Animal
, que declara el método getSound()
, cualquier instancia de un Animal
puede tener getSound()
llamada.
Animal dog = new Dog();
Animal cat = new Cat();
Animal bird = new Bird();
dog.getSound(); // "Woof"
cat.getSound(); // "Meow"
bird.getSound(); // "Chirp"
Debido a que cada uno de estos es un Animal
, incluso podríamos poner a los animales en una lista, recorrerlos e imprimir sus sonidos.
Animal[] animals = { new Dog(), new Cat(), new Bird() };
for (Animal animal : animals) {
System.out.println(animal.getSound());
}
Debido a que el orden de la matriz es Dog
, Cat
y luego Bird
, "Woof Meow Chirp" se imprimirá en la consola.
Las interfaces también se pueden utilizar como valor de retorno para las funciones. Por ejemplo, devolver un Dog
si la entrada es "perro" , Cat
si la entrada es "gato" y Bird
si es "pájaro" , y luego imprimir el sonido de ese animal se podría hacer usando
public Animal getAnimalByName(String name) {
switch(name.toLowerCase()) {
case "dog":
return new Dog();
case "cat":
return new Cat();
case "bird":
return new Bird();
default:
return null;
}
}
public String getAnimalSoundByName(String name){
Animal animal = getAnimalByName(name);
if (animal == null) {
return null;
} else {
return animal.getSound();
}
}
String dogSound = getAnimalSoundByName("dog"); // "Woof"
String catSound = getAnimalSoundByName("cat"); // "Meow"
String birdSound = getAnimalSoundByName("bird"); // "Chirp"
String lightbulbSound = getAnimalSoundByName("lightbulb"); // null
Las interfaces también son útiles para la extensibilidad, porque si quieres agregar un nuevo tipo de Animal
, no necesitarás cambiar nada con las operaciones que realices en ellos.
Implementando interfaces en una clase abstracta.
Un método definido en una interface
es por defecto public abstract
. Cuando una abstract class
implementa una interface
, los métodos que se definen en la interface
no tienen que ser implementado por el abstract class
. Esto se debe a que una class
que se declara abstract
puede contener declaraciones de métodos abstractos. Por lo tanto, es responsabilidad de la primera subclase concreta implementar cualquier método abstract
heredado de cualquier interfaz y / o la abstract class
.
public interface NoiseMaker {
void makeNoise();
}
public abstract class Animal implements NoiseMaker {
//Does not need to declare or implement makeNoise()
public abstract void eat();
}
//Because Dog is concrete, it must define both makeNoise() and eat()
public class Dog extends Animal {
@Override
public void makeNoise() {
System.out.println("Borf borf");
}
@Override
public void eat() {
System.out.println("Dog eats some kibble.");
}
}
Desde Java 8 en adelante, es posible que una interface
declare implementaciones default
de métodos, lo que significa que el método no será abstract
, por lo tanto, no se obligará a ninguna subclase concreta a implementar el método, sino que heredará la implementación default
menos que se anule.
Métodos predeterminados
Introducido en Java 8, los métodos predeterminados son una forma de especificar una implementación dentro de una interfaz. Esto podría usarse para evitar la típica clase "Base" o "Resumen" al proporcionar una implementación parcial de una interfaz y restringir la jerarquía de subclases.
Implementación del patrón observador
Por ejemplo, es posible implementar el patrón Observer-Listener directamente en la interfaz, proporcionando más flexibilidad a las clases de implementación.
interface Observer {
void onAction(String a);
}
interface Observable{
public abstract List<Observer> getObservers();
public default void addObserver(Observer o){
getObservers().add(o);
}
public default void notify(String something ){
for( Observer l : getObservers() ){
l.onAction(something);
}
}
}
Ahora, cualquier clase puede convertirse en "Observable" solo con la implementación de la interfaz de Observable, y ser libre de ser parte de una jerarquía de clases diferente.
abstract class Worker{
public abstract void work();
}
public class MyWorker extends Worker implements Observable {
private List<Observer> myObservers = new ArrayList<Observer>();
@Override
public List<Observer> getObservers() {
return myObservers;
}
@Override
public void work(){
notify("Started work");
// Code goes here...
notify("Completed work");
}
public static void main(String[] args) {
MyWorker w = new MyWorker();
w.addListener(new Observer() {
@Override
public void onAction(String a) {
System.out.println(a + " (" + new Date() + ")");
}
});
w.work();
}
}
Problema del diamante
El compilador en Java 8 es consciente del problema de diamante que se produce cuando una clase implementa interfaces que contienen un método con la misma firma.
Para resolverlo, una clase de implementación debe anular el método compartido y proporcionar su propia implementación.
interface InterfaceA {
public default String getName(){
return "a";
}
}
interface InterfaceB {
public default String getName(){
return "b";
}
}
public class ImpClass implements InterfaceA, InterfaceB {
@Override
public String getName() {
//Must provide its own implementation
return InterfaceA.super.getName() + InterfaceB.super.getName();
}
public static void main(String[] args) {
ImpClass c = new ImpClass();
System.out.println( c.getName() ); // Prints "ab"
System.out.println( ((InterfaceA)c).getName() ); // Prints "ab"
System.out.println( ((InterfaceB)c).getName() ); // Prints "ab"
}
}
Todavía existe el problema de tener métodos con el mismo nombre y parámetros con diferentes tipos de retorno, que no se compilarán.
Utilice métodos predeterminados para resolver problemas de compatibilidad
Las implementaciones de los métodos predeterminados son muy útiles si se agrega un método a una interfaz en un sistema existente en el que varias clases usan las interfaces.
Para evitar dividir todo el sistema, puede proporcionar una implementación de método predeterminada cuando agrega un método a una interfaz. De esta manera, el sistema todavía se compilará y las implementaciones reales se pueden hacer paso a paso.
Para obtener más información, consulte el tema Métodos predeterminados .
Modificadores en interfaces
La Guía de estilo de Java de Oracle dice:
Los modificadores no deben escribirse cuando están implícitos.
(Ver Modificadores en Oracle Official Code Standard para el contexto y un enlace al documento de Oracle real).
Esta guía de estilo se aplica particularmente a las interfaces. Consideremos el siguiente fragmento de código:
interface I {
public static final int VARIABLE = 0;
public abstract void method();
public static void staticMethod() { ... }
public default void defaultMethod() { ... }
}
Variables
Todas las variables de interfaz son constantes implícitamente con modificadores public
(accesibles para todos), static
(accesibles por nombre de interfaz) y final
(deben inicializarse durante la declaración):
public static final int VARIABLE = 0;
Métodos
- Todos los métodos que no proporcionan implementación son implícitamente
public
yabstract
.
public abstract void method();
- Todos los métodos con modificador
static
odefault
deben proporcionar implementación y son implícitamentepublic
.
public static void staticMethod() { ... }
Después de que se hayan aplicado todos los cambios anteriores, obtendremos lo siguiente:
interface I {
int VARIABLE = 0;
void method();
static void staticMethod() { ... }
default void defaultMethod() { ... }
}
Reforzar los parámetros de tipo acotado.
Los parámetros de tipo limitado le permiten establecer restricciones en los argumentos de tipo genérico:
class SomeClass {
}
class Demo<T extends SomeClass> {
}
Pero un parámetro de tipo solo puede vincularse a un solo tipo de clase.
Un tipo de interfaz se puede vincular a un tipo que ya tenía un enlace. Esto se logra usando el símbolo &
:
interface SomeInterface {
}
class GenericClass<T extends SomeClass & SomeInterface> {
}
Esto fortalece el enlace, lo que potencialmente requiere que los argumentos de tipo se deriven de múltiples tipos.
Se pueden enlazar varios tipos de interfaz a un parámetro de tipo:
class Demo<T extends SomeClass & FirstInterface & SecondInterface> {
}
Pero debe utilizarse con precaución. Los enlaces de interfaz múltiples suelen ser un signo de olor de código , lo que sugiere que se debe crear un nuevo tipo que actúe como un adaptador para los otros tipos:
interface NewInterface extends FirstInterface, SecondInterface {
}
class Demo<T extends SomeClass & NewInterface> {
}