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 14

Publicado en el grupo Random-ES
¡Fuegos artificiales! El mundo está en constante movimiento y nosotros estamos en constante movimiento. Anteriormente, para convertirse en desarrollador de Java bastaba con conocer un poco de sintaxis de Java, y el resto ya vendría. Con el tiempo, el nivel de conocimiento requerido para convertirse en desarrollador de Java ha aumentado significativamente, al igual que la competencia, que continúa elevando el nivel inferior de conocimiento requerido. Análisis de preguntas y respuestas de entrevistas para desarrollador Java.  Parte 14 - 1Si realmente desea convertirse en desarrollador, debe darlo por sentado y prepararse a fondo para destacar entre principiantes como usted. Lo que haremos hoy es continuar analizando las más de 250 preguntas . En artículos anteriores, examinamos todas las preguntas del nivel junior y hoy abordaremos las preguntas del nivel medio. Aunque observo que estas no son preguntas 100% de nivel medio, la mayoría de ellas se pueden encontrar en una entrevista de nivel junior, porque es en dichas entrevistas donde se realiza un sondeo detallado de su base teórica, mientras que para un estudiante de nivel medio las preguntas Las preguntas se centran más en sondear su experiencia. Análisis de preguntas y respuestas de entrevistas para desarrollador Java.  Parte 14 - 2Pero, sin más, comencemos.

Medio

Son comunes

1. ¿Cuáles son las ventajas y desventajas de la programación orientada a objetos en comparación con la programación procedimental/funcional?

Había esta pregunta en el análisis de preguntas para Junior y, en consecuencia, ya la respondí. Busque esta pregunta y su respuesta en esta parte del artículo, preguntas 16 y 17.

2. ¿En qué se diferencia la agregación de la composición?

En POO, existen varios tipos de interacción entre objetos, unidos bajo el concepto general de "Relación Tiene-A". Esta relación indica que un objeto es un componente de otro objeto. Al mismo tiempo, existen dos subtipos de esta relación: Composición : un objeto crea otro objeto y la vida útil de otro objeto depende de la vida útil del creador. Agregación : un objeto recibe un enlace (puntero) a otro objeto durante el proceso de construcción (en este caso, la vida útil del otro objeto no depende de la vida útil del creador). Para una mejor comprensión, veamos un ejemplo específico. Tenemos una determinada clase de automóvil - Car , que a su vez tiene campos internos del tipo - Engine y una lista de pasajeros - List<Passenger> , también tiene un método para iniciar el movimiento - startMoving() :
public class Car {

 private Engine engine;
 private List<Passenger> passengers;

 public Car(final List<Passenger> passengers) {
   this.engine = new Engine();
   this.passengers = passengers;
 }

 public void addPassenger(Passenger passenger) {
   passengers.add(passenger);
 }

 public void removePassengerByIndex(Long index) {
   passengers.remove(index);
 }

 public void startMoving() {
   engine.start();
   System.out.println("Машина начала своё движение");
   for (Passenger passenger : passengers) {
     System.out.println("В машине есть пассажир - " + passenger.getName());
   }
 }
}
En este caso, la Composición es la conexión entre Car y Engine , ya que el rendimiento del auto depende directamente de la presencia del objeto motor, porque si motor = null , entonces recibiremos una NullPointerException . A su vez, un motor no puede existir sin una máquina (¿por qué necesitamos un motor sin una máquina?) y no puede pertenecer a varias máquinas al mismo tiempo. Esto significa que si eliminamos el objeto Car , no habrá más referencias al objeto Engine y pronto será eliminado por Garbage Collector . Como puede ver, esta relación es muy estricta (fuerte). La agregación es la conexión entre Car y Passenger , ya que el rendimiento de Car no depende de ningún modo de los objetos del tipo Passenger y su número. Pueden abandonar el automóvil - removePassengerByIndex(Long index) o ingresar nuevos - addPassenger(Passenger pasajero) , a pesar de esto, el automóvil seguirá funcionando correctamente. A su vez, los objetos Passenger pueden existir sin un objeto Car . Como comprenderá, esta es una conexión mucho más débil de la que vemos en la composición. Análisis de preguntas y respuestas de entrevistas para desarrollador Java.  Parte 14 - 3Pero eso no es todo, un objeto que está conectado por agregación con otro también puede tener una conexión determinada con otros objetos en el mismo momento. Por ejemplo, usted, como estudiante de Java, está inscrito en cursos de inglés, programación orientada a objetos y logaritmos al mismo tiempo, pero al mismo tiempo no es una parte críticamente necesaria de ellos, sin los cuales el funcionamiento normal es imposible (como un profesor).

3. ¿Qué patrones GoF has utilizado en la práctica? Dar ejemplos.

Ya respondí esta pregunta antes, así que solo dejaré un enlace al análisis , vea la primera pregunta. También encontré un maravilloso artículo con una hoja de referencia sobre patrones de diseño, que recomiendo tener a mano.

4. ¿Qué es un objeto proxy? Dar ejemplos

Un proxy es un patrón de diseño estructural que le permite sustituir objetos sustitutos especiales, o en otras palabras, objetos proxy, en lugar de objetos reales. Estos objetos proxy interceptan llamadas al objeto original, lo que permite insertar cierta lógica antes o después de que la llamada se pase al original. Análisis de preguntas y respuestas de entrevistas para desarrollador Java.  Parte 14 - 4Ejemplos de uso de un objeto proxy:
  • Como proxy remoto: se utiliza cuando necesitamos un objeto remoto (un objeto en un espacio de direcciones diferente) que debe representarse localmente. En este caso, el proxy se encargará de la creación de la conexión, codificación, decodificación, etc., mientras que el cliente lo utilizará como si fuera el objeto original ubicado en el espacio local.

  • Como proxy virtual: se utiliza cuando se necesita un objeto que consume muchos recursos. En este caso, el objeto proxy sirve como algo así como una imagen de un objeto real que en realidad aún no existe. Cuando se envía una solicitud real (llamada a un método) a este objeto, solo entonces se carga el objeto original y se ejecuta el método. Este enfoque también se denomina inicialización retrasada; esto puede ser muy conveniente, porque en algunas situaciones el objeto original puede no ser útil y entonces no habrá ningún costo para crearlo.

  • Como proxy de seguridad: se utiliza cuando es necesario controlar el acceso a algún objeto en función de los derechos del cliente. Es decir, si un cliente al que le faltan derechos de acceso intenta acceder al objeto original, el proxy lo interceptará y no lo permitirá.

Veamos un ejemplo de un proxy virtual: tenemos una interfaz de controlador:
public interface Processor {
 void process();
}
Cuya implementación utiliza demasiados recursos, pero al mismo tiempo es posible que no se utilice cada vez que se inicia la aplicación:
public class HiperDifficultProcessor implements Processor {
 @Override
 public void process() {
   // некоторый сверхсложная обработка данных
 }
}
Clase de proxy:
public class HiperDifficultProcessorProxy implements Processor {
private HiperDifficultProcessor processor;

 @Override
 public void process() {
   if (processor == null) {
     processor = new HiperDifficultProcessor();
   }
   processor.process();
 }
}
Ejecutémoslo en main :
Processor processor = new HiperDifficultProcessorProxy();
// тут тяжеловсеного оригинального un objetoа, ещё не сущетсвует
// но при этом есть un objeto, который его представляет и у которого можно вызывать его методы
processor.process(); // лишь теперь, un objeto оригинал был создан
Observo que muchos marcos usan proxy, y para Spring este es un patrón clave (Spring está cosido con él por dentro y por fuera). Lea más sobre este patrón aquí . Análisis de preguntas y respuestas de entrevistas para desarrollador Java.  Parte 14 - 5

5. ¿Qué innovaciones se han anunciado en Java 8?

Las innovaciones que trae Java 8 son las siguientes:
  • Se han agregado interfaces funcionales, lea sobre qué tipo de bestia es esta aquí .

  • Las expresiones Lambda, que están estrechamente relacionadas con las interfaces funcionales, leen más sobre su uso aquí .

  • Se agregó Stream API para un procesamiento conveniente de las recopilaciones de datos; lea más aquí .

  • Se agregaron enlaces a métodos .

  • El método forEach() se ha agregado a la interfaz Iterable .

  • Se agregó una nueva API de fecha y hora en el paquete java.time , análisis detallado aquí .

  • API concurrente mejorada .

  • Al agregar una clase contenedora opcional , que se usa para manejar correctamente los valores nulos, puede encontrar un excelente artículo sobre este tema aquí .

  • Agregar la capacidad de que las interfaces utilicen métodos estáticos y predeterminados (lo que, en esencia, acerca a Java a la herencia múltiple), más detalles aquí .

  • Se agregaron nuevos métodos a la clase Collection(removeIf(), spliterator()) .

  • Mejoras menores en Java Core.

Análisis de preguntas y respuestas de entrevistas para desarrollador Java.  Parte 14 - 6

6. ¿Qué son la alta cohesión y el bajo acoplamiento? Dar ejemplos.

Alta Cohesión o Alta Cohesión es el concepto cuando una determinada clase contiene elementos que están estrechamente relacionados entre sí y combinados para su propósito. Por ejemplo, todos los métodos de la clase Usuario deberían representar el comportamiento del usuario. Una clase tiene baja cohesión si contiene elementos no relacionados. Por ejemplo, la clase Usuario que contiene un método de validación de dirección de correo electrónico:
public class User {
private String name;
private String email;

 public String getName() {
   return this.name;
 }

 public void setName(final String name) {
   this.name = name;
 }

 public String getEmail() {
   return this.email;
 }

 public void setEmail(final String email) {
   this.email = email;
 }

 public boolean isValidEmail() {
   // некоторая логика валидации емейла
 }
}
La clase de usuario puede ser responsable de almacenar la dirección de correo electrónico del usuario, pero no de validarla ni enviar el correo electrónico. Por lo tanto, para lograr una alta coherencia, trasladamos el método de validación a una clase de utilidad separada:
public class EmailUtil {
 public static boolean isValidEmail(String email) {
   // некоторая логика валидации емейла
 }
}
Y lo usamos según sea necesario (por ejemplo, antes de guardar al usuario). Low Coupling o Low Coupling es un concepto que describe la baja interdependencia entre módulos de software. Esencialmente, la interdependencia es cómo cambiar uno requiere cambiar el otro. Dos clases tienen un acoplamiento fuerte (o un acoplamiento estrecho) si están estrechamente relacionadas. Por ejemplo, dos clases concretas que almacenan referencias entre sí y llaman a los métodos de cada uno. Las clases poco acopladas son más fáciles de desarrollar y mantener. Como son independientes entre sí, pueden desarrollarse y probarse en paralelo. Además, se pueden cambiar y actualizar sin afectarse entre sí. Veamos un ejemplo de clases fuertemente acopladas. Tenemos alguna clase de estudiantes:
public class Student {
 private Long id;
 private String name;
 private List<Lesson> lesson;
}
Que contiene una lista de lecciones:
public class Lesson {
 private Long id;
 private String name;
 private List<Student> students;
}
Cada lección contiene un enlace para asistir a los estudiantes. Agarre increíblemente fuerte, ¿no crees? ¿Cómo puedes reducirlo? Primero, asegurémonos de que los estudiantes no tengan una lista de materias, sino una lista de sus identificadores:
public class Student {
 private Long id;
 private String name;
 private List<Long> lessonIds;
}
En segundo lugar, la clase de la lección no necesita conocer a todos los estudiantes, así que eliminemos su lista por completo:
public class Lesson {
 private Long id;
 private String name;
}
Entonces se volvió mucho más fácil y la conexión se volvió mucho más débil, ¿no crees? Análisis de preguntas y respuestas de entrevistas para desarrollador Java.  Parte 14 - 7

POO

7. ¿Cómo se puede implementar la herencia múltiple en Java?

La herencia múltiple es una característica del concepto orientado a objetos donde una clase puede heredar propiedades de más de una clase principal. El problema surge cuando hay métodos con la misma firma tanto en la superclase como en la subclase. Al llamar a un método, el compilador no puede determinar qué método de clase se debe llamar, ni siquiera cuando se llama al método de clase que tiene prioridad. Por lo tanto, ¡Java no admite herencia múltiple! Pero existe una especie de laguna jurídica, de la que hablaremos a continuación. Como mencioné anteriormente, con el lanzamiento de Java 8, se agregó a las interfaces la capacidad de tener métodos predeterminados . Si la clase que implementa la interfaz no anula este método, entonces se utilizará esta implementación predeterminada (no es necesario anular el método predeterminado, como implementar uno abstracto). En este caso, es posible implementar diferentes interfaces en una clase y utilizar sus métodos predeterminados. Veamos un ejemplo. Tenemos una interfaz de volante, con un método fly() predeterminado :
public interface Flyer {
 default void fly() {
   System.out.println("Я лечу!!!");
 }
}
La interfaz de Walker, con el método walk() predeterminado :
public interface Walker {
 default void walk() {
   System.out.println("Я хожу!!!");
 }
}
La interfaz del nadador, con el método swim() :
public interface Swimmer {
 default void swim() {
   System.out.println("Я плыву!!!");
 }
}
Bueno, ahora implementemos todo esto en una clase de pato:
public class Duck implements Flyer, Swimmer, Walker {
}
Y ejecutemos todos los métodos de nuestro pato:
Duck donald = new Duck();
donald.walk();
donald.fly();
donald.swim();
En la consola recibiremos:
¡¡¡Voy!!! ¡¡¡Estoy volando!!! ¡¡¡Estoy nadando!!!
Esto significa que hemos descrito correctamente la herencia múltiple, aunque no sea así. Análisis de preguntas y respuestas de entrevistas para desarrollador Java.  Parte 14 - 8También señalaré que si una clase implementa interfaces con métodos predeterminados que tienen los mismos nombres de método y los mismos argumentos en estos métodos, entonces el compilador comenzará a quejarse de incompatibilidad, ya que no comprende qué método realmente necesita usarse. Hay varias salidas:
  • Cambie el nombre de los métodos en las interfaces para que difieran entre sí.
  • Anule métodos tan controvertidos en la clase de implementación.
  • Hereda de una clase que implementa estos métodos controvertidos (entonces tu clase usará exactamente su implementación).

8. ¿Cuál es la diferencia entre los métodos final, finalmente y finalizar()?

final es una palabra clave que se utiliza para imponer una restricción a una clase, método o variable, una restricción significa:
  • Para una variable: después de la inicialización inicial, la variable no se puede redefinir.
  • Para un método, el método no se puede anular en una subclase (clase sucesora).
  • Para una clase, la clase no se puede heredar.
finalmente es una palabra clave antes de un bloque de código, que se usa cuando se manejan excepciones, junto con un bloque try y junto (o indistintamente) con un bloque catch. El código de este bloque se ejecuta en cualquier caso, independientemente de si se produce una excepción o no. En esta parte del artículo, en la pregunta 104, se comentan situaciones excepcionales en las que no se ejecutará este bloque. finalize() es un método de la clase Object , llamado antes de que el recolector de basura elimine cada objeto, este método se llamará (último) y se usa para limpiar los recursos ocupados. Para obtener más información sobre los métodos de la clase Object que hereda cada objeto, consulte la pregunta 11 de esta parte del artículo. Bueno, ahí es donde terminaremos hoy. ¡Nos vemos en la siguiente parte! Análisis de preguntas y respuestas de entrevistas para desarrollador Java.  Parte 14 - 9
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION