103. ¿Qué reglas se aplican a la verificación de excepciones durante la herencia?
Si entiendo la pregunta correctamente, me preguntan sobre las reglas para trabajar con excepciones durante la herencia. Las reglas pertinentes son las siguientes:- Un método anulado o implementado en un descendiente/implementación no puede generar excepciones marcadas que sean más altas en la jerarquía que las excepciones en un método de superclase/interfaz.
public interface Animal {
void speak() throws IOException;
}
Al implementar esta interfaz, no podemos exponer una excepción arrojable más general (por ejemplo, Exception , Throwable ), pero podemos reemplazar la excepción existente con una subclase, como FileNotFoundException :
public class Cat implements Animal {
@Override
public void speak() throws FileNotFoundException {
// Some implementation
}
}
- La cláusula throws del constructor de la subclase debe incluir todas las clases de excepción lanzadas por el constructor de la superclase llamado para crear el objeto.
public class Animal {
public Animal() throws ArithmeticException, NullPointerException, IOException {
}
Entonces un constructor de subclase también debe lanzarlos:
public class Cat extends Animal {
public Cat() throws ArithmeticException, NullPointerException, IOException {
super();
}
O, como ocurre con los métodos, puede especificar excepciones diferentes y más generales. En nuestro caso, podemos indicar Excepción , porque es más general y es un ancestro común de las tres excepciones indicadas en la superclase:
public class Cat extends Animal {
public Cat() throws Exception {
super();
}
104. ¿Puedes escribir algún código donde no se ejecute el bloque finalmente?
Primero, recordemos lo que finalmente es. Anteriormente, examinamos el mecanismo de captura de excepciones: un bloque try designa dónde se capturarán las excepciones, y los bloques catch son el código que se invocará cuando se detecte la excepción correspondiente. Un tercer bloque de código marcado por la palabra clave finalmente puede reemplazar o aparecer después de los bloques catch. La idea detrás de este bloque es que su código siempre se ejecuta independientemente de lo que suceda en un bloque try o catch (independientemente de si hay una excepción o no). Los casos en los que este bloque no se ejecuta son poco frecuentes y son anormales. El ejemplo más simple es cuando se llama a System.exit(0) antes del bloque finalmente, finalizando así el programa:try {
throw new IOException();
} catch (IOException e) {
System.exit(0);
} finally {
System.out.println("This message will not be printed on the console");
}
También hay otras situaciones en las que el bloque finalmente no se ejecuta:
-
Por ejemplo, una terminación anormal de un programa causada por errores críticos del sistema, o algún error que hace que la aplicación se bloquee (por ejemplo, StackOverflowError , que ocurre cuando la pila de la aplicación se desborda).
-
Otra situación es cuando un hilo de demonio ingresa a un bloque try-finally , pero luego el hilo principal del programa termina. Después de todo, los subprocesos del demonio son para trabajo en segundo plano que no es de alta prioridad ni obligatorio, por lo que la aplicación no esperará a que finalicen.
-
El ejemplo más absurdo es un bucle sin fin dentro de un bloque try o catch ; una vez dentro, un hilo quedará atrapado allí para siempre:
try { while (true) { } } finally { System.out.println("This message will not be printed on the console"); }
105. Escriba un ejemplo en el que maneje múltiples excepciones en un solo bloque catch.
1) No estoy seguro de que esta pregunta se haya formulado correctamente. Según tengo entendido, esta pregunta se refiere a varios bloques catch y un solo intento :try {
throw new FileNotFoundException();
} catch (FileNotFoundException e) {
System.out.print("Oops! There was an exception: " + e);
} catch (IOException e) {
System.out.print("Oops! There was an exception: " + e);
} catch (Exception e) {
System.out.print("Oops! There was an exception: " + e);
}
Si se produce una excepción en un bloque try , los bloques catch asociados intentan detectarla, secuencialmente de arriba a abajo. Una vez que la excepción coincida con uno de los bloques catch , los bloques restantes ya no podrán detectarlo ni manejarlo. Todo esto significa que las excepciones más limitadas se organizan por encima de las más generales en el conjunto de bloques catch . Por ejemplo, si nuestro primer bloque catch captura la clase Exception , los bloques posteriores no detectarán las excepciones marcadas (es decir, los bloques restantes con subclases de Exception serán completamente inútiles). 2) O quizás la pregunta se formuló correctamente. En ese caso, podríamos manejar las excepciones de la siguiente manera:
try {
throw new NullPointerException();
} catch (Exception e) {
if (e instanceof FileNotFoundException) {
// Some handling that involves a narrowing type conversion: (FileNotFoundException)e
} else if (e instanceof ArithmeticException) {
// Some handling that involves a narrowing type conversion: (ArithmeticException)e
} else if(e instanceof NullPointerException) {
// Some handling that involves a narrowing type conversion: (NullPointerException)e
}
Después de usar catch para detectar una excepción, intentamos descubrir su tipo específico usando el operador instancia de , que verifica si un objeto pertenece a un determinado tipo. Esto nos permite realizar con confianza una conversión de tipo restringido sin temor a consecuencias negativas. Podríamos aplicar cualquier enfoque en la misma situación. Expresé mis dudas sobre la pregunta sólo porque no consideraría que la segunda opción fuera un buen enfoque. En mi experiencia, nunca lo he encontrado y el primer enfoque que involucra múltiples bloques catch está muy extendido.
106. ¿Qué operador te permite forzar el lanzamiento de una excepción? Escribe un ejemplo
Ya la he usado varias veces en los ejemplos anteriores, pero la repetiré una vez más: la palabra clave throw . Ejemplo de cómo lanzar una excepción manualmente:throw new NullPointerException();
107. ¿Puede el método principal generar una excepción? Si es así, ¿a dónde va?
En primer lugar, quiero señalar que el método principal no es más que un método normal. Sí, lo llama la máquina virtual para iniciar la ejecución de un programa, pero más allá de eso, se puede llamar desde cualquier otro código. Eso significa que también está sujeto a las reglas habituales sobre la indicación de excepciones marcadas después de la palabra clave throws :public static void main(String[] args) throws IOException {
En consecuencia, puede generar excepciones. Cuando se llama a main como punto de partida del programa (en lugar de mediante algún otro método), cualquier excepción que arroje será manejada por UncaughtExceptionHandler
. Cada hilo tiene uno de esos controladores (es decir, hay uno de esos controladores en cada hilo). Si es necesario, puede crear su propio controlador y configurarlo llamando al método public static void main(String[] args) throws IOException {setDefaultUncaughtExceptionHandler en un public static void main(String[] args) throws IOException {Thread object.
subprocesos múltiples
108. ¿Qué mecanismos para trabajar en un entorno multiproceso conoces?
Los mecanismos básicos para subprocesos múltiples en Java son:-
La palabra clave sincronizada , que es una forma para que un subproceso bloquee un método/bloque cuando ingresa, evitando que otros subprocesos ingresen.
-
La palabra clave volátil garantiza un acceso consistente a una variable a la que acceden diferentes subprocesos. Es decir, cuando este modificador se aplica a una variable, todas las operaciones para asignar y leer esa variable se vuelven atómicas. En otras palabras, los subprocesos no copiarán la variable a su memoria local ni la cambiarán. Cambiarán su valor original.
-
Ejecutable : podemos implementar esta interfaz (que consta de un único método run() ) en alguna clase:
public class CustomRunnable implements Runnable { @Override public void run() { // Some logic } }
Y una vez que creamos un objeto de esa clase, podemos iniciar un nuevo hilo pasando nuestro objeto al constructor Thread y luego llamando al 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.
-
Hilo : podemos heredar esta clase y anular su método de ejecución :
public class CustomThread extends Thread { @Override public void run() { // Some logic } }
Podemos iniciar un nuevo hilo creando un objeto de esta clase y luego llamando al método start() :
new CustomThread().start();
- Concurrencia : este es un paquete de herramientas para trabajar en un entorno multiproceso.
Consiste en:
-
Colecciones concurrentes : se trata de una colección de colecciones creadas explícitamente para trabajar en un entorno multiproceso.
-
Colas : colas especializadas para un entorno multiproceso (con y sin bloqueo).
-
Sincronizadores : son utilidades especializadas para trabajar en un entorno multiproceso.
-
Ejecutores : mecanismos para crear grupos de subprocesos.
-
Bloqueos : mecanismos de sincronización de subprocesos que son más flexibles que los estándar (sincronizado, esperar, notificar, notificar a todos).
- Atomics : clases optimizadas para subprocesos múltiples. Cada una de sus operaciones es atómica.
-
109. Cuéntenos sobre la sincronización entre subprocesos. ¿Para qué sirven los métodos wait(), notify(), notifyAll() y join()?
La sincronización entre subprocesos se trata de la palabra clave sincronizada . Este modificador se puede colocar directamente en el bloque:synchronized (Main.class) {
// Some logic
}
O directamente en la firma del método:
public synchronized void move() {
// Some logic }
Como dije antes, sincronizado es un mecanismo para bloquear un bloque/método a otros subprocesos una vez que ingresa un subproceso. Pensemos en un bloque/método de código como una habitación. Un hilo se acerca a la habitación, entra y cierra la puerta con su llave. Cuando otros hilos se acercan a la habitación, ven que la puerta está cerrada y esperan cerca hasta que la habitación esté disponible. Una vez que el primer hilo termina con su trabajo en la habitación, abre la puerta, sale de la habitación y suelta la llave. He mencionado una clave un par de veces por una razón: porque realmente existe algo análogo. Este es un objeto especial que tiene un estado ocupado/libre. Cada objeto en Java tiene un objeto de este tipo, por lo que cuando usamos el bloque sincronizado , necesitamos usar paréntesis para indicar el objeto cuyo mutex se bloqueará:
Cat cat = new Cat();
synchronized (cat) {
// Some logic
}
También podemos usar un mutex asociado con una clase, como hice en el primer ejemplo ( Main.class ). Después de todo, cuando usamos sincronizado en un método, no especificamos el objeto que queremos bloquear, ¿verdad? En este caso, para métodos no estáticos, el mutex que se bloqueará es este objeto, es decir, el objeto actual de la clase. Para métodos estáticos, el mutex asociado con la clase actual ( this.getClass(); ) está bloqueado. wait() es un método que libera el mutex y pone el hilo actual en estado de espera, como si se conectara 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, ¿a qué estaría esperando y qué se lanzaría?). También tenga en cuenta que este es un método de la clase Objeto . Bueno, no uno, sino tres:
-
wait() pone el hilo actual en estado de espera hasta que otro hilo llame al método notify() o notifyAll() en este objeto (hablaremos de estos métodos más adelante).
-
esperar (tiempo de espera prolongado) coloca el subproceso actual en estado de espera hasta que otro subproceso llame al método notify() o notifyAll() en este objeto o hasta que expire el intervalo de tiempo especificado por el tiempo de espera.
-
wait(long timeout, int nanos) es como el método anterior, pero aquí nanos te permite especificar nanosegundos (un tiempo de espera más preciso).
-
notify() te permite activar un hilo aleatorio esperando en el bloque de sincronización actual. Nuevamente, este método solo se puede llamar en un bloque o método sincronizado (después de todo, en otros lugares no habría nadie a quien despertar).
-
notifyAll() despierta todos los subprocesos que esperan en el monitor actual (también se usa solo en un bloque o método sincronizado ).
110. ¿Cómo paramos un hilo?
Lo primero que hay que decir aquí es que cuando run() se ejecuta hasta su finalización, el hilo finaliza automáticamente. Pero a veces queremos cerrar un hilo antes de lo previsto, antes de que finalice el método. ¿Asi que que hacemos? ¿ Quizás podamos usar el método stop() en el objeto Thread ? ¡No! Ese método está en desuso y puede provocar fallos del sistema. Bueno, ¿entonces qué? Hay dos formas de hacer esto: Primero , use su indicador booleano interno. Veamos un ejemplo. Tenemos nuestra implementación de un hilo que debería mostrar una determinada frase en la pantalla hasta que el hilo 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("The thread is executing some logic...");
}
System.out.println("The thread stopped!");
}
}
public void stopRunningThread() {
isActive = false;
}
}
Llamar al método stopRunningThread() establece el indicador interno en falso, lo que provoca que el método run() finalice. Llamémoslo en principal :
System.out.println("Program starting...");
CustomThread thread = new CustomThread();
thread.start();
Thread.sleep(3);
// As long as our main thread is asleep, our CustomThread runs and prints its message on the console
thread.stopRunningThread();
System.out.println("Program stopping...");
Como resultado, veremos algo como esto en la consola:
public class CustomThread extends Thread {
@Override
public void run() {
{
while (!Thread.interrupted()) {
System.out.println("The thread is executing some logic...");
}
System.out.println("The thread stopped!");
}
}
}
Ejecutando en principal :
System.out.println("Program starting...");
Thread thread = new CustomThread();
thread.start();
Thread.sleep(3);
thread.interrupt();
System.out.println("Program stopping...");
El resultado de ejecutar esto es el mismo que en el primer caso, pero me gusta más este enfoque: escribimos menos código y usamos más funcionalidad estándar ya preparada. Bueno, ¡eso es todo por hoy!
GO TO FULL VERSION