"Java todavía no está muerto, y la gente está empezando a resolver eso".
Este tutorial utilizará un código anotado simple para describir nuevas funciones, y no verá texto de blockbuster.
1. El método predeterminado de la interfaz
Java 8 nos permite agregar una implementación de método que no se abstractan a la interfaz, solo use la palabra clave predeterminada también se llama método de extensión.
La copia del código es la siguiente:
Fórmula de interfaz {
calcular doble (int a);
Double sqrt predeterminado (int a) {
return math.sqrt (a);
}
}
Además de tener un método calculado, la interfaz de fórmula también define el método SQRT.
La copia del código es la siguiente:
Fórmula fórmula = nueva fórmula () {
@Anular
Public Double Calcule (int a) {
return sqrt (a * 100);
}
};
fórmula.calcule (100);
fórmula.sqrt (16);
La fórmula en el artículo se implementa como una instancia de una clase anónima. En la siguiente sección, veremos un enfoque más simple para implementar una interfaz de método único.
Nota del traductor: solo hay una única herencia en Java. En otros idiomas, el método de tener una clase tiene otro código reutilizable al mismo tiempo se llama Mixin. Este nuevo especial de Java 8 está más cerca del rasgo de Scala desde la perspectiva de la implementación del compilador. También hay un concepto llamado método de extensión en C#, que permite que los tipos existentes se extiendan, que es semánticamente diferente de esto en Java 8.
2. Expresiones Lambda
Primero, echemos un vistazo a cómo se organizan las cuerdas en la versión anterior de Java:
La copia del código es la siguiente:
Lista <String> Names = Arrays.aslist ("Peter", "Anna", "Mike", "Xenia");
Collections.sort (nombres, nuevo comparador <string> () {
@Anular
public int Compare (String A, String B) {
regresar B.Compareto (a);
}
});
Simplemente pase un objeto de lista y un comparador con la colección de métodos estáticos. Por lo general, se hace para crear un objeto de comparación anónimo y pasarlo al método de clasificación.
En Java 8, no necesita usar este método tradicional de objetos anónimos.
La copia del código es la siguiente:
Collections.sort (nombres, (cadena A, cadena B) -> {
regresar B.Compareto (a);
});
Ver, el código se vuelve más segmentado y legible, pero en realidad se puede escribir más corto:
La copia del código es la siguiente:
Colección.sort (nombres, (cadena A, cadena B) -> B.compareto (a));
Para el cuerpo de funciones con solo una línea de código, puede eliminar los aparatos {} y devolver las palabras clave, pero puede escribirlo más corto:
La copia del código es la siguiente:
Colección.sort (nombres, (a, b) -> b.compareto (a));
El compilador Java puede deducir automáticamente los tipos de parámetros, por lo que no tiene que volver a escribir el tipo. A continuación, veamos qué pueden hacer las expresiones de Lambda más convenientes:
3. Interfaz funcional
¿Cómo se representan las expresiones de Lambda en el sistema de tipos de Java? Cada expresión de lambda corresponde a un tipo, generalmente un tipo de interfaz. La "interfaz funcional" se refiere a una interfaz que solo contiene un método abstracto, y cada expresión de Lambda de este tipo coincidirá con este método abstracto. Debido a que el método predeterminado no es un método abstracto, también puede agregar métodos predeterminados a su interfaz funcional.
Podemos tratar las expresiones de Lambda como cualquier tipo de interfaz que contenga solo un método abstracto para garantizar que su interfaz debe cumplir con este requisito. Anotación, el compilador encontrará que la interfaz con la que marcó está anotada por esta anotación.
Los ejemplos son los siguientes:
La copia del código es la siguiente:
@FunctionalInterface
Converter de interfaz <f, t> {
T convertir (f de);
}
Convertidor <string, integer> converter = (from) -> integer.valueOf (from);
Entero convertido = convertidor.convert ("123");
System.out.println (convertido);
Cabe señalar que si @FunctionalInterface no se especifica, el código anterior también es correcto.
La nota del traductor mapea las expresiones de Lambda a una interfaz de método único. Función.
4. Método y referencia de constructor
El código en la sección anterior también puede representarse mediante referencias de método estático:
La copia del código es la siguiente:
Convertidor <String, Integer> Converter = Integer :: valueOf;
Entero convertido = convertidor.convert ("123");
System.out.println (convertido);
Java 8 le permite usar la palabra clave :: para pasar un método o referencia de constructor.
La copia del código es la siguiente:
convertidor = algo :: startswith;
Cadena convertida = convertidor.convert ("java");
System.out.println (convertido);
Echemos un vistazo a cómo se hace referencia a los constructores usando la palabra clave ::.
La copia del código es la siguiente:
persona de clase {
Cadena FirstName;
Cadena lastname;
Persona() {}
Persona (string firstName, String LastName) {
this.FirstName = FirstName;
this.lastName = LastName;
}
}
A continuación, especificamos una interfaz de fábrica de objetos para crear objetos de persona:
La copia del código es la siguiente:
interfaz personafactory <p extiende persona> {
P create (string firstName, string hastname);
}
Aquí utilizamos referencias de constructor para asociarlas en lugar de implementar una fábrica completa:
La copia del código es la siguiente:
Personfactory <OlPER> Personfactory = Person :: nuevo;
Persona persona = personafactory.create ("Peter", "Parker");
Solo necesitamos usar la persona :: Nuevo para obtener una referencia al constructor de la clase de persona.
5. ALCANCE DE LAMBDA
La forma de acceder a los ámbitos exteriores en expresiones Lambda es similar a la de las versiones anteriores de objetos anónimos. Puede acceder directamente a variables locales externas marcadas finales, o campos y variables estáticas de la instancia.
6. Acceda a variables locales
Podemos acceder a variables locales externas directamente en expresiones lambda:
La copia del código es la siguiente:
final int num = 1;
Convertidor <integer, string> stringConverter =
(desde) -> string.ValueOf (de + num);
StringConverter.Convert (2);
Pero a diferencia de los objetos anónimos, el número variable aquí puede declararse como final sin declararlo como final, y el código también es correcto:
La copia del código es la siguiente:
int num = 1;
Convertidor <integer, string> stringConverter =
(desde) -> string.ValueOf (de + num);
StringConverter.Convert (2);
Sin embargo, Num aquí no debe ser modificado por el código posterior (es decir, implícitamente tiene semántica final), por ejemplo, lo siguiente no se puede compilar:
La copia del código es la siguiente:
int num = 1;
Convertidor <integer, string> stringConverter =
(desde) -> string.ValueOf (de + num);
num = 3;
Intentar modificar NUM en expresiones lambda tampoco está permitido.
7. Acceso a campos de objetos y variables estáticas
A diferencia de las variables locales, los campos y las variables estáticas dentro de la lambda se pueden leer y escribir. Este comportamiento es consistente con los objetos anónimos:
Copie el código de la siguiente manera: clase Lambda4 {
static intuterstaticnum;
intuternum;
vacío testScopes () {
Convertidor <integer, string> stringConverter1 = (from) -> {
OUTERNUM = 23;
return string.ValueOf (desde);
};
Convertidor <integer, string> stringConverter2 = (from) -> {
exterstaticnum = 72;
return string.ValueOf (desde);
};
}
}
8. El método predeterminado para acceder a la interfaz
Recuerde el ejemplo de fórmula en la primera sección.
No se puede acceder al método predeterminado en la expresión de Lambda, y el siguiente código no se compilará:
La copia del código es la siguiente:
Fórmula de fórmula = (a) -> sqrt (a * 100);
Interfaces funcionales incorporadas
La API JDK 1.8 contiene muchas interfaces funcionales incorporadas, como el comparador o las interfaces ejecutables comúnmente utilizadas en Java antigua, y estas interfaces han agregado la anotación de Interface @Functional para que se usen en Lambdas.
La API Java 8 también proporciona muchas interfaces funcionales nuevas para hacer que el trabajo sea más conveniente.
Interfaz predicado
La interfaz de predicado tiene solo un parámetro, que devuelve el tipo booleano. Esta interfaz contiene una serie de métodos predeterminados para combinar predicado en otras lógicas complejas (como: versus o, no):
La copia del código es la siguiente:
Predicado <String> predicate = (s) -> s.length ()> 0;
predicto.test ("foo");
predicto.negate (). test ("foo");
Predicado <Boolean> nonnull = objets :: nonnull;
Predicado <Boolean> isNull = Objects :: isNull;
Predicado <String> isEtimty = string :: isEtimty;
Predicado <String> isNotEmpty = isEmpty.Negate ();
Interfaz de función
La interfaz de función tiene un parámetro y devuelve un resultado, y viene con algunos métodos predeterminados (componer, y entonces) que se pueden combinar con otras funciones:
La copia del código es la siguiente:
Function <string, integer> toInteger = integer :: valueOf;
Function <string, string> backToString = toInteger.andthen (string :: valueOf);
backtoString.apply ("123");
Interfaz de proveedor
La interfaz del proveedor devuelve un valor de cualquier tipo.
La copia del código es la siguiente:
Proveedor <Oll> Personsupplier = Person :: nuevo;
Personsupplier.get ();
Interfaz de consumo
La interfaz del consumidor representa la operación realizada en un solo parámetro.
La copia del código es la siguiente:
Consumer <Oll> gooder = (P) -> System.out.println ("Hola", + P.FirstName);
saluder.accept (nueva persona ("Luke", "Skywalker"));
Interfaz de comparación
Comparator es una interfaz clásica en Java antigua, y Java 8 ha agregado una variedad de métodos predeterminados:
La copia del código es la siguiente:
Comparador <Oll> Comparator = (p1, p2) -> p1.firstname.compareto (p2.firstname);
Persona p1 = nueva persona ("John", "Doe");
Persona P2 = nueva persona ("Alice", "Wonderland");
comparador.compare (P1, P2);
Comparator.Reversed (). Comparar (P1, P2);
Interfaz opcional
Opcional no es una función, pero una interfaz.
Opcional se define como un contenedor simple cuyo valor puede ser nulo o no. Antes de Java 8, una función generalmente debe devolver un objeto no vacío, pero ocasionalmente puede devolver nulo.
La copia del código es la siguiente:
Opcional <String> Oppectional = Oppectional.of ("Bam");
Opcional.ispresent ();
opcional.get ();
Opcional.orelse ("Fallback");
opcional.ifpresent ((s) -> system.out.println (s.charat (0)));
Interfaz de transmisión
java.util.stream representa una secuencia de operaciones que se pueden aplicar a un conjunto de elementos a la vez. Las operaciones de flujo se dividen en dos tipos: operaciones intermedias o operaciones finales. La creación de Stream requiere especificar una fuente de datos, como una subclase de java.util.collection, list o set, que no es compatible con MAP. Las operaciones de transmisión se pueden ejecutar en serie o en paralelo.
Primero, veamos cómo se usa el flujo.
La copia del código es la siguiente:
List <String> StringCollection = New ArrayList <> ();
StringCollection.Add ("DDD2");
StringCollection.Add ("AAA2");
StringCollection.Add ("BBB1");
StringCollection.Add ("AAA1");
StringCollection.Add ("BBB3");
StringCollection.Add ("CCC");
StringCollection.Add ("BBB2");
StringCollection.Add ("DDD1");
Java 8 extiende la clase de colección y puede crear una transmisión a través de Collection.stream () o Collection.ParallelStream (). Las siguientes secciones explicarán las operaciones de flujo de uso común en detalle:
Filtro de filtro
El filtrado se filtra a través de una interfaz de predicado y solo se mantienen elementos que cumplan con los criterios. foreach requiere una función para ejecutar elementos filtrados en secuencia. foreach es una operación final, por lo que no podemos realizar otras operaciones de transmisión después de foreach.
La copia del código es la siguiente:
StringCollection
.arroyo()
.filter ((s) -> S.Startswith ("A"))
.ForEach (System.out :: println);
// "AAA2", "AAA1"
Ordenar
Sort es una operación intermedia, y la transmisión devuelta después de que se devuelve la clasificación. Si no especifica un comparador personalizado, se utilizará el tipo predeterminado.
La copia del código es la siguiente:
StringCollection
.arroyo()
.sorted ()
.filter ((s) -> S.Startswith ("A"))
.ForEach (System.out :: println);
// "AAA1", "AAA2"
Cabe señalar que la clasificación solo crea un flujo dispuesto y no afectará la fuente de datos original.
La copia del código es la siguiente:
System.out.println (StringCollection);
// DDD2, AAA2, BBB1, AAA1, BBB3, CCC, BBB2, DDD1
Mapeo de mapas
El mapa de operación intermedio convierte elementos en objetos adicionales en secuencia de acuerdo con la interfaz de función especificada. También puede usar MAP para convertir objetos a otros tipos.
La copia del código es la siguiente:
StringCollection
.arroyo()
.map (string :: touppercase)
.sorted ((a, b) -> b.compareto (a))
.ForEach (System.out :: println);
// "ddd2", "ddd1", "ccc", "bbb3", "bbb2", "aaa2", "aaa1"
Fósforo
Stream proporciona una variedad de operaciones coincidentes, lo que permite la detección de si un predicado especificado coincide con toda la transmisión. Todas las operaciones coincidentes son operaciones finales y devuelven un valor de tipo booleano.
La copia del código es la siguiente:
boolean anystartswitha =
StringCollection
.arroyo()
.anymatch ((s) -> S.Startswith ("A"));
System.out.println (cualquiera
boolean AllStartswitha =
StringCollection
.arroyo()
.allmatch ((s) -> S.Startswith ("A"));
System.out.println (AllStartswitha);
booleano no se inicia
StringCollection
.arroyo()
.nonematch ((s) -> s.Startswith ("z"));
System.out.println (NonestStwithz);
Contar
El recuento es una operación final, que devuelve el número de elementos en la transmisión, y el tipo de valor de retorno es largo.
La copia del código es la siguiente:
Largo comienzo a la vez =
StringCollection
.arroyo()
.filter ((s) -> S.Startswith ("B"))
.contar();
System.out.println (Startswithb);
Reducir las regulaciones
Esta es una operación final, que permite definir múltiples elementos en la secuencia como un elemento a través de la función especificada, y el resultado de la elegibilidad está representado por la interfaz opcional:
La copia del código es la siguiente:
Opcional <String> Reducido =
StringCollection
.arroyo()
.sorted ()
.Reduce ((S1, S2) -> S1 + "#" + S2);
reducido.ifpresente (system.out :: println);
// "AAA1#AAA2#BB1#BB2#BB3#CCC#DDD1#DDD2"
Flujos paralelos
Como se mencionó anteriormente, Stream tiene dos tipos: serial y paralelo.
El siguiente ejemplo muestra cómo mejorar el rendimiento a través de la transmisión paralela:
Primero creamos una tabla grande sin elementos duplicados:
La copia del código es la siguiente:
int max = 1000000;
List <String> valores = new ArrayList <> (max);
para (int i = 0; i <max; i ++) {
Uuid uuid = uuid.randomuuid ();
valores.add (uuid.ToString ());
}
Luego calculamos cuánto tiempo lleva ordenar esta transmisión.
Sorteo de serie:
La copia del código es la siguiente:
largo T0 = System.nanotime ();
long count = value.stream (). Sorted (). Count ();
System.out.println (Count);
largo T1 = System.nanotime ();
Long Millis = TimeUnit.nanoseconds.tomillis (T1 - T0);
System.out.println (String.Format ("Sequentent Sorte Toke: %D MS", Millis));
// Tiempo de serie: 899 ms
Clasificación paralela:
La copia del código es la siguiente:
largo T0 = System.nanotime ();
long count = value.parallelStream (). Sorted (). Count ();
System.out.println (Count);
largo T1 = System.nanotime ();
Long Millis = TimeUnit.nanoseconds.tomillis (T1 - T0);
System.out.println (string.format ("se tomó en paralelo: %d ms", milis));
// La clasificación paralela lleva tiempo: 472 ms
Los dos códigos anteriores son casi los mismos, pero la versión paralela es tan rápida como el 50%.
Mapa
Como se mencionó anteriormente, el tipo de mapa no es compatible con la transmisión, pero MAP proporciona algunos métodos nuevos y útiles para manejar algunas tareas diarias.
La copia del código es la siguiente:
Map <integer, string> map = new HashMap <> ();
para (int i = 0; i <10; i ++) {
map.putifabsent (i, "val" + i);
}
map.ForEach ((id, val) -> System.out.println (val));
El código anterior es fácil de entender.
El siguiente ejemplo muestra otras funciones útiles en el mapa:
La copia del código es la siguiente:
map.computeifPresent (3, (num, val) -> val + num);
map.get (3);
map.computeifPresent (9, (num, val) -> nulo);
map.containskey (9);
map.computeifabsent (23, num -> "val" + num);
map.containskey (23);
map.computeifabsent (3, num -> "bam");
map.get (3);
A continuación, muestre cómo eliminar un elemento en el mapa que coincida con todas las claves:
La copia del código es la siguiente:
map.remove (3, "val3");
map.get (3);
map.remove (3, "val33");
map.get (3);
Otro método útil:
La copia del código es la siguiente:
map.getorDefault (42, "no encontrado");
También es fácil fusionar los elementos del mapa:
La copia del código es la siguiente:
map.merge (9, "val9", (valor, newValue) -> value.concat (newValue));
map.get (9);
map.merge (9, "concat", (valor, newValue) -> value.concat (newValue));
map.get (9);
Lo que Merge hace es insertar si el nombre de la clave no existe, de lo contrario, fusionar el valor correspondiente a la clave original y reinsertar en el mapa.
9. Fecha API
Java 8 contiene un nuevo conjunto de API de tiempo y fecha en el paquete Java.Time. La nueva API de fecha es similar a la Biblioteca de Tiempo Joda de código abierto, pero no es exactamente el mismo.
Reloj de reloj
La clase de reloj proporciona un método para acceder a la fecha y hora actuales. La clase instantánea también puede representar un punto específico en el tiempo, que también puede usarse para crear antiguos objetos Java.Util.Date.
La copia del código es la siguiente:
Reloj de reloj = reloj.SystemDefaultzone ();
long milis = clock.millis ();
Instant instant = clock.instant ();
Date legacydate = date.From (instantáneo);
Zona horaria
En la nueva API, la zona horaria está representada por ZoneID. La zona horaria se puede obtener fácilmente utilizando el método estático de. La zona horaria define la diferencia de tiempo en el tiempo de UTS, que es extremadamente importante al convertir entre el objeto de punto de tiempo instantáneo y el objeto de fecha local.
La copia del código es la siguiente:
System.out.println (ZoneId.getAvailableZoneIds ());
// Imprime todas las ID de zona horaria disponibles
ZoneId Zone1 = ZoneId.of ("Europa/Berlín");
ZoneId Zone2 = ZoneId.of ("Brasil/East");
System.out.println (Zone1.getRules ());
System.out.println (Zone2.getRules ());
// Zonerules [CurrentStandarDoffset =+01: 00]
// Zonerules [CurrentStandarDoffset = -03: 00]
Hora local de tiempo local
Localtime define un tiempo sin información de la zona horaria, como las 10 pm, o 17:30:15. El siguiente ejemplo crea dos tiempos locales utilizando la zona horaria creada por el código anterior. Luego se compara el tiempo y la diferencia de tiempo entre las dos veces se calcula en horas y minutos:
La copia del código es la siguiente:
Localtime Now1 = localtime.now (Zone1);
Localtime ahora2 = localtime.now (zona2);
System.out.println (ahora1.isbefore (ahora2));
largas horasbetween = cronounit.hours.between (ahora1, ahora2);
largos minutosbetween = Chronounit.Minutes.
System.out.println (SoursBetween);
System.out.println (Minutesbetween);
Localtime proporciona una variedad de métodos de fábrica para simplificar la creación de objetos, incluidas las cadenas de tiempo de análisis.
La copia del código es la siguiente:
Localtime tardío = Localtime.of (23, 59, 59);
System.out.println (tarde);
DateTimeFormatter GermanFormatter =
DateTimeFormatter
.flocalizedTime (FormatStyle.short)
.withlocale (locale.german);
Localtime leettime = localtime.parse ("13:37", GermanFormatter);
System.out.println (leettime);
Fecha local de fecha local
LocalDate representa una fecha exacta, como 2014-03-11. El valor de este objeto es inmutable y es básicamente el mismo que el de Localtime. El siguiente ejemplo muestra cómo agregar/luna/año a un objeto de fecha. También tenga en cuenta que estos objetos son inmutables, y la operación devuelve una nueva instancia.
La copia del código es la siguiente:
LocalDate hoy = localDate.now ();
Localdate mañana = hoy.plus (1, cronounit.days);
Localdate ayer = mañana.Minusdays (2);
LocalDate IndependEnEnday = localDate.Of (2014, mes. Julio, 4);
Dayofweek dayofweek = IndependEnEnderay.getdayOfWeek ();
System.out.println (Dayofweek);
Analizar un tipo de Date Local de una cadena es tan simple como analizar local a tiempo:
La copia del código es la siguiente:
DateTimeFormatter GermanFormatter =
DateTimeFormatter
.oflocalizedDate (FormatStyle.Medium)
.withlocale (locale.german);
Localdate xmas = localDate.Parse ("24.12.2014", GermanFormatter);
System.out.println (Navidad);
LocalDatetime Local DateTime
LocalDateTime representa la hora y la fecha, lo cual es equivalente a la fusión del contenido de las dos primeras secciones en un solo objeto. LocalDatetime, como Localtime y LocalDate, son inmutables. LocalDateTime proporciona algunos métodos para acceder a campos específicos.
La copia del código es la siguiente:
LocalDateTime Sylvester = LocalDateTime.of (2014, mes. Diciembre, 31, 23, 59, 59);
Dayofweek dayofweek = sylvester.getdayofweek ();
System.out.println (Dayofweek);
Mes mes = sylvester.getMonth ();
System.out.println (mes);
largo minutofday = sylvester.getLong (chronofield.minute_of_day);
System.out.println (MinuteOfday);
Simplemente adjunte la información de la zona horaria y se puede convertir en un objeto instantáneo de punto de tiempo.
La copia del código es la siguiente:
Instantáneo instantáneo = sylvester
.atzone (ZoneID.SystemDefault ())
.toinstant ();
Date legacyDate = date.From (instantáneo);
System.out.println (LegacyDate);
El formato de LocalDateTeTime es lo mismo que el tiempo y la fecha de formato.
La copia del código es la siguiente:
DateTimeFormatter Formatter =
DateTimeFormatter
.ofpattern ("mmm dd, yyyy - hh: mm");
LocalDateTime Parsed = localDateTime.Parse ("03 de noviembre de 2014 - 07:13", Formatter);
Cadena String = formatter.Format (parsed);
System.out.println (cadena);
A diferencia de java.text.numberFormat, la nueva versión de DateTimeFormatter es inmutable, por lo que es segura de hilo.
Información detallada sobre el formato de tiempo y fecha: http://download.java.net/jdk8/docs/api/java/time/format/datetimeformatter.html
10. Notas de anotación
Se admiten múltiples anotaciones en Java 8. Primero veamos un ejemplo para comprender lo que significa.
Primero, defina una anotación de sugerencias envasadas para colocar un conjunto específico de anotaciones de pistas:
La copia del código es la siguiente:
@Interface Sugeres {
Sugerir [] valor ();
}
@Repeatable (Hints.Class)
@Interface Sugerencia {
Valor de cadena ();
}
Java 8 nos permite usar anotaciones del mismo tipo varias veces, simplemente etiquete la anotación @Repeatable.
Ejemplo 1: Use la clase de envoltorio como contenedor para almacenar múltiples anotaciones (método anterior)
La copia del código es la siguiente:
@Hints ({ @tint ("tint1"), @hint ("hint2")})
Persona de clase {}
Ejemplo 2: Uso de anotaciones múltiples (nuevo método)
La copia del código es la siguiente:
@Hint ("Hint1")
@Hint ("Hint2")
Persona de clase {}
En el segundo ejemplo, el compilador Java definirá implícitamente la anotación de @Hints para usted.
La copia del código es la siguiente:
Sugerencia de sugerencias = persona.class.getAnnotation (tintin.class);
System.out.println (Sugerencia);
Sugerencias de sugerencias1 = persona.class.getAnnotation (sugerencias.class);
System.out.println (Sugerencias1.Value (). Longitud);
Sugerencia [] Sugerencias2 = Person.class.getAnnotationsByType (hint.class);
System.out.println (sugerencias2.length);
Incluso si no definimos la anotación de @Hints en la clase de la persona, aún podemos obtener la anotación de @Hints a través de GetAnnotation (sugerencias. Clase).
Además, se han agregado anotaciones Java 8 a dos nuevos objetivos:
La copia del código es la siguiente:
@Target ({elementtype.type_parameter, elementtype.type_use})
@Interface MyAnnotation {}
Se trata de las nuevas características de Java 8, y definitivamente hay más características esperando ser descubiertas. Hay muchas cosas útiles en JDK 1.8, como matrices.