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 12

Publicado en el grupo Random-ES
¡Hola! El conocimiento es poder. Cuanto más conocimiento tenga antes de su primera entrevista, más confianza se sentirá al hacerlo. Análisis de preguntas y respuestas de entrevistas para desarrollador Java.  Parte 12 - 1Con un buen conocimiento, será difícil confundirte y, al mismo tiempo, podrás sorprender gratamente a tu entrevistador. Por eso, hoy, sin más preámbulos, seguiremos reforzando tu base teórica examinando más de 250 preguntas para un desarrollador de Java . Análisis de preguntas y respuestas de entrevistas para desarrollador Java.  Parte 12 - 2

103. ¿Cuáles son las reglas para comprobar las excepciones en la herencia?

Si entiendo correctamente la pregunta, preguntan sobre las reglas para trabajar con excepciones durante la herencia, y son las siguientes:
  • Un método anulado o implementado en un descendiente/implementación no puede generar excepciones marcadas que sean superiores en la jerarquía que las excepciones en la superclase/método de interfaz.
Es decir, si tenemos una determinada interfaz Animal con un método que lanza IOException :
public  interface Animal {
   void voice() throws IOException;
}
En la implementación de esta interfaz, no podemos lanzar una excepción lanzada más general (por ejemplo, Exception , Throwable ), pero podemos reemplazarla con una excepción descendiente, como FileNotFoundException :
public class Cat implements Animal {
   @Override
   public void voice() throws FileNotFoundException {
// некоторая реализация
   }
}
  • El constructor de la subclase debe incluir en su bloque de lanzamientos todas las clases de excepción lanzadas por el constructor de la superclase que se invoca al crear el objeto.
Supongamos que el constructor de la clase Animal genera muchas excepciones:
public class Animal {
  public Animal() throws ArithmeticException, NullPointerException, IOException {
  }
Luego el heredero de la clase también deberá indicarlos en el constructor:
public class Cat extends Animal {
   public Cat() throws ArithmeticException, NullPointerException, IOException {
       super();
   }
O, como en el caso de los métodos, puede especificar no las mismas excepciones, sino otras más generales. En nuestro caso, bastará con especificar una excepción más general: Exception , ya que este es el ancestro común de las tres excepciones consideradas:
public class Cat extends Animal {
   public Cat() throws Exception {
       super();
   }

104. ¿Podrías escribir código para cuando no se ejecute el bloque finalmente?

Primero, recordemos qué es finalmente . Anteriormente, analizamos el mecanismo para detectar excepciones: el bloque try describe el área de captura, mientras que los bloques catch son el código que funcionará cuando se produzca una excepción específica. Finalmente está el tercer bloque de código después de finalmente que es intercambiable con catch pero no mutuamente excluyente. La esencia de este bloque es que el código que contiene siempre funciona, independientemente del resultado del intento o la captura (independientemente de si se lanzó una excepción o no). Los casos de fallo son muy raros y anormales. El caso más simple de falla es cuando se llama al método System.exit(0) en el código anterior , que finaliza el programa (lo extingue):
try {
   throw new IOException();
} catch (IOException e) {
   System.exit(0);
} finally {
   System.out.println("Данное сообщение не будет выведенно в консоль");
}
También hay algunas otras situaciones en las que finalmente no funcionará:
  • Terminación anormal del programa causada por problemas críticos del sistema, o la caída de algún Error que “bloqueará” la aplicación (un ejemplo de error puede ser el mismo StackOwerflowError que ocurre cuando la memoria de la pila se desborda).
  • Cuando el hilo demoníaco pasa por el ry...finalmente se bloquea y en paralelo con esto finaliza el programa. Después de todo, el hilo demoníaco es un hilo para acciones en segundo plano, es decir, no es prioritario ni obligatorio, y la aplicación no esperará a que finalice su trabajo.
  • El bucle infinito más común, en try o catch , una vez en el que el flujo permanecerá allí para siempre:

    try {
       while (true) {
       }
    } finally {
       System.out.println("Данное сообщение не будет выведенно в консоль");
    }

Esta pregunta es bastante popular en las entrevistas para principiantes, por lo que vale la pena recordar un par de estas situaciones excepcionales. Análisis de preguntas y respuestas de entrevistas para desarrollador Java.  Parte 12 - 3

105. Escriba un ejemplo de manejo de múltiples excepciones en un bloque catch.

1) Quizás la pregunta se formuló incorrectamente. Según tengo entendido, esta pregunta se refiere a múltiples capturas para un bloque de prueba :
try {
  throw new FileNotFoundException();
} catch (FileNotFoundException e) {
   System.out.print("Упс, у вас упало исключение - " + e);
} catch (IOException e) {
   System.out.print("Упс, у вас упало исключение - " + e);
} catch (Exception e) {
   System.out.print("Упс, у вас упало исключение - " + e);
}
Si se produce una excepción en un bloque try , entonces los bloques catch intentan capturarla alternativamente de arriba a abajo. Si un determinado bloque catch tiene éxito, obtiene el derecho de manejar la excepción, mientras que el resto de los bloques siguientes ya no lo serán. capaces de intentar captarlo y procesarlo a su manera. Por lo tanto, las excepciones más estrechas se colocan más arriba en la cadena de bloques de captura y las excepciones más amplias se colocan más abajo. Por ejemplo, si en nuestro primer bloque catch se detecta una excepción de la clase Exception , las excepciones marcadas no podrán ingresar a los bloques restantes (los bloques restantes con descendientes de Exception serán absolutamente inútiles). 2) La pregunta se formuló correctamente, en este caso nuestro procesamiento quedará de la siguiente manera:
try {
  throw new NullPointerException();
} catch (Exception e) {
   if (e instanceof FileNotFoundException) {
       // некоторая обработка с сужением типа (FileNotFoundException)e
   } else if (e instanceof ArithmeticException) {
       // некоторая обработка с сужением типа (ArithmeticException)e
   } else if(e instanceof NullPointerException) {
       // некоторая обработка с сужением типа (NullPointerException)e
   }
Habiendo detectado una excepción a través de catch , intentamos descubrir su tipo específico a través del método instancia de , que se utiliza para comprobar si un objeto pertenece a un determinado tipo, para que luego podamos limitarlo a este tipo sin consecuencias negativas. Ambos enfoques considerados se pueden usar en la misma situación, pero dije que la pregunta es incorrecta porque no llamaría buena a la segunda opción y nunca la he visto en mi práctica, mientras que al mismo tiempo el primer método con capturas múltiples se ha generalizado. atención difundiendo. Análisis de preguntas y respuestas de entrevistas para desarrollador Java.  Parte 12 - 4

106. ¿Qué operador le permite forzar el lanzamiento de una excepción? Escribe un ejemplo

Ya lo he usado varias veces arriba, pero aún así repetiré esta palabra clave: throw . Uso de ejemplo (forzar una excepción):
throw new NullPointerException();

107. ¿Puede el método principal generar una excepción de lanzamiento? Si es así, ¿adónde será transferido?

En primer lugar, quiero señalar que main no es más que un método normal, y sí, lo llama la máquina virtual para comenzar a ejecutar el programa, pero además de esto, se puede llamar desde cualquier otro código. Es decir, también está sujeto a las reglas habituales para especificar excepciones marcadas después de los lanzamientos :
public static void main(String[] args) throws IOException {
En consecuencia, también pueden ocurrir excepciones en él. Si main no fue llamado en algún método, pero se inició como un punto de inicio del programa, entonces la excepción que genere será manejada por el interceptor .UncaughtExceptionHandler . Este controlador es uno por subproceso (es decir, un controlador en cada subproceso). Si es necesario, puede crear su propio controlador y configurarlo usando el método setDefaultUncaughtExceptionHandler llamado en el objeto Thread .

subprocesos múltiples

Análisis de preguntas y respuestas de entrevistas para desarrollador Java.  Parte 12 - 5

108. ¿Qué herramientas conoces para trabajar con subprocesos múltiples?

Herramientas básicas/básicas para usar subprocesos múltiples en Java:
  • Sincronizado es un mecanismo para cerrar (bloquear) un método/bloque cuando un hilo ingresa en él, desde otros hilos.
  • Volátil es un mecanismo para garantizar el acceso consistente a una variable por parte de diferentes subprocesos, es decir, con la presencia de este modificador en una variable, todas las operaciones de asignación y lectura deben ser atómicas. En otras palabras, los hilos no copiarán esta variable a su memoria local y la cambiarán, sino que cambiarán su valor original.
Lea más sobre volátiles aquí .
  • Runnable es una interfaz que se puede implementar (en particular, su método de ejecución) en una determinada clase:
public class CustomRunnable implements Runnable {
   @Override
   public void run() {
       // некоторая логика
   }
}
Y habiendo creado un objeto de esta clase, puede iniciar un nuevo hilo configurando este objeto en el constructor del nuevo objeto Thread y llamando a su método start() :
Runnable runnable = new CustomRunnable();
new Thread(runnable).start();
El método start ejecuta el método run() implementado en un hilo separado.
  • Thread es una clase, heredando de la cual (mientras anula el método de ejecución ):
public class CustomThread extends Thread {
   @Override
   public void run() {
       // некоторая логика
   }
}
Y al crear un objeto de esta clase y ejecutarlo usando el método start() , lanzaremos un nuevo hilo:
new CustomThread().start();
  • La concurrencia es un paquete con herramientas para trabajar en un entorno multiproceso.
Consiste en:
  • Colecciones concurrentes : un conjunto de colecciones especializadas para trabajar en un entorno multiproceso.
  • Colas : colas especializadas para un entorno multiproceso (con y sin bloqueo).
  • Los sincronizadores son utilidades especializadas para trabajar en un entorno multiproceso.
  • Los ejecutores son mecanismos para crear grupos de subprocesos.
  • Bloqueos : mecanismos de sincronización de subprocesos (más flexibles que los estándar: sincronizar, esperar, notificar, notificar a todos).
  • Los atómicos son clases optimizadas para la ejecución de subprocesos múltiples; cada operación es atómica.
Lea más sobre el paquete concurrente aquí .

109. Hable sobre la sincronización entre subprocesos. ¿Para qué se utilizan los métodos wait(), notify() - notifyAll() join()?

Hasta donde tengo entendido la pregunta, la sincronización entre subprocesos se trata del modificador de clave: sincronizado . Este modificador se puede colocar directamente al lado del bloque:
synchronized (Main.class) {
   // некоторая логика
}
O directamente en la firma del método:
public synchronized void move() {
   // некоторая логика}
Como dije antes, sincronizado es un mecanismo que le permite cerrar un bloque/método de otros subprocesos cuando un subproceso ya ha ingresado en él. Piense en un bloque/método como una habitación. Alguna corriente, al llegar a ella, entrará y la bloqueará, otras corrientes, al llegar a la habitación y ver que está cerrada, esperarán cerca de ella hasta que esté libre. Una vez hecho su trabajo, el primer hilo sale de la habitación y suelta la llave. Y no en vano hablaba constantemente de la clave, porque realmente existe. Este es un objeto especial que tiene un estado ocupado/libre. Este objeto está adjunto a cada objeto Java, por lo que cuando usamos un bloque sincronizado debemos indicar entre paréntesis el objeto cuyo mutex queremos cerrar la puerta:
Cat cat = new Cat();
synchronized (cat) {
   // некоторая логика
}
También puedes usar una clase mutex, como hice en el primer ejemplo ( Main.class ). Cuando usamos sincronizado en un método, no especificamos el objeto que queremos cerrar, ¿verdad? En este caso, para un método no estático, se cerrará en el mutex de este objeto , es decir, el objeto actual de esta clase. El estático se cerrará en el mutex de la clase actual ( this.getClass(); ). Puede leer más sobre mutex aquí . Bueno, lee sobre sincronizado aquí . Wait() es un método que libera el mutex y pone el hilo actual en modo de espera, como si estuviera conectado al monitor actual (algo así como un ancla). Debido a esto, este método solo se puede llamar desde un bloque o método sincronizado (de lo contrario, qué debería liberarse y qué debería esperar). También tenga en cuenta que este es un método de la clase Objeto . Más precisamente, no uno, sino incluso tres:
  • Wait() : pone el hilo actual en modo de espera hasta que otro hilo llame al método notify() o notifyAll() para este objeto (hablaremos de estos métodos más adelante).

  • Esperar (tiempo de espera prolongado) : pone el subproceso actual en modo de espera hasta que otro subproceso llame al método notify() o notifyAll() en este objeto o hasta que expire el tiempo de espera especificado .

  • Espere (tiempo de espera prolongado, int nanos) : similar al anterior, solo que nanos le permite especificar nanosegundos (configuración de tiempo más precisa).

  • Notify() es un método que le permite activar un hilo aleatorio del bloque de sincronización actual. Nuevamente, solo se puede llamar en un bloque o método sincronizado (después de todo, en otros lugares no tendrá a nadie a quien descongelar).

  • NotifyAll() es un método que activa todos los subprocesos en espera en el monitor actual (también se usa solo en un bloque o método sincronizado ).

110. ¿Cómo detener el flujo?

Lo primero que hay que decir es que cuando el método run() se ejecuta por completo , el hilo se destruye automáticamente. Pero a veces es necesario matarlo antes de lo previsto, antes de que se complete este método. Entonces, ¿qué debemos hacer entonces? ¿Quizás el objeto Thread debería tener un método stop() ? ¡No importa cómo sea! Este método se considera obsoleto y puede provocar fallos del sistema. Análisis de preguntas y respuestas de entrevistas para desarrollador Java.  Parte 12 - 6Bueno, ¿entonces qué? Hay dos formas de hacer esto: la primera es usar su indicador booleano interno. Veamos un ejemplo. Tenemos nuestra propia implementación de un hilo que debería mostrar una determinada frase en la pantalla hasta que se detenga por completo:
public class CustomThread extends Thread {
private boolean isActive;

   public CustomThread() {
       this.isActive = true;
   }

   @Override
   public void run() {
       {
           while (isActive) {
               System.out.println("Поток выполняет некую логику...");
           }
           System.out.println("Поток остановлен!");
       }
   }

   public void stopRunningThread() {
       isActive = false;
   }
}
Cuando se utiliza el método stopRunning() , el indicador interno se vuelve falso y el método de ejecución deja de ejecutarse. Ejecutémoslo en main :
System.out.println("Начало выполнения программы");
CustomThread thread = new CustomThread();
thread.start();
Thread.sleep(3);
// пока наш основной поток спит, вспомогательный  CustomThread работает и выводит в коноль своё сообщение
thread.stopRunningThread();
System.out.println("Конец выполнения программы");
Como resultado, veremos algo como esto en la consola:
Inicio de la ejecución del programa El hilo está ejecutando algo de lógica... El hilo está ejecutando algo de lógica... El hilo está ejecutando algo de lógica... El hilo está ejecutando algo de lógica... El hilo está ejecutando algo de lógica... El El hilo está ejecutando alguna lógica... Fin de la ejecución del programa ¡El hilo está detenido!
Esto significa que nuestro hilo funcionó, envió una cierta cantidad de mensajes a la consola y se detuvo con éxito. Observo que la cantidad de mensajes generados variará de una ejecución a otra; a veces, el hilo adicional ni siquiera generó nada. Como noté, esto depende del tiempo de inactividad del hilo principal; cuanto más largo sea, menos posibilidades hay de que el hilo adicional no genere nada. Con un tiempo de suspensión de 1 ms, los mensajes casi nunca se emiten, pero si lo configura en 20 ms, casi siempre funciona. Quizás, cuando hay poco tiempo, el hilo simplemente no tiene tiempo de iniciarse y comenzar su trabajo, y se detiene inmediatamente. La segunda forma es utilizar el método interrumpido() en el objeto Thread , que devuelve el valor del indicador de interrupción interno (este indicador es falso de forma predeterminada ) y su otro método de interrupción() , que establece este indicador en verdadero (cuando este (la bandera es verdadera , el hilo debería detener su trabajo). Veamos un ejemplo:
public class CustomThread extends Thread {

   @Override
   public void run() {
       {
           while (!Thread.interrupted()) {
               System.out.println("Поток выполняет некую логику...");
           }
           System.out.println("Поток остановлен!");
       }
   }
}
Ejecutar en principal :
System.out.println("Начало выполнения программы");
Thread thread = new CustomThread();
thread.start();
Thread.sleep(3);
thread.interrupt();
System.out.println("Конец выполнения программы");
El resultado de la ejecución será el mismo que en el primer caso, pero me gusta más este enfoque: escribimos menos código y utilizamos más funciones estándar ya preparadas. Análisis de preguntas y respuestas de entrevistas para desarrollador Java.  Parte 12 - 7Ahí es donde nos detendremos hoy.Análisis de preguntas y respuestas de entrevistas para desarrollador Java.  Parte 12 - 8
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION