Desarrollo de Java 8 para Android métodos predeterminados y estáticos

Java 8 fue un gran paso adelante para el lenguaje de programación y ahora, con el lanzamiento de Android Studio 3.0, los desarrolladores de Android finalmente tienen acceso al soporte integrado para algunas de las características más importantes de Java 8.

En esta serie de tres partes, hemos estado explorando las características de Java 8 que puedes comenzar a usar en tus proyectos de Android hoy. En Cleaner Code Con Lambda Expressions, configuramos nuestro desarrollo para usar el soporte de Java 8 provisto por la cadena de herramientas predeterminada de Android, antes de analizar en profundidad las expresiones lambda..

En esta publicación, veremos dos formas diferentes en las que puede declarar métodos no abstractos en sus interfaces (algo que no era posible en versiones anteriores de Java). También responderemos a la pregunta de, ahora que las interfaces pueden implementar métodos, qué exactamente Es la diferencia entre clases abstractas e interfaces.?

También cubriremos una función de Java 8 que le brinda la libertad de usar la misma anotación tantas veces como desee en la misma ubicación, mientras que al mismo tiempo es compatible con versiones anteriores de Android..

Pero primero, echemos un vistazo a una característica de Java 8 que está diseñada para ser utilizada en combinación con las expresiones lambda que vimos en la publicación anterior.

Escribir expresiones Lambda más limpias, con referencias de métodos

En la última publicación, viste cómo puedes usar expresiones lambda para eliminar un montón de código repetitivo de tus aplicaciones de Android. Sin embargo, cuando una expresión lambda simplemente llama a un método único que ya tiene un nombre, puede cortar aún más código de su proyecto usando una referencia de método.

Por ejemplo, esta expresión lambda es en realidad simplemente redirigir el trabajo a un manejarVer clic método:

 FloatingActionButton fab = (FloatingActionButton) findViewById (R.id.fab); fab.setOnClickListener (view -> handleViewClick (view));  void manillar privadoViewClick (ver vista) 

En este escenario, podemos referirnos a este método por su nombre, usando la :: Método de referencia del operador. Usted crea este tipo de referencia de método, utilizando el siguiente formato:

Objeto / Clase / Tipo :: nombre de método

En nuestro ejemplo de botón de acción flotante, podemos usar una referencia de método como el cuerpo de nuestra expresión lambda:

 FloatingActionButton fab = (FloatingActionButton) findViewById (R.id.fab); fab.setOnClickListener (this :: handleViewClick); 

Tenga en cuenta que el método al que se hace referencia debe tomar los mismos parámetros que la interfaz; en esta instancia, eso es Ver.

Puede utilizar el operador de referencia de método (::) para hacer referencia a cualquiera de los siguientes:

Un método estático

Si tienes una expresión lambda que llama a un método estático:

(args) -> Class.staticMethod (args)

Entonces puedes convertirlo en una referencia de método:

Clase :: staticMethodName

Por ejemplo, si tuvieras un método estático. Imprimir mensaje en un Mi clase clase, entonces su referencia de método se vería algo así:

public class myClass public static void PrintMessage () System.out.println ("Este es un método estático");  public static void main (String [] args) Thread thread = new Thread (myClass :: PrintMessage); thread.start ();  

Un método de instancia de un objeto específico

Este es un método de instancia de un objeto que se conoce de antemano, lo que le permite reemplazar:

(argumentos) -> contenerObject.instanceMethodName (argumentos)

Con:

conteniendoObject :: instanceMethodName

Entonces, si tuvieras la siguiente expresión lambda:

MyClass.printNames (nombres, x -> System.out.println (x));

Luego, introducir una referencia de método le daría lo siguiente:

MyClass.printNames (nombres, System.out :: println);

Un método de instancia de un objeto arbitrario de un tipo particular

Este es un método de instancia de un objeto arbitrario que se suministrará más adelante y se escribirá en el siguiente formato:

ContainingType :: methodName

Referencias del constructor

Las referencias de constructor son similares a las referencias de métodos, excepto que usa la palabra clave nuevo Para invocar al constructor. Por ejemplo, Botón :: nuevo Es una referencia constructora para la clase. Botón, Aunque el constructor exacto que se invoca depende del contexto.

Usando referencias de constructor, puedes activar:

(argumentos) -> nuevo ClassName (argumentos)

Dentro:

ClassName :: nuevo

Por ejemplo, si tuvieras las siguientes MyInterface interfaz:

 Interfaz pública myInterface resumen público Estudiante getStudent (nombre de cadena, edad entera); 

Entonces podrías usar referencias de constructor para crear nuevos Estudiante instancias:

myInterface stu1 = Student :: new; Student stu = stu1.getStudent ("John Doe", 27);

También es posible crear referencias de constructor para tipos de matriz. Por ejemplo, una referencia de constructor para una matriz de En ts es int [] :: nuevo.

Agregue métodos predeterminados a sus interfaces

Antes de Java 8, solo podía incluir métodos abstractos en sus interfaces (es decir, métodos sin cuerpo), lo que dificultaba la evolución de las interfaces, después de la publicación..

Cada vez que agrega un método a una definición de interfaz, a cualquier clase que implementa esta interfaz le faltará una implementación. Por ejemplo, si tuvieras una interfaz (MyInterface) que fue utilizado por Mi clase, luego añadiendo un método para MyInterface rompería la compatibilidad con Mi clase.

En el mejor de los casos en los que era responsable del pequeño número de clases que usó MyInterface, este comportamiento sería molesto pero manejable; solo tendría que dedicar algo de tiempo para actualizar sus clases con la nueva implementación. Sin embargo, las cosas podrían convertirse mucho Más complicado si se implementa un gran número de clases. MyInterface, o si la interfaz se usó en clases de las que no era responsable.

Si bien hubo una serie de soluciones para este problema, ninguno de ellos fue ideal. Por ejemplo, podría incluir nuevos métodos en una clase abstracta, pero esto aún requeriría que todos actualicen su código para extender esta clase abstracta; y, mientras tu podría extienda la interfaz original con una nueva interfaz, cualquiera que desee utilizar estos nuevos métodos deberá volver a escribir todos sus referencias de interfaz existentes.

Con la introducción de métodos predeterminados en Java 8, ahora es posible declarar métodos no abstractos (es decir, métodos con un cuerpo) dentro de sus interfaces, para que finalmente pueda crear implementaciones predeterminadas para sus métodos..

Cuando agrega un método a su interfaz como método predeterminado, cualquier clase que implemente esta interfaz no necesariamente tiene que proporcionar su propia implementación, lo que le brinda una forma de actualizar sus interfaces sin romper la compatibilidad. Si agrega un nuevo método a una interfaz como método por defecto, luego, cada clase que use esta interfaz pero no proporcione su propia implementación simplemente heredará la implementación predeterminada del método. Dado que a la clase no le falta una implementación, seguirá funcionando normalmente..

De hecho, la introducción de los métodos predeterminados fue la razón por la que Oracle pudo realizar tantas adiciones a la API de colecciones en Java 8..

Colección es una interfaz genérica que se utiliza en muchas clases diferentes, por lo que agregar nuevos métodos a esta interfaz tenía el potencial de romper innumerables líneas de código. En lugar de agregar nuevos métodos a la Colección Interfaz y rompiendo todas las clases que se derivaron de esta interfaz, Oracle creó la función del método predeterminado y luego agregó estos nuevos métodos como métodos predeterminados. Si echa un vistazo al nuevo método Collection.Stream () (que exploraremos en detalle en la tercera parte), verá que se agregó como método predeterminado:

 Stream por defecto stream () return StreamSupport.stream (spliterator (), false); 

Crear un método por defecto es simple, solo agrega el defecto Modificador a su firma de método:

interfaz pública MyInterface void interfaceMethod (); void predeterminado DefaultMethod () Log.i (TAG, "Este es un método predeterminado");

Ahora si Mi clase usos MyInterface pero no proporciona su propia implementación de Método por defecto, simplemente heredará la implementación predeterminada provista por MyInterface. Por ejemplo, la siguiente clase todavía se compilará:

la clase pública MyClass extiende los implementos AppCompatActivity MyInterface 

Una clase de implementación puede anular la implementación predeterminada proporcionada por la interfaz, por lo que las clases aún tienen el control completo de sus implementaciones.

Si bien los métodos predeterminados son una adición bienvenida para los diseñadores de API, en ocasiones pueden causar problemas a los desarrolladores que intentan usar varias interfaces en la misma clase.. 

Imagina que además de MyInterface, usted tiene lo siguiente:

interfaz pública SecondInterface void interfaceMethod (); void predeterminado DefaultMethod () Log.i (TAG, "Este es también un método predeterminado");

Ambos MyInterface y SecondInterface contienen un método predeterminado con exactamente la misma firma (Método por defecto). Ahora imagine que intenta usar ambas interfaces en la misma clase:

clase pública MyClass extiende los implementos AppCompatActivity MyInterface, SecondInterface 

En este punto tienes dos implementaciones conflictivas de Método por defecto, y el compilador no tiene idea de qué método debería usar, por lo que vas a encontrar un error de compilación.

Una forma de resolver este problema es anular el método conflictivo con su propia implementación:

clase pública MyClass extiende AppCompatActivity implementa MyInterface, SecondInterface public void defaultMethod () 

La otra solución es especificar qué versión de Método por defecto quieres implementar, usando el siguiente formato:

.súper.();

Así que si querías llamar al MyInterface # defaultMethod () Implementación, entonces usarías lo siguiente:

clase pública MyClass extiende AppCompatActivity implementa MyInterface, SecondInterface public void defaultMethod () MyInterface.super.defaultMethod (); 

Uso de métodos estáticos en las interfaces de Java 8

De manera similar a los métodos predeterminados, los métodos de interfaz estática le brindan una forma de definir métodos dentro de una interfaz. Sin embargo, a diferencia de los métodos predeterminados, una clase de implementación no puede anular una interfaz estático metodos.

Si tiene métodos estáticos que son específicos de una interfaz, entonces los métodos de la interfaz estática de Java 8 le brindan una manera de colocar estos métodos dentro de la interfaz correspondiente, en lugar de tener que almacenarlos en una clase separada..

Se crea un método estático colocando la palabra clave estático al comienzo de la firma del método, por ejemplo:

interfaz pública MyInterface static void staticMethod () System.out.println ("Este es un método estático"); 

Cuando implementa una interfaz que contiene un método de interfaz estática, ese método aún es parte de la interfaz y no es heredado por la clase que lo implementa, por lo que deberá prefijar el método con el nombre de la interfaz, por ejemplo:

la clase pública MyClass extiende AppCompatActivity implementa MyInterface public static void main (String [] args) MyInterface.staticMethod ();… 

Esto también significa que una clase y una interfaz pueden tener un método estático con la misma firma. Por ejemplo, usando MyClass.staticMethod y MyInterface.staticMethod en la misma clase no causará un error en tiempo de compilación.

Entonces, ¿las interfaces son esencialmente solo clases abstractas??

La adición de métodos de interfaz estática y métodos predeterminados ha llevado a algunos desarrolladores a cuestionar si las interfaces de Java se están volviendo más como clases abstractas. Sin embargo, incluso con la adición de métodos de interfaz predeterminados y estáticos, todavía hay algunas diferencias notables entre las interfaces y las clases abstractas:

  • Las clases abstractas pueden tener variables finales, no finales, estáticas y no estáticas, mientras que una interfaz solo puede tener variables estáticas y finales.
  • Las clases abstractas le permiten declarar campos que no son estáticos y finales, mientras que los campos de una interfaz son inherentemente estáticos y finales.
  • En las interfaces, todos los métodos que declara o define como métodos predeterminados son intrínsecamente públicos, mientras que en las clases abstractas puede definir métodos concretos públicos, protegidos y privados..
  • Clases abstractas son clases, y por lo tanto puede tener estado; Las interfaces no pueden tener estado asociado con ellas..
  • Puede definir constructores dentro de una clase abstracta, algo que no es posible dentro de las interfaces Java.
  • Java solo le permite extender una clase (independientemente de si es abstracto), pero es libre de implementar tantas interfaces como necesite. Esto significa que las interfaces normalmente tienen la ventaja cuando se requiere herencia múltiple, aunque es necesario tener cuidado con el diamante mortal de la muerte!

Aplique la misma anotación tantas veces como desee

Tradicionalmente, una de las limitaciones de las anotaciones de Java ha sido que no puede aplicar la misma anotación más de una vez en la misma ubicación. Intente usar la misma anotación varias veces y se encontrará con un error en tiempo de compilación.

Sin embargo, con la introducción de las repetidas anotaciones de Java 8, ahora puede utilizar la misma anotación tantas veces como desee en la misma ubicación..

Para garantizar que su código siga siendo compatible con versiones anteriores de Java, deberá almacenar sus anotaciones repetidas en una anotación de contenedor..

Puede decirle al compilador que genere este contenedor, completando los siguientes pasos:

  • Marque la anotación en cuestión con el @Repeable meta-anotación (una anotación que se utiliza para anotar una anotación). Por ejemplo, si quisieras hacer el @Que hacer anotación repetible, usaría: @Repeatable (ToDos.class). El valor entre paréntesis es el tipo de anotación de contenedor que el compilador generará finalmente.
  • Declara el tipo de anotación que contiene. Esto debe tener un atributo que sea una matriz del tipo de anotación de repetición, por ejemplo:
public @interface ToDos ToDo [] value (); 

Intentar aplicar la misma anotación varias veces sin declarar primero que es repetible dará lugar a un error en el momento de la compilación. Sin embargo, una vez que haya especificado que se trata de una anotación repetible, puede usar esta anotación varias veces en cualquier lugar donde use una anotación estándar..

Conclusión

En esta segunda parte de nuestra serie en Java 8, vimos cómo puede cortar aún más código repetitivo de sus proyectos de Android combinando expresiones lambda con referencias de métodos, y cómo mejorar sus interfaces con métodos predeterminados y estáticos..

En la tercera y última entrega, veremos una nueva API de Java 8 que le permite procesar enormes cantidades de datos de una manera más eficiente y declarativa, sin Tener que preocuparse por la concurrencia y la gestión de hilos. También uniremos algunas de las diferentes características que hemos analizado a lo largo de esta serie, al explorar el rol que las Interfaces Funcionales tienen que jugar en las expresiones lambda, los métodos de interfaz estática, los métodos predeterminados y más..

Y finalmente, aunque todavía estamos esperando que la nueva API de fecha y hora de Java 8 llegue oficialmente a Android, le mostraré cómo puede comenzar a usar esta nueva API en sus proyectos de Android hoy, con la ayuda de terceros. bibliotecas.

Mientras tanto, echa un vistazo a algunas de nuestras otras publicaciones sobre el desarrollo de aplicaciones Java y Android!