Desarrollo de Java 8 para Android API de transmisión y bibliotecas de fecha y hora

En esta serie de tres partes, hemos estado explorando todas las características principales de Java 8 que puedes comenzar a usar en tus proyectos de Android hoy..

En Cleaner Code With Lambda Expressions, nos enfocamos en eliminar las repeticiones de sus proyectos usando expresiones lambda, y luego, en Default y Static Methods, vimos cómo hacer que estas expresiones lambda sean más concisas al combinarlas con referencias de métodos. También cubrimos la repetición de anotaciones y cómo declarar métodos no abstractos en sus interfaces utilizando métodos de interfaz predeterminados y estáticos..

En esta última publicación, veremos anotaciones de tipo, interfaces funcionales y cómo adoptar un enfoque más funcional para el procesamiento de datos con la nueva API Stream de Java 8..

También te mostraré cómo acceder a algunas características adicionales de Java 8 que actualmente no son compatibles con la plataforma Android, utilizando las bibliotecas Joda-Time y ThreeTenABP.

Escriba anotaciones

Las anotaciones lo ayudan a escribir un código más robusto y menos propenso a errores, al informar a las herramientas de inspección de códigos, como Lint, sobre los errores que deben estar observando. Estas herramientas de inspección le avisarán si un fragmento de código no cumple con las reglas establecidas por estas anotaciones..

Las anotaciones no son una característica nueva (de hecho, se remontan a Java 5.0), pero en versiones anteriores de Java solo era posible aplicar anotaciones a las declaraciones..

Con el lanzamiento de Java 8, ahora puede usar anotaciones en cualquier lugar donde haya usado un tipo, incluidos los receptores de métodos; expresiones de creación de instancia de clase; la implementación de interfaces; genéricos y matrices; la especificación de arroja y implementos cláusulas y tipo de fundición.

De manera frustrante, aunque Java 8 hace posible usar anotaciones en más ubicaciones que nunca, no proporciona ninguna anotación que sea específica para tipos.

La biblioteca de soporte de anotaciones de Android proporciona acceso a algunas anotaciones adicionales, como @Nullable, @NonNull, y anotaciones para validar tipos de recursos tales como  @DrawableRes, @DimenRes, @ColorRes, y @StringRes. Sin embargo, es posible que también desee utilizar una herramienta de análisis estático de terceros, como el marco de Checker, que se desarrolló conjuntamente con la especificación JSR 308 (la especificación de anotaciones en tipos de Java). Este marco proporciona su propio conjunto de anotaciones que se pueden aplicar a los tipos, más una serie de "comprobadores" (procesadores de anotaciones) que se enganchan en el proceso de compilación y realizan "comprobaciones" específicas para cada anotación de tipo incluida en el Marco de Checker.

Dado que las anotaciones de tipo no afectan la operación en tiempo de ejecución, puede usar las anotaciones de tipo de Java 8 en sus proyectos mientras se mantiene compatible con versiones anteriores de Java..

API de transmisión

La API de Stream ofrece un enfoque alternativo de "tuberías y filtros" para el procesamiento de colecciones.

Antes de Java 8, manipulaba las colecciones manualmente, normalmente iterando sobre la colección y operando en cada elemento por turno. Este bucle explícito requirió una gran cantidad de repeticiones, además de que es difícil agarrar la estructura del bucle for hasta que llegue al cuerpo del bucle..

La API de Stream le brinda una manera de procesar los datos de manera más eficiente, realizando una única ejecución sobre esos datos, independientemente de la cantidad de datos que esté procesando, o si está realizando múltiples cálculos.

En Java 8, cada clase que implementa. java.util.Coleccion tiene un corriente Método que puede convertir sus instancias en Corriente objetos. Por ejemplo, si tienes un Formación:

String [] myArray = new String [] "A", "B", "C", "D";

Luego puedes convertirlo en un Stream con lo siguiente:

Corriente myStream = Arrays.stream (myArray);

La API Stream procesa los datos mediante el transporte de valores desde una fuente, a través de una serie de pasos computacionales, conocidos como tubería de flujo. Un flujo de tubería se compone de lo siguiente:

  • Una fuente, como un Colección, matriz, o función de generador.
  • Cero o más operaciones intermedias "perezosas". Las operaciones intermedias no comienzan a procesar elementos hasta que invoca un operación terminal-Es por eso que son considerados perezosos.Por ejemplo, llamando Stream.filter () en una fuente de datos simplemente configura la línea de flujo; en realidad, no se realiza ningún filtrado hasta que llame a la operación del terminal. Esto hace posible encadenar múltiples operaciones y luego realizar todos estos cálculos en una sola pasada de los datos. Las operaciones intermedias producen un nuevo flujo (por ejemplo,, filtrar producirá una corriente que contiene los elementos filtrados) sin modificando la fuente de datos, para que pueda usar los datos originales en otras partes de su proyecto o crear múltiples transmisiones desde la misma fuente.
  • Una operación de terminal, tal como Stream.forEach. Cuando invoca la operación del terminal, todas sus operaciones intermedias se ejecutarán y producirán una nueva secuencia. Un flujo no es capaz de almacenar elementos, por lo que tan pronto invoca una operación de terminal, ese flujo se considera "consumido" y ya no se puede utilizar. Si desea volver a visitar los elementos de una secuencia, deberá generar una nueva secuencia a partir del origen de datos original..

Creando un Stream

Hay varias formas de obtener un flujo desde una fuente de datos, incluyendo:

  • Corriente de()Crea un flujo a partir de valores individuales:

Corriente stream = Stream.of ("A", "B", "C");
  • IntStream.range () Crea una secuencia de un rango de números:

IntStream i = IntStream.range (0, 20);
  • Stream.iterate () Crea un flujo aplicando repetidamente un operador a cada elemento. Por ejemplo, aquí estamos creando una secuencia donde cada elemento aumenta su valor en uno:

Corriente s = Stream.iterate (0, n -> n + 1);

Transformando un Stream con Operaciones

Hay una tonelada de operaciones que puede utilizar para realizar cálculos de estilo funcional en sus flujos. En esta sección, voy a cubrir solo algunas de las operaciones de transmisión más utilizadas..

Mapa

los mapa() La operación toma una expresión lambda como su único argumento, y usa esta expresión para transformar el valor o el tipo de cada elemento en la corriente. Por ejemplo, lo siguiente nos da un nuevo flujo, donde cada Cuerda se ha convertido a mayúsculas:

Corriente myNewStream = myStream.map (s -> s.toUpperCase ());

Límite

Esta operación establece un límite en el tamaño de un flujo. Por ejemplo, si desea crear una nueva transmisión que contenga un máximo de cinco valores, entonces usaría lo siguiente:

Lista number_string = numbers.stream () .limit (5)

Filtrar

los filtro (predicado) La operación le permite definir los criterios de filtrado mediante una expresión lambda. Esta expresión lambda debe devuelve un valor booleano que determina si cada elemento debe incluirse en el flujo resultante. Por ejemplo, si tuviera una serie de cadenas y quisiera filtrar las cadenas que contenían menos de tres caracteres, usaría lo siguiente:  

Arrays.stream (myArray) .filter (s -> s.length ()> 3) .forEach (System.out :: println); 

Ordenados

Esta operación ordena los elementos de un flujo. Por ejemplo, lo siguiente devuelve un flujo de números ordenados en orden ascendente:

Lista list = Arrays.asList (10, 11, 8, 9, 22); list.stream () .sorted () .forEach (System.out :: println);

Procesamiento en paralelo

Todas las operaciones de flujo pueden ejecutarse en serie o en paralelo, aunque los flujos son secuenciales a menos que especifique explícitamente lo contrario. Por ejemplo, lo siguiente procesará cada elemento uno por uno:

Stream.of ("a", "b", "c", "d", "e") .forEach (System.out :: print);

Para ejecutar una secuencia en paralelo, debe marcar explícitamente esa secuencia como paralela, utilizando la paralela() método:

Stream.of ("a", "b", "c", "d", "e") .parallel () .forEach (System.out :: print);

Bajo el capó, los flujos paralelos utilizan el marco de Fork / Join, por lo que el número de subprocesos disponibles siempre es igual al número de núcleos disponibles en la CPU.

El inconveniente de los flujos paralelos es que cada vez que se ejecuta el código pueden estar involucrados diferentes núcleos, por lo que normalmente obtendrá una salida diferente con cada ejecución. Por lo tanto, solo debe usar flujos paralelos cuando el orden de procesamiento no es importante, y evitar flujos paralelos al realizar operaciones basadas en pedidos, como encontrar primero ().

Operaciones de terminal

Se recopilan los resultados de una secuencia utilizando una operación de terminal, que es siempre el último elemento en una cadena de métodos de flujo, y siempre devuelve algo distinto a un flujo.

Hay algunos tipos diferentes de operaciones de terminal que devuelven varios tipos de datos, pero en esta sección vamos a ver dos de las operaciones de terminal más utilizadas..

Recoger

los Recoger La operación reúne todos los elementos procesados ​​en un contenedor, como un Lista o Conjunto. Java 8 proporciona una Coleccionistas clase de utilidad, por lo que no necesita preocuparse por implementar el Coleccionistas interfaz, más fábricas para muchos coleccionistas comunes, incluyendo Listar(), toSet (), y toCollection ().

El siguiente código producirá un Lista conteniendo solo formas rojas:

shapes.stream () .filter (s -> s.getColor (). equals ("red")) .collect (Collectors.toList ());

Alternativamente, usted podría recolectar estos elementos filtrados en una Conjunto:

 .recoger (Collectors.toSet ());

para cada

los para cada() La operación realiza alguna acción en cada elemento de la secuencia, lo que la convierte en el equivalente de la API Stream de una instrucción para cada una.

Si tuvieras una artículos lista, entonces podrías usar para cada para imprimir todos los elementos que se incluyen en este Lista:

items.forEach (item-> System.out.println (item));

En el ejemplo anterior, estamos usando una expresión lambda, por lo que es posible realizar el mismo trabajo en menos código, usando una referencia de método:

items.forEach (System.out :: println);

Interfaces funcionales

Una interfaz funcional es una interfaz que contiene exactamente un método abstracto, conocido como el método funcional.

El concepto de interfaces de un solo método no es nuevo.-Ejecutable, Comparador, Callable, y OnClickListener son ejemplos de este tipo de interfaz, aunque en versiones anteriores de Java se conocían como interfaces de método abstracto único (interfaces SAM).  

Esto es más que un simple cambio de nombre, ya que hay algunas diferencias notables en la forma de trabajar con interfaces funcionales (o SAM) en Java 8, en comparación con versiones anteriores..

Antes de Java 8, normalmente instalaba una interfaz funcional utilizando una implementación de clase anónima voluminosa. Por ejemplo, aquí estamos creando una instancia de Ejecutable utilizando una clase anónima:

Runnable r = new Runnable () @Override public void run () System.out.println ("My Runnable"); ;

Como vimos en la primera parte, cuando tiene una interfaz de un solo método, puede crear una instancia de esa interfaz utilizando una expresión lambda, en lugar de una clase anónima. Ahora, podemos actualizar esta regla: puedes instanciar interfaces funcionales, utilizando una expresión lambda. Por ejemplo:

Runnable r = () -> System.out.println ("My Runnable");

Java 8 también introduce una @FunctionalInterface anotación que le permite marcar una interfaz como una interfaz funcional:

@FunctionalInterface interfaz pública MyFuncInterface public void doSomething (); 

Para garantizar la compatibilidad con versiones anteriores de Java, la @FunctionalInterface la anotación es opcional; sin embargo, se recomienda ayudar a garantizar que esté implementando sus interfaces funcionales correctamente.

Si intenta implementar dos o más métodos en una interfaz que está marcada como @FunctionalInterface, entonces el compilador se quejará de que ha descubierto múltiples métodos abstractos que no prevalecen. Por ejemplo, lo siguiente no compilará:

@FunctionalInterface interfaz pública MyFuncInterface void doSomething (); // Definir un segundo método abstracto // void doSomethingElse ();  

Y, si intentas compilar un @FunctionInterface interfaz que contiene cero métodos, entonces vas a encontrar un No se ha encontrado ningún método objetivo error.

Las interfaces funcionales deben contener exactamente un método abstracto, pero como los métodos predeterminados y estáticos no tienen un cuerpo, se consideran no abstractos. Esto significa que puede incluir múltiples métodos predeterminados y estáticos en una interfaz, marcarlo como @FunctionalInterface, y va a todavía compilar.

Java 8 también agregó un paquete java.util.function que contiene muchas interfaces funcionales. Vale la pena tomarse el tiempo para familiarizarse con todas estas nuevas interfaces funcionales, solo para que sepa exactamente qué hay disponible de inmediato..

JSR-310: Nueva API de fecha y hora de Java

Trabajar con fecha y hora en Java nunca ha sido tan sencillo, ya que muchas API omiten funcionalidades importantes, como la información de zona horaria..

Java 8 introdujo una nueva API de fecha y hora (JSR-310) que tiene como objetivo resolver estos problemas, pero desafortunadamente al momento de escribir esta API no es compatible con la plataforma Android. Sin embargo, puede usar algunas de las nuevas funciones de Fecha y Hora en sus proyectos de Android hoy, utilizando una biblioteca de terceros..

En esta sección final, les mostraré cómo configurar y usar dos bibliotecas de terceros populares que hacen posible usar la API de fecha y hora de Java 8 en Android.

ThreeTen Android Backport

ThreeTen Android Backport (también conocido como ThreeTenABP) es una adaptación del popular proyecto ThreeTen backport, que proporciona una implementación de JSR-310 para Java 6.0 y Java 7.0. ThreeTenABP está diseñado para proporcionar acceso a todas las clases de API de fecha y hora (aunque con un nombre de paquete diferente) sin agregar una gran cantidad de métodos a sus proyectos de Android.

Para comenzar a utilizar esta biblioteca, abra su nivel de módulo construir.gradle Archivo y agregue ThreeTenABP como una dependencia de proyecto:

dependencias // Agregue la siguiente línea // compile 'com.jakewharton.threetenabp: threetenabp: 1.0.5'

A continuación, debe agregar la declaración de importación de ThreeTenABP:

importar com.jakewharton.threetenabp.AndroidThreeTen;

E inicialice la información de zona horaria en su Aplicación.onCrear () método:

@Override public void onCreate () super.onCreate (); AndroidThreeTen.init (este); 

ThreeTenABP contiene dos clases que muestran dos "tipos" de información de fecha y hora:

  • LocalDateTimeTime, que almacena una hora y una fecha en el formato 2017-10-16T13: 17: 57.138
  • ZonedDateTime, que es consciente de la zona horaria y almacena información de fecha y hora en el siguiente formato: 2011-12-03T10: 15: 30 + 01: 00 [Europa / París]

Para darle una idea de cómo usaría esta biblioteca para recuperar información de fecha y hora, usemos la LocalDateTimeTime clase para mostrar la fecha y hora actual:

importar android.support.v7.app.AppCompatActivity; importar android.os.Bundle; importar com.jakewharton.threetenabp.AndroidThreeTen; importar android.widget.TextView; importar org.threeten.bp.LocalDateTime; la clase pública MainActivity extiende AppCompatActivity @Override protected void onCreate (Bundle savedInstanceState) super.onCreate (savedInstanceState); AndroidThreeTen.init (getApplication ()); setContentView (R.layout.activity_main); TextView textView = nuevo TextView (este); textView.setText ("Time:" + LocalDateTime.now ()); setContentView (textView); 

¡Esta no es la forma más fácil de mostrar la fecha y la hora! Para analizar estos datos sin procesar en algo más legible para los humanos, puede usar el DateTimeFormatter clase y configurarlo en uno de los siguientes valores:

  • BASIC_ISO_DATE. Formatea la fecha como 2017-1016 + 01.00
  • ISO_LOCAL_DATE. Formatea la fecha como 2017-10-16
  • ISO_LOCAL_TIME. Formatea el tiempo como 14: 58: 43.242
  • ISO_LOCAL_DATE_TIME. Formatea la fecha y la hora como 2017-10-16T14: 58: 09.616
  • ISO_OFFSET_DATE. Formatea la fecha como 2017-10-16 + 01.00
  • ISO_OFFSET_TIME.  Formatea el tiempo como 14: 58: 56.218 + 01: 00
  • ISO_OFFSET_DATE_TIME. Formatea la fecha y hora como 2017-10-16T14: 5836.758 + 01: 00
  • ISO_ZONED_DATE_TIME. Formatea la fecha y hora como 2017-10-16T14: 58: 51.324 + 01: 00 (Europa / Londres)
  • ISO_INSTANT. Formatea la fecha y hora como 2017-10-16T13: 52: 45.246Z
  • ISO_DATE. Formatea la fecha como 2017-10-16 + 01: 00
  • ISO_TIME. Formatea el tiempo como 14: 58: 40.945 + 01: 00
  • ISO_DATE_TIME. Formatea la fecha y hora como 2017-10-16T14: 55: 32.263 + 01: 00 (Europa / Londres)
  • ISO_ORDINAL_DATE. Formatea la fecha como 2017-289 + 01: 00
  • ISO_WEEK_DATE. Formatea la fecha como 2017-W42-1 + 01: 00
  • RFC_1123_DATE_TIME. Formatea la fecha y hora como Lun, 16 de octubre de 2017 14: 58: 43 + 01: 00

Aquí, estamos actualizando nuestra aplicación para mostrar la fecha y la hora con DateTimeFormatter.ISO_DATE formateo

importar android.support.v7.app.AppCompatActivity; importar android.os.Bundle; importar com.jakewharton.threetenabp.AndroidThreeTen; importar android.widget.TextView; // Añadir la declaración de importación DateTimeFormatter // importar org.threeten.bp.format.DateTimeFormatter; importar org.threeten.bp.ZonedDateTime; la clase pública MainActivity extiende AppCompatActivity @Override protected void onCreate (Bundle savedInstanceState) super.onCreate (savedInstanceState); AndroidThreeTen.init (getApplication ()); setContentView (R.layout.activity_main); TextView textView = nuevo TextView (este); DateTimeFormatter formatter = DateTimeFormatter.ISO_DATE; String formattedZonedDate = formatter.format (ZonedDateTime.now ()); textView.setText ("Time:" + formattedZonedDate); setContentView (textView); 

Para mostrar esta información en un formato diferente, simplemente sustituya DateTimeFormatter.ISO_DATE por otro valor. Por ejemplo:

DateTimeFormatter formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;

Tiempo de joda

Antes de Java 8, la biblioteca Joda-Time se consideraba la biblioteca estándar para el manejo de la fecha y la hora en Java, hasta el punto en que la nueva API de fecha y hora de Java 8 realmente se basa en "la experiencia obtenida del proyecto Joda-Time"

Si bien el sitio web de Joda-Time recomienda que los usuarios migren a Java 8 Date and Time tan pronto como sea posible, ya que Android actualmente no admite esta API, Joda-Time sigue siendo una opción viable para el desarrollo de Android. Sin embargo, tenga en cuenta que Joda-Time tiene una gran API y carga información de zona horaria usando un recurso JAR, los cuales pueden afectar el rendimiento de su aplicación..

Para comenzar a trabajar con la biblioteca de Joda-Time, abra su nivel de módulo construir.gradle archivo y añadir lo siguiente:

dependencias compile 'joda-time: joda-time: 2.9.9'… 

La biblioteca de Joda-Time tiene seis clases principales de fecha y hora:

  • Instante: Representa un punto en la línea de tiempo; por ejemplo, puede obtener la fecha y hora actual llamando al Instant.now ().
  • Fecha y hora: Un reemplazo de propósito general para JDK's Calendario clase.
  • Fecha local: Una fecha sin hora, o cualquier referencia a una zona horaria.
  • Hora local: Una hora sin una fecha o cualquier referencia a una zona horaria, por ejemplo 14:00:00.
  • LocalDateTimeTime: Una fecha y hora local, aún sin ninguna información de zona horaria.
  • ZonedDateTime: Una fecha y hora con una zona horaria.

Veamos cómo imprimiría la fecha y la hora usando Joda-Time. En el siguiente ejemplo, estoy reutilizando el código de nuestro ejemplo de ThreeTenABP, para hacer las cosas más interesantes, también estoy usando con zona para convertir la fecha y la hora en una ZonedDateTime valor.

importar android.support.v7.app.AppCompatActivity; importar android.os.Bundle; importar android.widget.TextView; importar org.joda.time.DateTime; importar org.joda.time.DateTimeZone; la clase pública MainActivity extiende AppCompatActivity @Override protected void onCreate (Bundle savedInstanceState) super.onCreate (savedInstanceState); setContentView (R.layout.activity_main); DateTime today = new DateTime (); // Devuelva un nuevo formateador (usando withZone) y especifique la zona horaria, usando ZoneId // DateTime todayNy = today.withZone (DateTimeZone.forID ("America / New_York")); TextView textView = nuevo TextView (este); textView.setText ("Time:" + todayNy); setContentView (textView); 

Encontrará una lista completa de las zonas horarias admitidas en los documentos oficiales de Joda-Time.

Conclusión

En esta publicación, vimos cómo crear un código más robusto utilizando anotaciones de tipo, y exploramos el enfoque de "tuberías y filtros" para el procesamiento de datos con la nueva API Stream de Java 8.

También observamos cómo las interfaces han evolucionado en Java 8 y cómo usarlas en combinación con otras características que hemos estado explorando a lo largo de esta serie, incluidas las expresiones lambda y los métodos de interfaz estática..

Para resumir, le mostré cómo acceder a algunas características adicionales de Java 8 que Android actualmente no admite de forma predeterminada, utilizando los proyectos Joda-Time y ThreeTenABP..

Puede obtener más información sobre el lanzamiento de Java 8 en el sitio web de Oracle.

Y mientras esté aquí, eche un vistazo a algunas de nuestras otras publicaciones sobre el desarrollo de Java 8 y Android!