Interfaces en Java

Las interfaces Java son diferentes de las clases y es importante saber cómo usar sus propiedades especiales en sus programas Java. Este tutorial presenta la diferencia entre clases e interfaces, luego lo guía a través de ejemplos que demuestran cómo declarar, implementar y extender interfaces Java.

También aprenderá cómo ha evolucionado la interfaz en Java 8, con la adición de métodos predeterminados y estáticos, y en Java 9 con los nuevos métodos privados. Estas adiciones hacen que las interfaces sean más útiles para los desarrolladores experimentados. Desafortunadamente, también difuminan las líneas entre clases e interfaces, haciendo que la programación de interfaces sea aún más confusa para los principiantes de Java.

descargar Obtener el código Descargar el código fuente, por ejemplo, las aplicaciones de este tutorial. Creado por Jeff Friesen para JavaWorld.

¿Qué es una interfaz Java?

Una interfaz es un punto donde dos sistemas se encuentran e interactúan. Por ejemplo, puede utilizar una interfaz de máquina expendedora para seleccionar un artículo, pagarlo y recibir un artículo de comida o bebida. Desde una perspectiva de programación, una interfaz se encuentra entre los componentes del software. Considere que una interfaz de encabezado de método (nombre del método, lista de parámetros, etc.) se encuentra entre el código externo que llama al método y el código dentro del método que se ejecutará como resultado de la llamada. He aquí un ejemplo:

System.out.println(average(10, 15)); double average(double x, double y) // interface between average(10, 15) call and return (x + y) / 2; { return (x + y) / 2; }

Lo que a menudo confunde a los principiantes de Java es que las clases también tienen interfaces. Como expliqué en Java 101: Clases y objetos en Java, la interfaz es la parte de la clase que es accesible al código ubicado fuera de ella. La interfaz de una clase consta de una combinación de métodos, campos, constructores y otras entidades. Considere el Listado 1.

Listado 1. La clase Account y su interfaz

class Account { private String name; private long amount; Account(String name, long amount) { this.name = name; setAmount(amount); } void deposit(long amount) { this.amount += amount; } String getName() { return name; } long getAmount() { return amount; } void setAmount(long amount) { this.amount = amount; } }

El Account(String name, long amount)constructor y el void deposit(long amount), String getName(), long getAmount(), y void setAmount(long amount)métodos forman la Accountinterfaz de la clase: son accesibles a código externo. Los campos private String name;y private long amount;son inaccesibles.

Más sobre interfaces Java

¿Qué puede hacer con las interfaces en sus programas Java? Obtenga una descripción general de los seis roles de Jeff de la interfaz Java.

El código de un método, que admite la interfaz del método, y esa parte de una clase que admite la interfaz de la clase (como los campos privados) se conoce como implementación del método o de la clase . Una implementación debe ocultarse del código externo para que pueda cambiarse para cumplir con los requisitos en evolución.

Cuando se exponen las implementaciones, pueden surgir interdependencias entre los componentes del software. Por ejemplo, el código del método puede depender de variables externas y los usuarios de una clase pueden volverse dependientes de campos que deberían haberse ocultado. Este acoplamiento puede generar problemas cuando las implementaciones deben evolucionar (quizás los campos expuestos deben eliminarse).

Los desarrolladores de Java utilizan la característica del lenguaje de interfaz para abstraer las interfaces de clase, desacoplando así las clases de sus usuarios. Al centrarse en las interfaces Java en lugar de las clases, puede minimizar el número de referencias a los nombres de las clases en su código fuente. Esto facilita el cambio de una clase a otra (quizás para mejorar el rendimiento) a medida que su software madura. Aquí hay un ejemplo:

List names = new ArrayList() void print(List names) { // ... }

Este ejemplo declara e inicializa un namescampo que almacena una lista de nombres de cadenas. El ejemplo también declara un print()método para imprimir el contenido de una lista de cadenas, quizás una cadena por línea. Por brevedad, he omitido la implementación del método.

Listes una interfaz Java que describe una colección secuencial de objetos. ArrayListes una clase que describe una implementación basada en matrices de la Listinterfaz Java. Se ArrayListobtiene una nueva instancia de la clase y se asigna a la Listvariable names. ( Listy ArrayListse almacenan en el java.utilpaquete de la biblioteca de clases estándar ).

Corchetes angulares y genéricos

Los corchetes angulares ( <y >) son parte del conjunto de funciones genéricas de Java. Indican que namesdescribe una lista de cadenas (solo las cadenas se pueden almacenar en la lista). Presentaré los genéricos en un futuro artículo de Java 101.

Cuando el código del cliente interactúe con names, invocará los métodos declarados por Listy implementados por ArrayList. El código de cliente no interactuará directamente con ArrayList. Como resultado, el código del cliente no se romperá cuando LinkedListse requiera una clase de implementación diferente, como :

List names = new LinkedList() // ... void print(List names) { // ... }

Debido a que el print()tipo de parámetro del método es List, la implementación de este método no tiene que cambiar. Sin embargo, si el tipo hubiera sido ArrayList, el tipo tendría que cambiarse a LinkedList. Si ambas clases declararan sus propios métodos únicos, es posible que deba cambiar significativamente print()la implementación.

El desacoplamiento Listde ArrayListy LinkedListle permite escribir código que es inmune a los cambios de clase de implementación. Mediante el uso de interfaces Java, puede evitar problemas que podrían surgir al depender de clases de implementación. Este desacoplamiento es la razón principal para utilizar interfaces Java.

Declaración de interfaces Java

Usted declara una interfaz adhiriéndose a una sintaxis similar a una clase que consiste en un encabezado seguido de un cuerpo. Como mínimo, el encabezado consta de una palabra clave interfaceseguida de un nombre que identifica la interfaz. El cuerpo comienza con un carácter de corsé abierto y termina con un corsé cerrado. Entre estos delimitadores se encuentran las declaraciones de encabezado de método y constante:

interface identifier { // interface body }

Por convención, la primera letra del nombre de una interfaz se escribe en mayúsculas y las letras siguientes en minúsculas (por ejemplo, Drawable). Si un nombre consta de varias palabras, la primera letra de cada palabra se escribe en mayúscula (como DrawableAndFillable). Esta convención de nomenclatura se conoce como CamelCasing.

El Listado 2 declara una interfaz llamada Drawable.

Listado 2. Un ejemplo de interfaz Java

interface Drawable { int RED = 1; int GREEN = 2; int BLUE = 3; int BLACK = 4; int WHITE = 5; void draw(int color); }

Interfaces en la biblioteca de clases estándar de Java

Como convención de nomenclatura, muchas interfaces en la biblioteca de clases estándar de Java terminan con el sufijo capaz . Los ejemplos incluyen Callable, Cloneable, Comparable, Formattable, Iterable, Runnable, Serializable, y Transferable. Sin embargo, el sufijo no es obligatorio; la biblioteca de clases estándar incluye las interfaces CharSequence, ClipboardOwner, Collection, Executor, Future, Iterator, List, Mapy muchos otros.

Drawabledeclara cinco campos que identifican las constantes de color. Esta interfaz también declara el encabezado de un draw()método que debe llamarse con una de estas constantes para especificar el color utilizado para dibujar un contorno. (El uso de constantes enteras no es una buena idea porque se puede pasar cualquier valor entero draw(). Sin embargo, son suficientes en un ejemplo simple).

Field and method header defaults

Fields that are declared in an interface are implicitly public final static. An interface's method headers are implicitly public abstract.

Drawable identifies a reference type that specifies what to do (draw something) but not how to do it. Implementation details are consigned to classes that implement this interface. Instances of such classes are known as drawables because they know how to draw themselves.

Marker and tagging interfaces

An interface with an empty body is known as a marker interface or a tagging interface. The interface exists only to associate metadata with a class. For example, Cloneable (see Inheritance in Java, Part 2) implies that instances of its implementing class can be shallowly cloned. When Object's clone() method detects (via runtime type identification) that the calling instance's class implements Cloneable, it shallowly clones the object.

Implementing Java interfaces

A class implements an interface by appending Java's implements keyword followed by a comma-separated list of interface names to the class header, and by coding each interface method in the class. Listing 3 presents a class that implements Listing 2's Drawable interface.

Listing 3. Circle implementing the Drawable interface

class Circle implements Drawable { private double x, y, radius; Circle(double x, double y, double radius) { this.x = x; this.y = y; this.radius = radius; } @Override public void draw(int color) { System.out.println("Circle drawn at (" + x + ", " + y + "), with radius " + radius + ", and color " + color); } double getRadius() { return radius; } double getX() { return x; } double getY() { return y; } }

Listing 3's Circle class describes a circle as a center point and a radius. As well as providing a constructor and suitable getter methods, Circle implements the Drawable interface by appending implements Drawable to the Circle header, and by overriding (as indicated by the @Override annotation) Drawable's draw() method header.

Listing 4 presents a second example: a Rectangle class that also implements Drawable.

Listing 4. Implementing the Drawable interface in a Rectangle context

class Rectangle implements Drawable { private double x1, y1, x2, y2; Rectangle(double x1, double y1, double x2, double y2) { this.x1 = x1; this.y1 = y1; this.x2 = x2; this.y2 = y2; } @Override public void draw(int color) { System.out.println("Rectangle drawn with upper-left corner at (" + x1 + ", " + y1 + ") and lower-right corner at (" + x2 + ", " + y2 + "), and color " + color); } double getX1() { return x1; } double getX2() { return x2; } double getY1() { return y1; } double getY2() { return y2; } }

Listing 4's Rectangle class describes a rectangle as a pair of points denoting the upper-left and lower-right corners of this shape. As with Circle, Rectangle provides a constructor and suitable getter methods, and also implements the Drawable interface.

Overriding interface method headers

The compiler reports an error when you attempt to compile a non-abstract class that includes an implements interface clause but doesn't override all of the interface's method headers.

An interface type's data values are the objects whose classes implement the interface and whose behaviors are those specified by the interface's method headers. This fact implies that you can assign an object's reference to a variable of the interface type, provided that the object's class implements the interface. Listing 5 demonstrates.

Listing 5. Aliasing Circle and Rectangle objects as Drawables

class Draw { public static void main(String[] args) { Drawable[] drawables = new Drawable[] { new Circle(10, 20, 15), new Circle(30, 20, 10), new Rectangle(5, 8, 8, 9) }; for (int i = 0; i < drawables.length; i++) drawables[i].draw(Drawable.RED); } }

Because Circle and Rectangle implement Drawable, Circle and Rectangle objects have Drawable type in addition to their class types. Therefore, it's legal to store each object's reference in an array of Drawables. A loop iterates over this array, invoking each Drawable object's draw() method to draw a circle or a rectangle.

Assuming that Listing 2 is stored in a Drawable.java source file, which is in the same directory as the Circle.java, Rectangle.java, and Draw.java source files (which respectively store Listing 3, Listing 4, and Listing 5), compile these source files via either of the following command lines:

javac Draw.java javac *.java

Run the Draw application as follows:

java Draw

You should observe the following output:

Circle drawn at (10.0, 20.0), with radius 15.0, and color 1 Circle drawn at (30.0, 20.0), with radius 10.0, and color 1 Rectangle drawn with upper-left corner at (5.0, 8.0) and lower-right corner at (8.0, 9.0), and color 1

Note that you could also generate the same output by specifying the following main() method:

public static void main(String[] args) { Circle c = new Circle(10, 20, 15); c.draw(Drawable.RED); c = new Circle(30, 20, 10); c.draw(Drawable.RED); Rectangle r = new Rectangle(5, 8, 8, 9); r.draw(Drawable.RED); }

Como puede ver, es tedioso invocar repetidamente el draw()método de cada objeto . Además, al hacerlo, se agrega un código de bytes adicional al Drawarchivo de clase. Al pensar en Circley Rectanglecomo Drawables, puede aprovechar una matriz y un bucle simple para simplificar el código. Este es un beneficio adicional de diseñar código para preferir interfaces sobre clases.

¡Precaución!