JavaRush /Blog Java /Random-ES /Análisis de preguntas y respuestas de entrevistas para de...

Análisis de preguntas y respuestas de entrevistas para desarrollador Java. parte 7

Publicado en el grupo Random-ES
¡Hola a todos! La programación está llena de trampas. Y prácticamente no hay tema en el que no tropezarás y no te toparás con baches. Esto es especialmente cierto para los principiantes. La única manera de reducirlo es estudiando. En particular, esto se aplica a análisis detallados de los temas más básicos. Hoy continúo analizando más de 250 preguntas de entrevistas con desarrolladores de Java, que cubren bien temas básicos. Me gustaría señalar que la lista también contiene algunas preguntas no estándar que le permiten ver temas comunes desde un ángulo diferente.Análisis de preguntas y respuestas de entrevistas para desarrollador Java.  Parte 7 - 1

62. ¿Qué es un grupo de cadenas y por qué es necesario?

En la memoria de Java (montón, del que hablaremos más adelante) hay un área: grupo de cadenas o grupo de cadenas. Está diseñado para almacenar valores de cadena. En otras palabras, cuando creas una determinada cadena, por ejemplo mediante comillas dobles:
String str = "Hello world";
Se realiza una verificación para ver si el grupo de cadenas tiene el valor dado. Si es así, a la variable str se le asigna una referencia a ese valor en el grupo. Si no es así, se creará un nuevo valor en el grupo y se asignará una referencia a él a la variable str . Veamos un ejemplo:
String firstStr = "Hello world";
String secondStr = "Hello world";
System.out.println(firstStr == secondStr);
verdadero se mostrará en la pantalla . Recordamos que == compara referencias, lo que significa que estas dos referencias se refieren al mismo valor del grupo de cadenas. Esto se hace para no producir muchos objetos idénticos del tipo String en la memoria, porque como recordamos, String es una clase inmutable, y si tenemos muchas referencias al mismo valor, no hay nada de malo en ello. Ahora es imposible que una situación en la que se cambia un valor en un lugar cambie varios otros enlaces a la vez. Sin embargo, si creamos una cadena usando new :
String str = new String("Hello world");
Se creará un objeto separado en la memoria que almacenará este valor de cadena (y no importa si ya tenemos ese valor en el grupo de cadenas). Como confirmación:
String firstStr = new String("Hello world");
String secondStr = "Hello world";
String thirdStr = new String("Hello world");
System.out.println(firstStr == secondStr);
System.out.println(firstStr == thirdStr);
Obtendremos dos valores falsos , lo que significa que aquí tenemos tres valores diferentes a los que se hace referencia. En realidad, es por eso que se recomienda crear cadenas simplemente usando comillas dobles. Sin embargo, puede agregar (u obtener una referencia a) valores en un grupo de cadenas al crear un objeto usando new . Para hacer esto, usamos el método de clase de cadena: intern() . Este método crea a la fuerza un valor en el grupo de cadenas u obtiene un enlace si ya está almacenado allí. He aquí un ejemplo:
String firstStr = new String("Hello world").intern();
String secondStr = "Hello world";
String thirdStr = new String("Hello world").intern();
System.out.println(firstStr == secondStr);
System.out.println(firstStr == thirdStr);
System.out.println(secondStr == thirdStr);
Como resultado, recibiremos tres valores verdaderos en la consola , lo que significa que las tres variables se refieren a la misma cadena.Análisis de preguntas y respuestas de entrevistas para desarrollador Java.  Parte 7 - 2

63. ¿Qué patrones GOF se utilizan en el grupo de cadenas?

El patrón GOF es claramente visible en el grupo de cuerdas : peso mosca , también llamado colono. Si ves otra plantilla aquí, compártela en los comentarios. Bueno, hablemos de la plantilla ligera. El peso ligero es un patrón de diseño estructural en el que un objeto que se presenta como una instancia única en diferentes lugares del programa, en realidad no lo es. Lightweight ahorra memoria al compartir el estado compartido de los objetos entre ellos, en lugar de almacenar los mismos datos en cada objeto. Para entender la esencia, veamos el ejemplo más simple. Digamos que tenemos una interfaz de empleado:
public interface Employee {
   void work();
}
Y hay algunas implementaciones, por ejemplo, abogado:
public class Lawyer implements Employee {

   public Lawyer() {
       System.out.println("Юрист взят в штат.");
   }

   @Override
   public void work() {
       System.out.println("Решение юридических вопросов...");
   }
}
Y el contador:
public class Accountant implements Employee{

   public Accountant() {
       System.out.println("Бухгалтер взят в штат.");
   }

   @Override
   public void work() {
       System.out.println("Ведение бухгалтерского отчёта....");
   }
}
Los métodos son muy condicionales: sólo hay que velar por que se lleven a cabo. La misma situación se aplica al constructor. Gracias a la salida de la consola, veremos cuando se crean nuevos objetos. También contamos con un departamento de empleados, cuya tarea es emitir al empleado solicitado, y si no está, contratarlo y emitir en respuesta a la solicitud:
public class StaffDepartment {
   private Map<String, Employee> currentEmployees = new HashMap<>();

   public Employee receiveEmployee(String type) throws Exception {
       Employee result;
       if (currentEmployees.containsKey(type)) {
           result = currentEmployees.get(type);
       } else {
           switch (type) {
               case "Бухгалтер":
                   result = new Accountant();
                   currentEmployees.put(type, result);
                   break;
               case "Юрист":
                   result = new Lawyer();
                   currentEmployees.put(type, result);
                   break;
               default:
                   throw new Exception("Данный сотрудник в штате не предусмотрен!");
           }
       }
       return result;
   }
}
Es decir, la lógica es simple: si hay una unidad determinada, devuélvela; si no, créala, colócala en un almacenamiento (algo así como un caché) y devuélvela. Ahora veamos cómo funciona todo:
public static void main(String[] args) throws Exception {
   StaffDepartment staffDepartment = new StaffDepartment();
   Employee empl1  = staffDepartment.receiveEmployee("Юрист");
   empl1.work();
   Employee empl2  = staffDepartment.receiveEmployee("Бухгалтер");
   empl2.work();
   Employee empl3  = staffDepartment.receiveEmployee("Юрист");
   empl1.work();
   Employee empl4  = staffDepartment.receiveEmployee("Бухгалтер");
   empl2.work();
   Employee empl5  = staffDepartment.receiveEmployee("Юрист");
   empl1.work();
   Employee empl6  = staffDepartment.receiveEmployee("Бухгалтер");
   empl2.work();
   Employee empl7  = staffDepartment.receiveEmployee("Юрист");
   empl1.work();
   Employee empl8  = staffDepartment.receiveEmployee("Бухгалтер");
   empl2.work();
   Employee empl9  = staffDepartment.receiveEmployee("Юрист");
   empl1.work();
   Employee empl10  = staffDepartment.receiveEmployee("Бухгалтер");
   empl2.work();
}
Y en la consola, en consecuencia, habrá una salida:
El abogado ha sido contratado. Resolviendo problemas legales... Se ha contratado a un contador. Mantener un informe contable.... Resolver cuestiones legales... Mantener un informe contable.... Resolver cuestiones legales... Mantener un informe contable.... Resolver cuestiones legales... Mantener un informe contable... Resolver cuestiones legales... Mantener informes contables...
Como puedes ver, sólo se crearon dos objetos, que fueron reutilizados muchas veces. El ejemplo es muy simple, pero demuestra claramente cómo el uso de esta plantilla puede ahorrar nuestros recursos. Bueno, como habrás notado, la lógica de este patrón es muy similar a la lógica del grupo de seguros. Puedes leer más sobre los tipos de patrones GOF en este artículo .Análisis de preguntas y respuestas de entrevistas para desarrollador Java.  Parte 7 - 3

64. ¿Cómo dividir una cuerda en partes? Proporcione un ejemplo del código correspondiente.

Obviamente, esta pregunta es sobre el método de división . La clase String tiene dos variaciones de este método:
String split(String regex);
Y
String split(String regex);
regex es un separador de líneas, alguna expresión regular que divide una cadena en una matriz de cadenas, por ejemplo:
String str = "Hello, world it's Amigo!";
String[] arr = str.split("\\s");
for (String s : arr) {
  System.out.println(s);
}
Lo siguiente se enviará a la consola:
¡Hola mundo, soy Amigo!
Es decir, nuestro valor de cadena se dividió en una matriz de cadenas y el separador era un espacio (para la separación, podríamos usar una expresión regular sin espacio "\\s" y solo una expresión de cadena " " ). El segundo método sobrecargado tiene un argumento adicional: límite . límite : el valor máximo permitido de la matriz resultante. Es decir, cuando la cadena ya se ha dividido en el número máximo permitido de subcadenas, no habrá más divisiones y el último elemento tendrá un "resto" de la cadena posiblemente subdividida. Ejemplo:
String str = "Hello, world it's Amigo!";
String[] arr = str.split(" ", 2);
for (String s : arr) {
  System.out.println(s);
}
Salida de consola:
¡Hola mundo, soy Amigo!
Como podemos ver, si no fuera por la restricción límite = 2 , el último elemento de la matriz podría dividirse en tres subcadenas.Análisis de preguntas y respuestas de entrevistas para desarrollador Java.  Parte 7 - 4

65. ¿Por qué una matriz de caracteres es mejor que una cadena para almacenar una contraseña?

Hay varias razones para preferir una matriz a una cadena al almacenar una contraseña: 1. Grupo de cadenas e inmutabilidad de cadenas. Cuando usamos una matriz ( char[] ), podemos borrar explícitamente los datos una vez que hayamos terminado con ella. Además, podemos reescribir la matriz tanto como queramos, y la contraseña válida no estará en ninguna parte del sistema, incluso antes de la recolección de basura (basta con cambiar un par de celdas a valores no válidos). Al mismo tiempo, String es una clase inmutable. Es decir, si queremos cambiar su valor, obtendremos uno nuevo, mientras que el anterior permanecerá en el grupo de cadenas. Si queremos eliminar el valor String de una contraseña, esto puede ser una tarea muy difícil, ya que necesitamos que el recolector de basura elimine el valor del grupo String , y existe una alta probabilidad de que este valor String permanezca allí por un tiempo. largo tiempo. Es decir, en esta situación, String es inferior a la matriz char en términos de seguridad del almacenamiento de datos. 2. Si el valor de la cadena se envía accidentalmente a la consola (o a los registros), se mostrará el valor en sí:
String password = "password";
System.out.println("Пароль - " + password);
Salida de consola:
Contraseña
Al mismo tiempo, si accidentalmente envía una matriz a la consola:
char[] arr = new char[]{'p','a','s','s','w','o','r','d'};
System.out.println("Пароль - " + arr);
Habrá palabrería incomprensible en la consola:
Contraseña: [C@7f31245a
En realidad, no es una tontería, pero: [C es el nombre de la clase, es una matriz de caracteres , @ es un separador, seguido de 7f31245a es un código hash hexadecimal. 3. El documento oficial, la Guía de arquitectura de criptografía de Java, menciona explícitamente el almacenamiento de contraseñas en char[] en lugar de String : “Parecería lógico recopilar y almacenar una contraseña en un objeto de tipo java.lang.String . Sin embargo, hay una advertencia aquí: los objetos String son inmutables, es decir, no hay métodos definidos que permitan modificar (sobrescribir) o anular el contenido de un objeto String después de su uso. Esta característica hace que los objetos String no sean adecuados para almacenar información confidencial, como contraseñas de usuario. En cambio, siempre debes recopilar y almacenar información de seguridad confidencial en una matriz de caracteres”.Análisis de preguntas y respuestas de entrevistas para desarrollador Java.  Parte 7 - 5

enumeración

66. Dé una breve descripción de Enum en Java.

Enum es una enumeración, un conjunto de constantes de cadena unidas por un tipo común. Declarado a través de la palabra clave -enum . Aquí hay un ejemplo con enumeración : roles válidos en una determinada escuela:
public enum Role {
   STUDENT,
   TEACHER,
   DIRECTOR,
   SECURITY_GUARD
}
Las palabras escritas en mayúsculas son las mismas constantes de enumeración que se declaran de forma simplificada, sin utilizar el operador new . El uso de enumeraciones simplifica enormemente la vida, ya que ayudan a evitar errores y confusiones al nombrar (ya que solo puede haber una determinada lista de valores). Personalmente, los encuentro muy convenientes cuando se usan en el diseño lógico de Switch .

67. ¿Puede Enum implementar interfaces?

Sí. Después de todo, las enumeraciones deben representar algo más que simples colecciones pasivas (como roles). En Java, pueden representar objetos más complejos con alguna funcionalidad, por lo que es posible que deba agregarles funcionalidad adicional. Esto también le permitirá utilizar las capacidades del polimorfismo sustituyendo el valor de enumeración en lugares donde se requiere el tipo de interfaz implementada.

68. ¿Puede Enum ampliar una clase?

No, no puede, porque una enumeración es una subclase predeterminada de la clase genérica Enum <T> , donde T representa el tipo de enumeración genérica. No es más que una clase base común para todos los tipos de enumeración del lenguaje Java. La conversión de enumeración a clase la realiza el compilador de Java en el momento de la compilación. Esta extensión no se indica explícitamente en el código, pero siempre está presente de forma invisible.

69. ¿Es posible crear una enumeración sin instancias de objetos?

En cuanto a mí, la pregunta es un poco extraña o no la entendí del todo. Tengo dos interpretaciones: 1. ¿Puede haber una enumeración sin valores? Sí, por supuesto, sería algo así como una clase vacía, sin sentido:
public enum Role {
}
Y llamando:
var s = Role.values();
System.out.println(s);
Recibiremos en la consola:
[L peso mosca.Rol;@9f70c54
(matriz vacía de valores de roles ) 2. ¿Es posible crear una enumeración sin el nuevo operador ? Sí, por supuesto. Como dije anteriormente, no es necesario utilizar el operador new para valores de enumeración (enumeraciones) , ya que estos son valores estáticos.

70. ¿Podemos anular el método toString() para Enum?

Sí, por supuesto, puede anular el método toString() para definir una forma específica de mostrar su enumeración al llamar al método toString (al traducir la enumeración a una cadena normal, por ejemplo, para enviarla a la consola o a los registros).
public enum Role {
   STUDENT,
   TEACHER,
   DIRECTOR,
   SECURITY_GUARD;

   @Override
   public String toString() {
       return "Выбрана роль - " + super.toString();
   }
}
¡Eso es todo por hoy, hasta la siguiente parte!Análisis de preguntas y respuestas de entrevistas para desarrollador Java.  Parte 7 - 6
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION