Usos para opcional


271

Después de haber estado utilizando Java 8 ahora durante más de 6 meses, estoy bastante contento con los nuevos cambios de API. Un área en la que aún no confío es cuándo usarla Optional. Parece que me pongo entre querer usarlo en todas partes donde algo pueda estar null, y en ninguna parte.

Parece que hay muchas situaciones en las que podría usarlo, y nunca estoy seguro de si agrega beneficios (legibilidad / seguridad nula) o solo causa una sobrecarga adicional.

Entonces, tengo algunos ejemplos, y estaría interesado en los pensamientos de la comunidad sobre si Optionales beneficioso.

1 - Como un método de devolución de método público cuando el método podría devolver null:

public Optional<Foo> findFoo(String id);

2 - Como parámetro de método cuando el parámetro puede ser null:

public Foo doSomething(String id, Optional<Bar> barOptional);

3 - Como miembro opcional de un bean:

public class Book {

  private List<Pages> pages;
  private Optional<Index> index;

}

4 - En Collections:

En general no pienso:

List<Optional<Foo>>

agrega cualquier cosa, especialmente porque se puede usar filter()para eliminar nullvalores, etc., pero ¿hay algún buen uso Optionalen las colecciones?

¿Algún caso que me haya perdido?


2
Un caso que encuentro útil es, por ejemplo, si tiene un mapa de sustitución. Por ejemplo Map<Character, String>. Si no hay una sustitución puedo usar esto: Optional.ofNullable(map.get(c)).orElse(String.valueOf(c)). También tenga en cuenta que Opcional fue robado de Guava y tiene una sintaxis mucho mejor:Optional.fromNullable(map.get(c)).or(String.valueOf(c));
fge

3
Además, en colecciones, bueno, ¡hay colecciones que no permiten valores nulos! Opcional se ajusta a la factura aquí. Y puede .filter(Optional::absent)eliminar los "valores nulos"
fge

2
@fge Para ser justos, creo que el concepto de Opcional en realidad se deriva de FP.
VH-NZZ

2
@fge no está mejor expresado con getOrDefault()?
Jim Garrison

Respuestas:


206

El punto principal de Optionales proporcionar un medio para que una función devuelva un valor para indicar la ausencia de un valor de retorno. Ver esta discusión . Esto permite que la persona que llama continúe una cadena de llamadas de métodos fluidas.

Esto coincide más estrechamente con el caso de uso # 1 en la pregunta del OP. Sin embargo , la ausencia de un valor es una formulación más precisa que nula ya que algo así IntStream.findFirstnunca podría volver nula.

Para el caso de uso # 2 , pasar un argumento opcional a un método, esto podría funcionar, pero es bastante torpe. Supongamos que tiene un método que toma una cadena seguida de una segunda cadena opcional. Aceptar un Optionalcomo el segundo argumento daría como resultado un código como este:

foo("bar", Optional.of("baz"));
foo("bar", Optional.empty());

Incluso aceptar nulo es mejor:

foo("bar", "baz");
foo("bar", null);

Probablemente lo mejor es tener un método sobrecargado que acepte un único argumento de cadena y proporcione un valor predeterminado para el segundo:

foo("bar", "baz");
foo("bar");

Esto tiene limitaciones, pero es mucho mejor que cualquiera de los anteriores.

Los casos de uso n.º 3 y n.º 4 , que tienen un Optionalcampo de clase o una estructura de datos, se consideran un mal uso de la API. Primero, va en contra del objetivo principal de diseño Optionalcomo se indica en la parte superior. En segundo lugar, no agrega ningún valor.

Hay tres formas de lidiar con la ausencia de un valor en un Optional: para proporcionar un valor sustituto, para llamar a una función para proporcionar un valor sustituto o para lanzar una excepción. Si está almacenando en un campo, lo haría en el momento de la inicialización o la asignación. Si está agregando valores a una lista, como lo mencionó el OP, tiene la opción adicional de simplemente no agregar el valor, "aplanando" los valores ausentes.

Estoy seguro de que alguien podría idear algunos casos artificiales en los que realmente quieran almacenar una Optionalen un campo o una colección, pero en general, es mejor evitar hacerlo.


47
No estoy de acuerdo con el n. ° 3. Puede ser útil tener un Optionalcampo como cuando las cosas se deben hacer o no con la presencia o ausencia de un valor.
glglgl

15
No veo por qué hacer if(foo == null)internamente sería mejor que simplemente almacenar el campo como optional. Y no veo por qué llamar explícitamente getOptionalFoo()internamente es mejor que simplemente almacenar el campo como un Optional. Además, si un campo puede ser nully un campo no puede ser null, ¿por qué no comunicarlo al compilador haciéndolos Optionalo no Optional?
mogronalol

27
@StuartMarks, esta es una de las cosas que me desconcierta cuando lo escucho de todos los Optional<>expertos: "no almacenar Optional<>en un campo". Realmente estoy tratando de entender el punto de esta recomendación. Considere un árbol DOM donde un nodo podría tener un padre o no. Parece dentro del caso de uso que Node.getParent()volvería Optional<Node>. ¿Realmente se espera que almacene un nully ajuste el resultado cada vez? ¿Por qué? ¿Qué me compra esta ineficiencia adicional, aparte de hacer que mi código se vea feo?
Garret Wilson

77
@GarretWilson Por otro lado, supongamos que sí Optional<Node> getParent() { return Optional.ofNullable(parent); }. ¡Esto parece caro porque asigna una Optionalcada vez! Pero si la persona que llama lo desempaqueta inmediatamente, el objeto tiene una vida extremadamente corta y nunca sale del espacio edén. Posiblemente incluso podría ser eliminado por el análisis de escape del JIT. La respuesta final es "depende" como siempre, pero el uso Optionalen campos potencialmente aumenta el uso de memoria y ralentiza el recorrido de la estructura de datos. Y finalmente creo que desordenan el código, pero esto es cuestión de gustos.
Stuart Marks

66
@GarretWilson Entonces, ¿escribiste un blog para eso? Me interesaría eso :)
akhil_mittal

78

Llego tarde al juego, pero por lo que vale, quiero agregar mis 2 centavos. Van en contra del objetivo de diseño deOptional , que está bien resumido por la respuesta de Stuart Marks , pero todavía estoy convencido de su validez (obviamente).

Usar opcional en todas partes

En general

Escribí una publicación de blogOptional completa sobre el uso, pero básicamente se reduce a esto:

  • diseñe sus clases para evitar la opcionalidad siempre que sea posible
  • en todos los casos restantes, el valor predeterminado debería ser usar en Optionallugar denull
  • posiblemente hacer excepciones para:
    • variables locales
    • devolver valores y argumentos a métodos privados
    • bloques de código de rendimiento crítico (sin adivinanzas, use un generador de perfiles)

Las dos primeras excepciones pueden reducir la sobrecarga percibida de envolver y desenvolver referencias en Optional. Se eligen de manera que un valor nulo nunca pueda pasar legalmente un límite de una instancia a otra.

Tenga en cuenta que esto casi nunca permitirá Optionals en colecciones que es casi tan malo como nulls. Solo no lo hagas. ;)

Con respecto a sus preguntas

  1. Si.
  2. Si la sobrecarga no es una opción, sí.
  3. Si otros enfoques (subclases, decoración, ...) no son una opción, sí.
  4. ¡Por favor no!

Ventajas

Hacer esto reduce la presencia de nulls en su base de código, aunque no los erradica. Pero ese ni siquiera es el punto principal. Hay otras ventajas importantes:

Aclara la intención

El uso Optionalexpresa claramente que la variable es, bueno, opcional. Cualquier lector de su código o consumidor de su API será derrotado con el hecho de que podría no haber nada allí y que es necesario realizar una verificación antes de acceder al valor.

Elimina la incertidumbre

Sin Optionalel significado de un nullhecho no está claro. Podría ser una representación legal de un estado (ver Map.get) o un error de implementación como una inicialización faltante o fallida.

Esto cambia dramáticamente con el uso persistente de Optional. Aquí, ya la aparición de nullsignifica la presencia de un error. (Porque si se permitiera que faltara el valor, Optionalse habría utilizado un.) Esto hace que la depuración de una excepción de puntero nulo sea mucho más fácil ya que la pregunta sobre el significado de esto nullya está respondida.

Más cheques nulos

Ahora que nada puede ser nullmás, esto puede hacerse cumplir en todas partes. Ya sea con anotaciones, aserciones o comprobaciones simples, nunca tendrá que pensar si este argumento o ese tipo de retorno pueden ser nulos. No puede!

Desventajas

Por supuesto, no hay bala de plata ...

Actuación

Ajustar los valores (especialmente los primitivos) a una instancia adicional puede degradar el rendimiento. En lazos cerrados, esto podría volverse notable o incluso peor.

Tenga en cuenta que el compilador podría evitar la referencia adicional para vidas cortas de Optionals. En Java 10 , los tipos de valor pueden reducir o eliminar aún más la penalización.

Publicación por entregas

Optionalno es serializable, pero una solución alternativa no es demasiado complicada.

Invariancia

Debido a la invariabilidad de los tipos genéricos en Java, ciertas operaciones se vuelven engorrosas cuando el tipo de valor real se inserta en un argumento de tipo genérico. Aquí se da un ejemplo (ver "Polimorfismo paramétrico") .


Otra desventaja (o limitación) es que no puede ordenar instancias opcionales (xxx), aunque Oracle impuso claramente esa restricción deliberadamente. Soy escéptico sobre la sabiduría de tener estas clases disponibles donde solo deberían usarse en circunstancias específicas, y suponiendo que el desarrollador haga lo correcto. Resolver un problema puede causar otro. Tal vez debería haber advertencias del compilador para ayudar al desarrollador al usar las clases Opcionales (xxx) de manera "indeseable" (por ejemplo, como parámetros de método, en colecciones, en constructores, etc.) si ese no es el uso previsto.
skomisa

Aproximadamente 4, opcionales en colecciones: en realidad, a veces es útil almacenar opcionales en Lists. Este es el caso cuando es importante que cierto elemento esté ubicado en un cierto índice, pero el elemento puede estar presente o no. Eso se comunica claramente por un List<Optional<T>>tipo. Si, por otro lado, solo es importante qué objetos que son miembros alguna colección, entonces no tiene sentido.
Lii

3
Creo que el caso de uso # 2 sigue siendo muy cuestionable. Un Optionalser pasado a un método puede ser null. Entonces, ¿qué has ganado? Ahora tienes que hacer dos cheques. Ver stackoverflow.com/a/31923042/650176
David V el

2
@DavidV mira la lista de ventajas que le di: todas se aplican también a ese caso. Además, si lo haces todo Optional(lo que debería ser el caso si estás dispuesto a pasarlo como argumento) nullnunca es un valor legal. Entonces, llamar a un método con un parámetro explícito nulles obviamente un error.
Nicolai

28

Personalmente, prefiero usar la herramienta de inspección de código de IntelliJ para usar @NotNully @Nullableverificar, ya que estos son en gran parte tiempo de compilación (puede tener algunas verificaciones de tiempo de ejecución) Esto tiene una sobrecarga menor en términos de legibilidad de código y rendimiento de tiempo de ejecución. No es tan riguroso como usar Opcional, sin embargo, esta falta de rigor debería estar respaldada por pruebas unitarias decentes.

public @Nullable Foo findFoo(@NotNull String id);

public @NotNull Foo doSomething(@NotNull String id, @Nullable Bar barOptional);

public class Book {

  private List<Pages> pages;
  private @Nullable Index index;

}

List<@Nullable Foo> list = ..

Esto funciona con Java 5 y no es necesario ajustar y desenvolver valores. (o crear objetos de envoltura)


77
Uhm, ¿son esas anotaciones de IDEA? Prefiero JSR 305 @Nonnullpersonalmente - trabaja con FindBugs;)
fge

@fge Hay una falta de estándares en este sentido, pero creo que las herramientas son generalmente configurables y no tiene que terminar con@Nonnull @NotNull etc
Peter Lawrey

44
Si eso es verdad; IDEA (13.x) ofrece tres opciones diferentes ... Meh, siempre termino usando JSR 305 anywa
fge

3
Sé que esto es antiguo, pero la discusión sobre anotaciones y herramientas merece un enlace a stackoverflow.com/questions/35892063/… , que por cierto aborda específicamente el caso de Java 8.
Stephan Herrmann

25

Creo que Guava Opcional y su página wiki lo explican bastante bien:

Además del aumento en la legibilidad que viene de dar un nombre nulo, la mayor ventaja de Opcional es su prueba de idiota. Te obliga a pensar activamente en el caso ausente si quieres que tu programa se compile, ya que tienes que desenvolver activamente el Opcional y abordar ese caso. Null hace que sea inquietantemente fácil olvidar las cosas, y aunque FindBugs ayuda, no creemos que aborde el problema casi tan bien.

Esto es especialmente relevante cuando devuelve valores que pueden o no estar "presentes". Usted (y otros) tienen muchas más probabilidades de olvidar que other.method (a, b) podría devolver un valor nulo de lo que es probable que olvide que a podría ser nulo cuando implementa other.method. Devolver Opcional hace que sea imposible para las personas que llaman olvidar ese caso, ya que tienen que desenvolver el objeto ellos mismos para que su código se compile. - (Fuente: Guava Wiki - Uso y evitación de nulo - ¿Cuál es el punto? )

Optionalagrega algo de sobrecarga, pero creo que su clara ventaja es hacer explícito que un objeto podría estar ausente y exige que los programadores manejen la situación. Impide que alguien olvide el querido != nullcheque.

Tomando el ejemplo de 2 , creo que es un código mucho más explícito para escribir:

if(soundcard.isPresent()){
  System.out.println(soundcard.get());
}

que

if(soundcard != null){
  System.out.println(soundcard);
}

Para mí, Optionalmejor captura el hecho de que no hay una tarjeta de sonido presente.

Mis 2 ¢ sobre tus puntos:

  1. public Optional<Foo> findFoo(String id);- No estoy seguro de esto. Tal vez devolvería un Result<Foo>que podría estar vacío o contener un Foo. Es un concepto similar, pero en realidad no Optional.
  2. public Foo doSomething(String id, Optional<Bar> barOptional);- Preferiría @Nullable y una comprobación de findbugs, como en la respuesta de Peter Lawrey - vea también esta discusión .
  3. Su ejemplo de libro: no estoy seguro de si usaría el Opcional internamente, eso podría depender de la complejidad. Para la "API" de un libro, usaría un Optional<Index> getIndex()para indicar explícitamente que el libro podría no tener un índice.
  4. No lo usaría en colecciones, sino que no permitiría valores nulos en colecciones

En general, trataría de minimizar el paso nulls. (Una vez quemada ...) Creo que vale la pena encontrar las abstracciones apropiadas e indicar a los demás programadores qué representa un cierto valor de retorno.


15
Tu escribirias soundcard.ifPresent(System.out::println). Llamar isPresentes semánticamente lo mismo que verificar null.
un mejor oliver

El problema para mí es que las cosas con tipos opcionales aún pueden ser nulas. Entonces, para estar completamente seguro, tienes que hacer algo como eso if(soundcard != null && soundcard.isPresent()). Aunque hacer una API que devuelva un Opcional y también podría ser nulo es algo que espero que nadie haga.
Simon Baumgardt-Wellander

"Te obliga a pensar activamente en el caso ausente si quieres que tu programa se compile, ya que tienes que desenvolver activamente el Opcional y abordar ese caso". No estoy realmente seguro de cómo fuerza esto, aunque no trabajo mucho en Java. ¿Podría explicar qué hace Java para obligarlo a desenvolver Y verificar el caso! IsPresent simplemente para compilar?
aplastar

15

Del tutorial de Oracle :

El propósito de Opcional no es reemplazar cada referencia nula en su base de código, sino más bien ayudar a diseñar mejores API en las que, simplemente leyendo la firma de un método, los usuarios puedan saber si esperan un valor opcional. Además, Opcional lo obliga a desenvolver activamente un Opcional para lidiar con la ausencia de un valor; como resultado, protege su código contra excepciones de puntero nulo no intencionadas.


3
API solo para la parte de retorno del método, ¿verdad? ¿Los opcionales no deben usarse como parámetros debido a la eliminación de tipo? a veces utilizo un Opcional internamente en un método, convertido a partir de un parámetro anulable o como un campo inicializado a partir de un parámetro de constructor nulo ... todavía estoy tratando de averiguar si eso es más útil que dejarlo como nulo: ¿alguien tiene alguna idea?
ycomp

@ycomp Puede encontrar mucha información sobre esos casos en esta o aquella pregunta. Las respuestas a esas preguntas dicen mucho más de lo que se preguntó y cubren otros casos. Para obtener más información, navegue más o haga su propia pregunta, porque probablemente no obtendrá mucha atención aquí. ; )
ctomek

8

1 - Como un método de devolución de método público cuando el método podría devolver nulo:

Aquí hay un buen artículo que muestra la utilidad del caso de uso # 1. Hay este código

...
if (user != null) {
    Address address = user.getAddress();
    if (address != null) {
        Country country = address.getCountry();
        if (country != null) {
            String isocode = country.getIsocode();
            isocode = isocode.toUpperCase();
        }
    }
}
...

se transforma a esto

String result = Optional.ofNullable(user)
  .flatMap(User::getAddress)
  .flatMap(Address::getCountry)
  .map(Country::getIsocode)
  .orElse("default");

utilizando Opcional como valor de retorno de los métodos getter respectivos .


2

Aquí hay un uso interesante (creo) para ... Pruebas.

Tengo la intención de probar uno de mis proyectos y, por lo tanto, construir afirmaciones; solo hay cosas que tengo que verificar y otras que no.

Por lo tanto, construyo cosas para afirmar y uso una afirmación para verificarlas, así:

public final class NodeDescriptor<V>
{
    private final Optional<String> label;
    private final List<NodeDescriptor<V>> children;

    private NodeDescriptor(final Builder<V> builder)
    {
        label = Optional.fromNullable(builder.label);
        final ImmutableList.Builder<NodeDescriptor<V>> listBuilder
            = ImmutableList.builder();
        for (final Builder<V> element: builder.children)
            listBuilder.add(element.build());
        children = listBuilder.build();
    }

    public static <E> Builder<E> newBuilder()
    {
        return new Builder<E>();
    }

    public void verify(@Nonnull final Node<V> node)
    {
        final NodeAssert<V> nodeAssert = new NodeAssert<V>(node);
        nodeAssert.hasLabel(label);
    }

    public static final class Builder<V>
    {
        private String label;
        private final List<Builder<V>> children = Lists.newArrayList();

        private Builder()
        {
        }

        public Builder<V> withLabel(@Nonnull final String label)
        {
            this.label = Preconditions.checkNotNull(label);
            return this;
        }

        public Builder<V> withChildNode(@Nonnull final Builder<V> child)
        {
            Preconditions.checkNotNull(child);
            children.add(child);
            return this;
        }

        public NodeDescriptor<V> build()
        {
            return new NodeDescriptor<V>(this);
        }
    }
}

En la clase NodeAssert, hago esto:

public final class NodeAssert<V>
    extends AbstractAssert<NodeAssert<V>, Node<V>>
{
    NodeAssert(final Node<V> actual)
    {
        super(Preconditions.checkNotNull(actual), NodeAssert.class);
    }

    private NodeAssert<V> hasLabel(final String label)
    {
        final String thisLabel = actual.getLabel();
        assertThat(thisLabel).overridingErrorMessage(
            "node's label is null! I didn't expect it to be"
        ).isNotNull();
        assertThat(thisLabel).overridingErrorMessage(
            "node's label is not what was expected!\n"
            + "Expected: '%s'\nActual  : '%s'\n", label, thisLabel
        ).isEqualTo(label);
        return this;
    }

    NodeAssert<V> hasLabel(@Nonnull final Optional<String> label)
    {
        return label.isPresent() ? hasLabel(label.get()) : this;
    }
}

¡Lo que significa que la afirmación realmente solo se activa si quiero verificar la etiqueta!


2

Optionalclase le permite evitar el uso nully proporcionar una mejor alternativa:

  • Esto alienta al desarrollador a hacer comprobaciones de presencia para evitar no ser capturado NullPointerException.

  • La API se documenta mejor porque es posible ver dónde esperar los valores que pueden estar ausentes.

Optionalproporciona API conveniente para trabajar más con el objeto isPresent():; get(); orElse(); orElseGet(); orElseThrow(); map(); filter(); flatmap().

Además, muchos marcos utilizan activamente este tipo de datos y lo devuelven desde su API.


2

En Java, simplemente no los use a menos que sea adicto a la programación funcional.

No tienen lugar como argumentos de método (prometo que alguien algún día le pasará una opción nula, no solo una opción que esté vacía).

Tienen sentido para los valores de retorno, pero invitan a la clase del cliente a seguir estirando la cadena de desarrollo del comportamiento.

FP y las cadenas tienen poco lugar en un lenguaje imperativo como Java porque hace que sea muy difícil depurar, no solo leer. Cuando te acercas a la línea, no puedes conocer el estado ni la intención del programa; tiene que intervenir para resolverlo (en un código que a menudo no es suyo y muchos marcos de pila profundos a pesar de los filtros de pasos) y debe agregar muchos puntos de interrupción para asegurarse de que pueda detenerse en el código / lambda que agregó, en lugar de simplemente caminar por las líneas triviales if / else / call.

Si desea una programación funcional, elija algo más que Java y espere que tenga las herramientas para depurar eso.


1

No creo que Opcional sea un sustituto general de los métodos que potencialmente devuelven valores nulos.

La idea básica es: la ausencia de un valor no significa que esté potencialmente disponible en el futuro. Es una diferencia entre findById (-1) y findById (67).

La información principal de los opcionales para la persona que llama es que no puede contar con el valor dado, pero puede estar disponible en algún momento. Tal vez desaparecerá nuevamente y volverá más tarde una vez más. Es como un interruptor de encendido / apagado. Tiene la "opción" para encender o apagar la luz. Pero no tiene opción si no tiene una luz para encender.

Por lo tanto, me resulta demasiado desordenado introducir los opcionales en todas partes donde anteriormente se devolvió nulo. Todavía usaré nulo, pero solo en áreas restringidas como la raíz de un árbol, inicialización perezosa y métodos de búsqueda explícitos.


0

Parece Optionalsólo es útil si el tipo T en opcional es un tipo simple como int, long, char, etc Para las clases "reales", que no tiene sentido para mí, como se puede utilizar un nullvalor de todos modos.

Creo que fue tomado de aquí (o de otro concepto de lenguaje similar).

Nullable<T>

En C #, esto Nullable<T>se introdujo hace mucho tiempo para ajustar los tipos de valor.


2
Se trata de una estafa de guayaba es Optionalde hecho
FGE

2
@fge OK, pero cuando esto estaba presente en C # (2005, MS.NET 2.0), creo que no había guayaba. Y ... quién sabe de dónde tomó C # esto.
peter.petrov

3
@fge "Ripoff of Guava's Opcional" es una forma divertida de decirlo, ya que los chicos de Guava participaron en discusiones, brindaron sus experiencias y, en general, estuvieron a favor java.util.Optional.
Stuart Marks,

66
@fge Sin duda, las API de Java fueron fuertemente influenciadas por las de Guava. Estoy en desacuerdo con el "timo" que suena como robar. Los chicos de Guava contribuyeron con muchas ideas valiosas a Java 8.
Stuart Marks

66
Te estás perdiendo el punto. Opcional se debe utilizar en lugar de devolver nulo. Es más seguro que potencialmente lanzar una NullPointerException. Ver: oracle.com/technetwork/articles/java/…
Kilizo


-1

Java SE 8 presenta una nueva clase llamada java.util.Optional,

Puede crear un Opcional u Opcional vacío con valor nulo.

Optional<String> emptyOptional = Optional.empty(); 

Y aquí hay un Opcional con un valor no nulo:

String valueString = new String("TEST");
Optional<String> optinalValueString = Optional.of(valueString );

Hacer algo si hay un valor presente

Ahora que tiene un objeto Opcional, puede acceder a los métodos disponibles para tratar explícitamente la presencia o ausencia de valores. En lugar de tener que recordar hacer una verificación nula, de la siguiente manera:

String nullString = null;
if (nullString != null) {
    System.out.println(nullString);
}

Puede usar el método ifPresent () de la siguiente manera:

Optional<String> optinalString= null;
optinalString.ifPresent(System.out::println);

package optinalTest;

import java.util.Optional;

public class OptionalTest {
    public Optional<String> getOptionalNullString() {
        return null;
//      return Optional.of("TESt");
    }

    public static void main(String[] args) {

        OptionalTest optionalTest = new OptionalTest();

        Optional<Optional<String>> optionalNullString = Optional.ofNullable(optionalTest.getOptionalNullString());

        if (optionalNullString.isPresent()) {
            System.out.println(optionalNullString.get());
        }
    }
}

Esta copia tiene la intención de ganar reputación barata, pero no está relacionada con las preguntas de OP
stinger
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.