JavaRush /Blog Java /Random-ES /Las 50 preguntas y respuestas principales de las entrevis...

Las 50 preguntas y respuestas principales de las entrevistas sobre Java Core. Parte 1

Publicado en el grupo Random-ES
¡Hola a todos, damas y caballeros ingenieros de software! Hablemos de las preguntas de la entrevista. Sobre para qué necesita prepararse y qué necesita saber. Esta es una excelente razón para repetir o estudiar estos puntos desde cero. Las 50 preguntas y respuestas principales de las entrevistas sobre Java Core.  Parte 1 - 1Tengo una colección bastante extensa de preguntas frecuentes sobre programación orientada a objetos, sintaxis de Java, excepciones en Java, colecciones y subprocesos múltiples, que por conveniencia dividiré en varias partes. Importante:Solo hablaremos de las versiones de Java hasta la 8. Todas las innovaciones de 9, 10, 11, 12, 13 no se tendrán en cuenta aquí. Cualquier idea/comentario sobre cómo mejorar las respuestas es bienvenido . Feliz lectura, ¡vamos!

Entrevista de Java: preguntas de programación orientada a objetos

1. ¿Qué características tiene Java?

Respuesta:
  1. Conceptos de programación orientada a objetos:

    1. orientación de objetos;
    2. herencia;
    3. encapsulación;
    4. polimorfismo;
    5. abstracción.
  2. Multiplataforma: un programa Java se puede ejecutar en cualquier plataforma sin modificaciones. Lo único que necesita es una JVM (máquina virtual Java) instalada.

  3. Alto rendimiento: JIT (compilador Just In Time) permite un alto rendimiento. JIT convierte el código de bytes en código de máquina y luego la JVM comienza la ejecución.

  4. Multihilo: un hilo de ejecución conocido como Thread. La JVM crea un hilo llamado main thread. Un programador puede crear múltiples subprocesos heredando de la clase Thread o implementando una interfaz Runnable.

2. ¿Qué es la herencia?

Herencia significa que una clase puede heredar (“ extender ”) otra clase. De esta manera puedes reutilizar el código de la clase de la que heredas. La clase existente se conoce como superclass, y la que se está creando se conoce como subclass. También dicen parenty child.
public class Animal {
   private int age;
}

public class Dog extends Animal {

}
donde Animalesta parent, y Dog- child.

3. ¿Qué es la encapsulación?

Esta pregunta surge a menudo durante las entrevistas con desarrolladores de Java. La encapsulación oculta la implementación mediante modificadores de acceso, captadores y definidores. Esto se hace con el fin de cerrar el acceso para uso externo en aquellos lugares donde los desarrolladores lo consideren necesario. Un ejemplo accesible de la vida es un automóvil. No tenemos acceso directo al funcionamiento del motor. Para nosotros, el trabajo es poner la llave en el contacto y encender el motor. Y los procesos que se llevarán a cabo bajo el capó no son asunto nuestro. Además, nuestra intromisión en esta actividad puede dar lugar a una situación impredecible, por la que podemos romper el coche y hacernos daño. En programación pasa exactamente lo mismo. Bien descrito en Wikipedia . También hay un artículo sobre encapsulación en JavaRush .

4. ¿Qué es el polimorfismo?

El polimorfismo es la capacidad de un programa de utilizar de forma idéntica objetos con la misma interfaz sin información sobre el tipo específico de ese objeto. Como dicen, una interfaz, muchas implementaciones. Con el polimorfismo, puedes combinar y utilizar diferentes tipos de objetos según su comportamiento común. Por ejemplo, tenemos una clase Animal que tiene dos descendientes: Perro y Gato. La clase Animal genérica tiene un comportamiento común para todos: emitir un sonido. En el caso de que necesitemos reunir a todos los descendientes de la clase Animal y ejecutar el método "hacer un sonido", utilizamos las posibilidades del polimorfismo. Así es como se verá:
List<Animal> animals = Arrays.asList(new Cat(), new Dog(), new Cat());
animals.forEach(animal -> animal.makeSound());
Entonces el polimorfismo nos ayuda. Además, esto también se aplica a los métodos polimórficos (sobrecargados). Práctica de uso del polimorfismo.

Preguntas de la entrevista: sintaxis de Java

5. ¿Qué es un constructor en Java?

Son válidas las siguientes características:
  1. Cuando se crea un nuevo objeto, el programa utiliza el constructor apropiado para hacerlo.
  2. Un constructor es como un método. Su peculiaridad es que no hay ningún elemento que regrese (incluido el vacío) y su nombre es el mismo que el nombre de la clase.
  3. Si no se escribe ningún constructor explícitamente, se creará automáticamente un constructor vacío.
  4. El constructor se puede anular.
  5. Si se creó un constructor con parámetros, pero también se necesita sin parámetros, se debe escribir por separado, ya que no se crea automáticamente.

6. ¿Qué dos clases no heredan de Object?

No se deje engañar por las provocaciones, no existen tales clases: ¡todas las clases directamente o a través de ancestros se heredan de la clase Objeto!

7. ¿Qué es la variable local?

Otra pregunta popular durante una entrevista para un desarrollador de Java. Una variable local es una variable que se define dentro de un método y existe hasta el momento en que se ejecuta el método. Una vez finalizada la ejecución, la variable local dejará de existir. Aquí hay un programa que usa la variable local helloMessage en el método main():
public static void main(String[] args) {
   String helloMessage;
   helloMessage = "Hello, World!";
   System.out.println(helloMessage);
}

8. ¿Qué es la variable de instancia?

La variable de instancia es una variable que se define dentro de una clase y existe hasta el momento en que existe el objeto. Un ejemplo es la clase Bee, que tiene dos variables nectarCapacity y maxNectarCapacity:
public class Bee {

   /**
    * Current nectar capacity
    */
   private double nectarCapacity;

   /**
    * Maximal nectar that can take bee.
    */
   private double maxNectarCapacity = 20.0;

  ...
}

9. ¿Qué son los modificadores de acceso?

Los modificadores de acceso son una herramienta que le permite personalizar el acceso a clases, métodos y variables. Existen los siguientes modificadores, ordenados en orden creciente de acceso:
  1. private- utilizado para métodos, campos y constructores. El nivel de acceso es sólo la clase dentro de la cual se declara.
  2. package-private(default)- se puede utilizar para clases. Acceso solo en un paquete específico en el que se declara una clase, método, variable, constructor.
  3. protected— el mismo acceso que package-private+ para aquellas clases que heredan de una clase con el modificador protected.
  4. public- También se utiliza para clases. Acceso completo a toda la aplicación.
  5. Las 50 preguntas y respuestas principales de las entrevistas sobre Java Core.  Parte 1 - 2

10. ¿Qué son los métodos primordiales?

La anulación de métodos ocurre cuando el niño quiere cambiar el comportamiento de la clase principal. Si desea que se ejecute lo que está en el método principal, puede usar una construcción como super.methodName() en el método secundario, que hará el trabajo del método principal y solo entonces agregará lógica. Requisitos a cumplir:
  • la firma del método debe ser la misma;
  • el valor de retorno debe ser el mismo.

11. ¿Qué es la firma de un método?

Las 50 preguntas y respuestas principales de las entrevistas sobre Java Core.  Parte 1 - 3La firma de un método es un conjunto del nombre del método y los argumentos que acepta el método. La firma de un método es un identificador único de un método cuando se sobrecargan métodos.

12. ¿Qué es la sobrecarga de métodos?

La sobrecarga de métodos es una propiedad del polimorfismo en la que al cambiar la firma del método, puedes crear diferentes métodos para las mismas acciones:
  • mismo nombre de método;
  • diferentes argumentos;
  • puede haber un tipo de devolución diferente.
Por ejemplo, lo mismo add()se ArrayListpuede sobrecargar de la siguiente manera y realizará la suma de una manera diferente, dependiendo de los argumentos entrantes:
  • add(Object o)- simplemente añade un objeto;
  • add(int index, Object o)— agrega un objeto a un índice específico;
  • add(Collection<Object> c)— agrega una lista de objetos;
  • add(int index, Collection<Object> c)— agrega una lista de objetos, a partir de un índice determinado.

13. ¿Qué es la interfaz?

La herencia múltiple no está implementada en Java, por lo que para superar este problema, se agregaron interfaces tal como las conocemos;) Durante mucho tiempo, las interfaces solo tenían métodos sin implementarlos. Como parte de esta respuesta, hablaremos de ellos. Por ejemplo:

public interface Animal {
   void makeSound();
   void eat();
   void sleep();
}
De esto se desprenden algunos matices:
  • todos los métodos de la interfaz son públicos y abstractos;
  • todas las variables son públicas estáticas finales;
  • las clases no los heredan (extienden), sino que los implementan (implementan). Además, puedes implementar tantas interfaces como quieras.
  • Las clases que implementan una interfaz deben proporcionar implementaciones de todos los métodos que tiene la interfaz.
Como esto:
public class Cat implements Animal {
   public void makeSound() {
       // implementación del método
   }

   public void eat() {
       // implementación
   }

   public void sleep() {
       // implementación
   }
}

14. ¿Cuál es el método predeterminado en la interfaz?

Ahora hablemos de los métodos predeterminados. ¿Para qué, para quién? Estos métodos se agregaron para que todo fuera “tanto tuyo como nuestro”. ¿De qué estoy hablando? Sí, por un lado, era necesario agregar una nueva funcionalidad: lambdas, Stream API, por otro lado, era necesario dejar aquello por lo que Java es famoso: la compatibilidad con versiones anteriores. Para hacer esto, fue necesario introducir soluciones listas para usar en las interfaces. Así es como nos llegaron los métodos predeterminados. Es decir, el método predeterminado es un método implementado en la interfaz que tiene la palabra clave default. Por ejemplo, el conocido método stream()en Collection. Compruébalo, esta interfaz no es tan sencilla como parece ;). O también un método igualmente conocido forEach()de Iterable. Tampoco existía hasta que se agregaron los métodos predeterminados. Por cierto, también puedes leer sobre esto en JavaRush .

15. ¿Cómo entonces heredar dos métodos predeterminados idénticos?

Según la respuesta anterior sobre cuál es el método predeterminado, puedes hacer otra pregunta. Si puede implementar métodos en interfaces, entonces, en teoría, puede implementar dos interfaces con el mismo método, y ¿cómo hacerlo? Hay dos interfaces diferentes con el mismo método:
interface A {
   default void foo() {
       System.out.println("Foo A");
   }
}

interface B {
   default void foo() {
       System.out.println("Foo B");
   }
}
Y hay una clase que implementa estas dos interfaces. Para evitar incertidumbre y compilar el código, necesitamos anular el método foo()en la clase Cy podemos simplemente llamar a un método foo()de cualquiera de las interfaces que contiene, Ao B. Pero, ¿ cómo elegir un método de interfaz específico А? ВHay una estructura como esta para esto A.super.foo():
public class C implements A, B {
   @Override
   public void foo() {
       A.super.foo();
   }
}
o:
public class C implements A, B {
   @Override
   public void foo() {
       B.super.foo();
   }
}
Por lo tanto, un método foo()de clase Cutilizará el método predeterminado foo()de la interfaz Ao un método foo()de la interfaz B.

16. ¿Qué son los métodos y clases abstractos?

Java tiene una palabra reservada abstractque se utiliza para indicar clases y métodos abstractos. Primero, algunas definiciones. Un método abstracto es un método que se crea sin una implementación con una palabra clave abstracten una clase abstracta. Es decir, este es un método como en la interfaz, solo que con la adición de una palabra clave, por ejemplo:
public abstract void foo();
Una clase abstracta es una clase que también tiene abstractla palabra:
public abstract class A {

}
Una clase abstracta tiene varias características:
  • no se puede crear un objeto sobre su base;
  • puede tener métodos abstractos;
  • puede que no tenga métodos abstractos.
Se necesitan clases abstractas para generalizar algún tipo de abstracción (perdón por la tautología), que no existe en la vida real, pero que contiene muchos comportamientos y estados comunes (es decir, métodos y variables). Hay ejemplos más que suficientes de la vida. Todo está a nuestro alrededor. Podría ser "animal", "coche", "figura geométrica", etc.

17. ¿Cuál es la diferencia entre String, String Builder y String Buffer?

Los valores Stringse almacenan en un grupo de cadenas constante. Una vez que se crea una fila, aparecerá en este grupo. Y no será posible eliminarlo. Por ejemplo:
String name = "book";
...la variable se referirá al grupo de cadenas Grupo de cadenas constantes Las 50 preguntas y respuestas principales de las entrevistas sobre Java Core.  Parte 1 - 4 Si establece el nombre de la variable en un valor diferente, obtendrá lo siguiente:
name = "pen";
Grupo de cadenas constantes Las 50 preguntas y respuestas principales de las entrevistas sobre Java Core.  Parte 1 - 5Entonces estos dos valores permanecerán allí. Búfer de cadena:
  • Los valores Stringse almacenan en la pila. Si se cambia el valor, el nuevo valor será reemplazado por el anterior;
  • String Buffersincronizado y por lo tanto seguro para subprocesos;
  • Debido a la seguridad del hilo, la velocidad de funcionamiento deja mucho que desear.
Ejemplo:
StringBuffer name = "book";
Las 50 preguntas y respuestas principales de las entrevistas sobre Java Core.  Parte 1 - 6Tan pronto como el valor del nombre cambia, el valor en la pila cambia: Las 50 preguntas y respuestas principales de las entrevistas sobre Java Core.  Parte 1 - 7StringBuilder Exactamente igual que StringBuffer, solo que no es seguro para subprocesos. Por tanto, su velocidad es claramente mayor que en StringBuffer.

18. ¿Cuál es la diferencia entre una clase abstracta y una interfaz?

Clase abstracta:
  • las clases abstractas tienen un constructor predeterminado; se llama cada vez que se crea un hijo de esta clase abstracta;
  • Contiene métodos tanto abstractos como no abstractos. En general, puede que no contenga métodos abstractos, pero aún así sea una clase abstracta;
  • una clase que hereda de una abstracta debe implementar sólo métodos abstractos;
  • una clase abstracta puede contener una variable de instancia (consulte la pregunta n.° 5).
Interfaz:
  • no tiene constructor y no se puede inicializar;
  • sólo se deben agregar métodos abstractos (sin contar los métodos predeterminados);
  • las clases que implementan una interfaz deben implementar todos los métodos (sin contar los métodos predeterminados);
  • Las interfaces solo pueden contener constantes.

19. ¿Por qué acceder a un elemento de una matriz requiere O(1)?

Esta pregunta es literalmente de la última entrevista. Como supe más tarde, esta pregunta se hace para ver cómo piensa una persona. Está claro que este conocimiento tiene poco significado práctico: basta con conocer este hecho. Primero, debemos aclarar que O(1) es una designación para la complejidad temporal de un algoritmo cuando la operación se realiza en tiempo constante. Es decir, esta designación es la ejecución más rápida. Para responder a esta pregunta, ¿necesitamos comprender lo que sabemos sobre las matrices? Para crear un array int, debemos escribir lo siguiente:
int[] intArray = new int[100];
De esta grabación se pueden sacar varias conclusiones:
  1. Al crear una matriz, se conoce su tipo, si se conoce el tipo, entonces está claro qué tamaño tendrá cada celda de la matriz.
  2. Se sabe qué tamaño tendrá la matriz.
De esto se deduce: para saber en qué celda escribir, solo necesita calcular en qué área de memoria escribir. Para un coche no podría ser más sencillo. La máquina tiene un inicio de memoria asignada, una cantidad de elementos y un tamaño de celda único. De esto queda claro que el espacio de grabación será igual a la ubicación inicial de la matriz + el tamaño de la celda multiplicado por su tamaño.

¿Cómo se obtiene O(1) al acceder a objetos en una ArrayList?

Esta pregunta sigue inmediatamente a la anterior. Es cierto que cuando trabajamos con un array y hay primitivas ahí, sabemos de antemano cuál es el tamaño de ese tipo cuando se crea. Pero, ¿qué pasa si hay un esquema como el de la imagen: Las 50 preguntas y respuestas principales de las entrevistas sobre Java Core.  Parte 1 - 8y queremos crear una colección con elementos de tipo A y agregar diferentes implementaciones: B, C, D:
List<A> list = new ArrayList();
list.add(new B());
list.add(new C());
list.add(new D());
list.add(new B());
En esta situación, ¿cómo se puede saber qué tamaño tendrá cada celda, porque cada objeto será diferente y puede tener diferentes campos adicionales (o ser completamente diferentes)? ¿Qué hacer? Aquí la pregunta se plantea de tal manera que confunde y confunde. Sabemos que, de hecho, la colección no almacena objetos, solo enlaces a estos objetos. Y todos los enlaces tienen el mismo tamaño, y se sabe. Entonces contar el espacio aquí funciona de la misma manera que en la pregunta anterior.

21. Autoboxing y unboxing

Antecedentes históricos: el autoboxing y el autounboxing son una de las principales innovaciones de JDK 5. El autoboxing es el proceso de conversión automática de un tipo primitivo a la clase contenedora apropiada. Auto-unboxing : hace exactamente lo opuesto al auto-boxing: convierte una clase contenedora en una primitiva. Pero si hay un valor contenedor null, se generará una excepción durante el descomprimido NullPointerException.

Coincidencia primitiva - contenedor

Primitivo clase contenedora
booleano Booleano
En t Entero
byte Byte
carbonizarse Personaje
flotar Flotar
largo Largo
corto Corto
doble Doble

Se produce el embalaje automático:

  • al asignar una primitiva una referencia a la clase contenedora:

    ANTES de Java 5:

    //empaquetado manual o como era ANTES de Java 5.
    public void boxingBeforeJava5() {
       Boolean booleanBox = new Boolean(true);
       Integer intBox = new Integer(3);
       // y así sucesivamente con otros tipos
    }
    
    после Java 5:
    //empaquetado automático o cómo se convirtió en Java 5.
    public void boxingJava5() {
       Boolean booleanBox = true;
       Integer intBox = 3;
       // y así sucesivamente con otros tipos
    }
  • al pasar una primitiva como argumento a un método que espera un contenedor:

    public void exampleOfAutoboxing() {
       long age = 3;
       setAge(age);
    }
    
    public void setAge(Long age) {
       this.age = age;
    }

Se produce el desembalaje automático:

  • cuando asignamos una variable primitiva a la clase contenedora:

    //antes de Java 5:
    int intValue = new Integer(4).intValue();
    double doubleValue = new Double(2.3).doubleValue();
    char c = new Character((char) 3).charValue();
    boolean b = Boolean.TRUE.booleanValue();
    
    //y después de JDK 5:
    int intValue = new Integer(4);
    double doubleValue = new Double(2.3);
    char c = new Character((char) 3);
    boolean b = Boolean.TRUE;
  • En los casos con operaciones aritméticas. Se aplican solo a tipos primitivos; para esto es necesario realizar unboxing al primitivo.

    // Antes de Java 5
    Integer integerBox1 = new Integer(1);
    Integer integerBox2 = new Integer(2);
    
    // para comparar era necesario hacer esto:
    integerBox1.intValue() > integerBox2.intValue()
    
    //в Java 5
    integerBox1 > integerBox2
  • cuando se pasa a un contenedor en un método que acepta la primitiva correspondiente:

    public void exampleOfAutoboxing() {
       Long age = new Long(3);
       setAge(age);
    }
    
    public void setAge(long age) {
       this.age = age;
    }

22. ¿Cuál es la palabra clave final y dónde utilizarla?

La palabra clave finalse puede utilizar para variables, métodos y clases.
  1. Una variable final no se puede reasignar a otro objeto.
  2. la clase final es estéril)) no puede tener herederos.
  3. El método final no se puede anular en un antepasado.
Hemos cubierto la parte superior, ahora analicémoslo con más detalle.

variables finales

;Java nos ofrece dos formas de crear una variable y asignarle algún valor:
  1. Puede declarar una variable e inicializarla más tarde.
  2. Puede declarar una variable y asignarla inmediatamente.
Ejemplo de uso de la variable final para estos casos:
public class FinalExample {

   //variable estática final, que se inicializa inmediatamente:
   final static String FINAL_EXAMPLE_NAME = "I'm likely final one";

   //final es una variable que no se inicializa, pero solo funcionará si
   //inicializar esto en el constructor:
   final long creationTime;

   public FinalExample() {
       this.creationTime = System.currentTimeMillis();
   }

   public static void main(String[] args) {
       FinalExample finalExample = new FinalExample();
       System.out.println(finalExample.creationTime);

       // campo final FinalExample.FINAL_EXAMPLE_NAME no se puede asignar
//    FinalExample.FINAL_EXAMPLE_NAME = "Not you're not!";

       // no se puede asignar el campo final Config.creationTime
//    finalExample.creationTime = 1L;
   }
}

¿Se puede considerar la variable final como una constante?

Como no podremos asignar un nuevo valor a una variable final, parece que se trata de variables constantes. Pero esto es sólo a primera vista. Si el tipo de datos al que se refiere la variable es immutable, entonces sí, es una constante. Pero si el tipo de datos mutablees mutable, utilizando métodos y variables será posible cambiar el valor del objeto al que finalse refiere la variable, y en este caso no se le puede llamar constante. Entonces, el ejemplo muestra que algunas de las variables finales son realmente constantes, pero otras no lo son y se pueden cambiar.
public class FinalExample {

   //variables finales inmutables:
   final static String FINAL_EXAMPLE_NAME = "I'm likely final one";
   final static Integer FINAL_EXAMPLE_COUNT  = 10;

   // variables de filtro mutables
   final List<String> addresses = new ArrayList();
   final StringBuilder finalStringBuilder = new StringBuilder("constant?");
}

Variables finales locales

Cuando finalse crea una variable dentro de un método, se llama local finalvariable:
public class FinalExample {

   public static void main(String[] args) {
       // Así es como puedes
       final int minAgeForDriveCar = 18;

       // o puedes hacerlo de esta manera, en el bucle foreach:
       for (final String arg : args) {
           System.out.println(arg);
       }
   }

}
Podemos usar la palabra clave finalen bucle extendido forporque después de completar una iteración del bucle, forse crea una nueva variable cada vez. Pero esto no se aplica a un bucle for normal, por lo que el siguiente código generará un error en tiempo de compilación.
// final local modificado j no se puede asignar
for (final int i = 0; i < args.length; i ++) {
   System.out.println(args[i]);
}

clase final

No se puede extender una clase declarada como final. En pocas palabras, ninguna clase puede heredar de ésta. Un gran ejemplo finalde una clase en el JDK es String. El primer paso para crear una clase inmutable es marcarla como finalpara que no pueda ampliarse:
public final class FinalExample {
}

// Error de compilación aquí
class WantsToInheritFinalClass extends FinalExample {
}

Métodos finales

Cuando un método está marcado como final, se llama método final (lógico, ¿verdad?). El método Final no se puede anular en una clase descendiente. Por cierto, los métodos de la clase Object (esperar() y notificar()) son finales, por lo que no tenemos la posibilidad de anularlos.
public class FinalExample {
   public final String generateAddress() {
       return "Some address";
   }
}

class ChildOfFinalExample extends FinalExample {

   // error de compilación aquí
   @Override
   public String generateAddress() {
       return "My OWN Address";
   }
}

Cómo y dónde usar final en Java

  • use la palabra clave final para definir algunas constantes a nivel de clase;
  • cree variables finales para objetos cuando no desee que se modifiquen. Por ejemplo, propiedades específicas de objetos que podemos usar con fines de registro;
  • si no desea que la clase se extienda, márquela como final;
  • si necesita crear una clase inmutable <, debe hacerla definitiva;
  • Si desea que la implementación de un método no cambie en sus descendientes, designe el método como final. Esto es muy importante para garantizar que la implementación no cambie.

23. ¿Qué es mutable e inmutable?

Mudable

Mutables son objetos cuyos estados y variables se pueden cambiar después de la creación. Por ejemplo, clases como StringBuilder, StringBuffer. Ejemplo:
public class MutableExample {

   private String address;

   public MutableExample(String address) {
       this.address = address;
   }

   public String getAddress() {
       return address;
   }

   // este setter puede cambiar el campo de nombre
   public void setAddress(String address) {
       this.address = address;
   }

   public static void main(String[] args) {

       MutableExample obj = new MutableExample("first address");
       System.out.println(obj.getAddress());

       // actualice el campo de nombre, por lo que este es un objeto mutable
       obj.setAddress("Updated address");
       System.out.println(obj.getAddress());
   }
}

Inmutable

Inmutables son objetos cuyos estados y variables no se pueden cambiar después de crear el objeto. ¿Por qué no una gran clave para un HashMap, verdad?) Por ejemplo, String, Integer, Double, etc. Ejemplo:
// hacer que esta clase sea final para que nadie pueda cambiarla
public final class ImmutableExample {

   private String address;

   ImmutableExample (String address) {
       this.address = address;
   }

   public String getAddress() {
       return address;
   }

   //quitar el setter

   public static void main(String[] args) {

       ImmutableExample obj = new ImmutableExample("old address");
       System.out.println(obj.getAddress());

       // Por lo tanto, no cambie este campo de ninguna manera, por lo que este es un objeto inmutable
       // obj.setName("new address");
       // System.out.println(obj.getName());

   }
}

24. ¿Cómo escribir una clase inmutable?

Después de descubrir qué son los objetos mutables e inmutables, la siguiente pregunta es natural: ¿cómo escribirlos? Para escribir una clase inmutable inmutable, debe seguir unos sencillos pasos:
  • hacer la clase definitiva.
  • haga que todos los campos sean privados y cree solo captadores para ellos. Los configuradores, por supuesto, no son necesarios.
  • Haga que todos los campos mutables sean finales para que el valor solo se pueda establecer una vez.
  • inicializar todos los campos a través del constructor, realizando una copia profunda (es decir, copiando el objeto en sí, sus variables, variables de variables, etc.)
  • clonar objetos variables mutables en captadores para devolver solo copias de valores y no referencias a objetos reales.
Ejemplo:
/**
* Un ejemplo de creación de un objeto inmutable.
*/
public final class FinalClassExample {

   private final int age;

   private final String name;

   private final HashMap<String, String> addresses;

   public int getAge() {
       return age;
   }


   public String getName() {
       return name;
   }

   /**
    * Clona el objeto antes de devolverlo.
    */
   public HashMap<String, String> getAddresses() {
       return (HashMap<String, String>) addresses.clone();
   }

   /**
    * En el constructor, copia en profundidad los objetos mutables.
    */
   public FinalClassExample(int age, String name, HashMap<String, String> addresses) {
       System.out.println("Realizando una copia profunda en el constructor");
       this.age = age;
       this.name = name;
       HashMap<String, String> temporaryMap = new HashMap<>();
       String key;
       Iterator<String> iterator = addresses.keySet().iterator();
       while (iterator.hasNext()) {
           key = iterator.next();
           temporaryMap.put(key, addresses.get(key));
       }
       this.addresses = temporaryMap;
   }
}
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION