1. Información general
En este artículo, echaremos un vistazo rápido a algunas de las nuevas funciones más interesantes de Java 8.
Hablaremos de: métodos predeterminados y estáticos de la interfaz, referencia de método y opcional.
Ya hemos cubierto algunas de las características de la versión de Java 8 (API de transmisión, expresiones lambda e interfaces funcionales), ya que son temas completos que merecen una mirada por separado.
2. Métodos estáticos y predeterminados de la interfaz
Antes de Java 8, las interfaces solo podían tener métodos abstractos públicos. No fue posible agregar nueva funcionalidad a la interfaz existente sin forzar a todas las clases de implementación a crear una implementación de los nuevos métodos, ni fue posible crear métodos de interfaz con una implementación.
A partir de Java 8, las interfaces pueden tener métodos estáticos y predeterminados que, a pesar de estar declarados en una interfaz, tienen un comportamiento definido.
2.1. Método estático
Considere el siguiente método de la interfaz (llamemos a esta interfaz Vehículo ):
static String producer() { return "N&F Vehicles"; }
El método productor estático () está disponible solo a través y dentro de una interfaz. No puede ser anulado por una clase de implementación.
Para llamarlo fuera de la interfaz, se debe usar el enfoque estándar para la llamada al método estático:
String producer = Vehicle.producer();
2.2. Método predeterminado
Los métodos predeterminados se declaran utilizando la nueva palabra clave predeterminada . Estos son accesibles a través de la instancia de la clase de implementación y se pueden anular.
Agreguemos un método predeterminado a nuestra interfaz de vehículo , que también hará una llamada al método estático de esta interfaz:
default String getOverview() { return "ATV made by " + producer(); }
Suponga que esta interfaz está implementada por la clase VehicleImpl. Para ejecutar el método predeterminado , se debe crear una instancia de esta clase:
Vehicle vehicle = new VehicleImpl(); String overview = vehicle.getOverview();
3. Referencias de métodos
La referencia de método se puede usar como una alternativa más corta y legible para una expresión lambda que solo llama a un método existente. Hay cuatro variantes de referencias de métodos.
3.1. Referencia a un método estático
La referencia a un método estático tiene la siguiente sintaxis: ContainingClass :: methodName.
Intentemos contar todas las cadenas vacías en la Lista con la ayuda de Stream API.
boolean isReal = list.stream().anyMatch(u -> User.isRealUser(u));
Eche un vistazo más de cerca a la expresión lambda en el método anyMatch () , solo hace una llamada a un método estático isRealUser (Usuario usuario) de la clase Usuario . Por tanto, puede sustituirse por una referencia a un método estático:
boolean isReal = list.stream().anyMatch(User::isRealUser);
Este tipo de código parece mucho más informativo.
3.2. Referencia a un método de instancia
La referencia a un método de instancia tiene la siguiente sintaxis: c ontainingInstance :: methodName. El siguiente código llama al método isLegalName (String string) de tipo User que valida un parámetro de entrada:
User user = new User(); boolean isLegalName = list.stream().anyMatch(user::isLegalName);
3.3. Referencia a un método de instancia de un objeto de un tipo particular
Este método de referencia tiene la siguiente sintaxis: C ontainingType :: methodName. Un ejemplo::
long count = list.stream().filter(String::isEmpty).count();
3.4. Referencia a un constructor
Una referencia a un constructor toma la siguiente sintaxis: ClassName :: new. Como el constructor en Java es un método especial, la referencia del método también se podría aplicar con la ayuda de new como nombre de método .
Stream stream = list.stream().map(User::new);
4. Opcional
Antes de Java 8, los desarrolladores tenían que validar cuidadosamente los valores a los que se referían, debido a la posibilidad de lanzar NullPointerException (NPE) . Todas estas comprobaciones exigían un código repetitivo bastante molesto y propenso a errores.
La clase opcional Java 8 puede ayudar a manejar situaciones en las que existe la posibilidad de obtener el NPE . Funciona como un contenedor para el objeto de tipo T. Puede devolver un valor de este objeto si este valor no es nulo . Cuando el valor dentro de este contenedor es nulo , permite realizar algunas acciones predefinidas en lugar de lanzar NPE.
4.1. Creación del Opcional
Se puede crear una instancia de la clase Optional con la ayuda de sus métodos estáticos:
Optional optional = Optional.empty();
Devuelve un opcional vacío .
String str = "value"; Optional optional = Optional.of(str);
Devuelve un opcional que contiene un valor no nulo.
Optional optional = Optional.ofNullable(getString());
Will return an Optional with a specific value or an empty Optional if the parameter is null.
4.2. Optional Usage
For example, you expect to get a List and in the case of null you want to substitute it with a new instance of an ArrayList. With pre-Java 8's code you need to do something like this:
List list = getList(); List listOpt = list != null ? list : new ArrayList();
With Java 8 the same functionality can be achieved with a much shorter code:
List listOpt = getList().orElseGet(() -> new ArrayList());
There is even more boilerplate code when you need to reach some object's field in the old way. Assume you have an object of type User which has a field of type Address with a field street of type String. And for some reason you need to return a value of the street field if some exist or a default value if street is null:
User user = getUser(); if (user != null) { Address address = user.getAddress(); if (address != null) { String street = address.getStreet(); if (street != null) { return street; } } } return "not specified";
This can be simplified with Optional:
Optional user = Optional.ofNullable(getUser()); String result = user .map(User::getAddress) .map(Address::getStreet) .orElse("not specified");
In this example we used the map() method to convert results of calling the getAdress() to the Optional and getStreet() to Optional. If any of these methods returned null the map() method would return an empty Optional.
Imagine that our getters return Optional. So, we should use the flatMap() method instead of the map():
Optional optionalUser = Optional.ofNullable(getOptionalUser()); String result = optionalUser .flatMap(OptionalUser::getAddress) .flatMap(OptionalAddress::getStreet) .orElse("not specified");
Another use case of Optional is changing NPE with another exception. So, as we did previously, let's try to do this in pre-Java 8's style:
String value = null; String result = ""; try { result = value.toUpperCase(); } catch (NullPointerException exception) { throw new CustomException(); }
And what if we use Optional? The answer is more readable and simpler:
String value = null; Optional valueOpt = Optional.ofNullable(value); String result = valueOpt.orElseThrow(CustomException::new).toUpperCase();
Notice, that how and for what purpose to use Optional in your app is a serious and controversial design decision, and explanation of its all pros and cons is out of the scope of this article. If you are interested, you can dig deeper, there are plenty of interesting articles on the Internet devoted to this problem. This one and this another one could be very helpful.
5. Conclusion
In this article, we are briefly discussing some interesting new features in Java 8.
There are of course many other additions and improvements which are spread across many Java 8 JDK packages and classes.
But, the information illustrated in this article is a good starting point for exploring and learning about some of these new features.
Finalmente, todo el código fuente del artículo está disponible en GitHub.