CodeGym /Blogue Java /Random-PT /Explorando perguntas e respostas de uma entrevista de emp...
John Squirrels
Nível 41
San Francisco

Explorando perguntas e respostas de uma entrevista de emprego para um cargo de desenvolvedor Java. Parte 12

Publicado no grupo Random-PT
Oi! Conhecimento é poder. Quanto mais conhecimento você tiver na primeira entrevista, mais confiante você se sentirá. Explorando perguntas e respostas de uma entrevista de emprego para um cargo de desenvolvedor Java.  Parte 12 - 1Se você trouxer um cérebro grande e cheio de conhecimento, seu entrevistador achará difícil confundi-lo e provavelmente ficará agradavelmente surpreso. Então, sem mais delongas, hoje continuaremos fortalecendo sua base teórica revisando questões para um desenvolvedor Java.

103. Que regras se aplicam à verificação de exceções durante a herança?

Se bem entendi a pergunta, eles estão perguntando sobre regras para trabalhar com exceções durante a herança. As regras relevantes são as seguintes:
  • Um método substituído ou implementado em um descendente/implementação não pode lançar exceções verificadas que sejam superiores na hierarquia do que exceções em um método de superclasse/interface.
Por exemplo, suponha que temos alguma interface Animal com um método que lança uma IOException :
public interface Animal {
   void speak() throws IOException;
}
Ao implementar esta interface, não podemos expor uma exceção lançável mais geral (por exemplo, Exception , Throwable ), mas podemos substituir a exceção existente por uma subclasse, como FileNotFoundException :
public class Cat implements Animal {
   @Override
   public void speak() throws FileNotFoundException {
// Some implementation
   }
}
  • A cláusula throws do construtor da subclasse deve incluir todas as classes de exceção lançadas pelo construtor da superclasse chamada para criar o objeto.
Suponha que o construtor da classe Animal gere muitas exceções:
public class Animal {
  public Animal() throws ArithmeticException, NullPointerException, IOException {
  }
Então um construtor de subclasse também deve lançá-los:
public class Cat extends Animal {
   public Cat() throws ArithmeticException, NullPointerException, IOException {
       super();
   }
Ou, como acontece com os métodos, você pode especificar exceções diferentes e mais gerais. No nosso caso, podemos indicar Exception , pois é mais geral e é ancestral comum de todas as três exceções indicadas na superclasse:
public class Cat extends Animal {
   public Cat() throws Exception {
       super();
   }

104. Você pode escrever algum código onde o bloco final não seja executado?

Primeiro, vamos lembrar o que finalmente é. Anteriormente, examinamos o mecanismo de captura de exceções: um bloco try designa onde as exceções serão capturadas e os blocos catch são o código que será invocado quando uma exceção correspondente for capturada. Um terceiro bloco de código marcado pela palavra-chave finalmente pode substituir ou vir depois dos blocos catch. A ideia por trás deste bloco é que seu código seja sempre executado independentemente do que aconteça em um bloco try ou catch (independentemente de haver uma exceção ou não). As instâncias em que este bloco não é executado são pouco frequentes e anormais. O exemplo mais simples é quando System.exit(0) é chamado antes do bloco finalmente, encerrando assim o programa:
try {
   throw new IOException();
} catch (IOException e) {
   System.exit(0);
} finally {
   System.out.println("This message will not be printed on the console");
}
Existem também outras situações em que o bloco final não será executado:
  • Por exemplo, um encerramento anormal do programa causado por erros críticos do sistema ou algum erro que faz com que o aplicativo trave (por exemplo, o StackOverflowError , que ocorre quando a pilha do aplicativo está sobrecarregada).

  • Outra situação é quando um thread daemon entra em um bloco try-finally , mas então o thread principal do programa termina. Afinal, os threads daemon são para trabalho em segundo plano que não é de alta prioridade ou obrigatório, portanto o aplicativo não esperará que eles terminem.

  • O exemplo mais estúpido é um loop infinito dentro de um bloco try ou catch — uma vez dentro, um thread ficará preso lá para sempre:

    try {
       while (true) {
       }
    } finally {
       System.out.println("This message will not be printed on the console");
    }
Essa pergunta é muito popular em entrevistas com desenvolvedores juniores, por isso é uma boa ideia lembrar algumas dessas situações excepcionais. Explorando perguntas e respostas de uma entrevista de emprego para um cargo de desenvolvedor Java.  Parte 12 - 2

105. Escreva um exemplo onde você lida com múltiplas exceções em um único bloco catch.

1) Não tenho certeza se esta pergunta foi feita corretamente. Pelo que entendi, esta questão se refere a vários blocos catch e a um único try :
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);
}
Se uma exceção for lançada em um bloco try , os blocos catch associados tentarão capturá-la, sequencialmente, de cima para baixo. Uma vez que a exceção corresponda a um dos blocos catch , quaisquer blocos restantes não serão mais capazes de capturá-la e tratá-la. Tudo isso significa que exceções mais restritas são organizadas acima das mais gerais no conjunto de blocos catch . Por exemplo, se nosso primeiro bloco catch capturar a classe Exception , então quaisquer blocos subsequentes não capturarão as exceções verificadas (ou seja, os blocos restantes com subclasses de Exception serão completamente inúteis). 2) Ou talvez a pergunta tenha sido feita corretamente. Nesse caso, poderíamos tratar as exceções da seguinte maneira:
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
   }
Depois de usar catch para capturar uma exceção, tentamos descobrir seu tipo específico usando o operador instanceof , que verifica se um objeto pertence a um determinado tipo. Isso nos permite realizar com segurança uma conversão de tipo de estreitamento, sem medo de consequências negativas. Poderíamos aplicar qualquer abordagem na mesma situação. Expressei dúvidas sobre a questão apenas porque não consideraria a segunda opção uma boa abordagem. Na minha experiência, nunca encontrei isso, e a primeira abordagem envolvendo vários blocos catch é generalizada.

106. Qual operador permite forçar o lançamento de uma exceção? Escreva um exemplo

Já usei diversas vezes nos exemplos acima, mas vou repetir mais uma vez: a palavra-chave throw . Exemplo de lançamento manual de uma exceção:
throw new NullPointerException();

107. O método principal pode lançar uma exceção? Se sim, então para onde vai?

Em primeiro lugar, quero observar que o método principal nada mais é do que um método comum. Sim, é chamado pela máquina virtual para iniciar a execução de um programa, mas além disso pode ser chamado a partir de qualquer outro código. Isso significa que também está sujeito às regras usuais sobre a indicação de exceções verificadas após a palavra-chave throws :
public static void main(String[] args) throws IOException {
Conseqüentemente, pode lançar exceções. Quando main é chamado como o ponto inicial do programa (e não por algum outro método), qualquer exceção lançada será tratada por UncaughtExceptionHandler . Cada thread possui um desses manipuladores (ou seja, existe um desses manipuladores em cada thread). Se necessário, você pode criar seu próprio manipulador e configurá-lo chamando o método public static void main(String[] args) throws IOException {setDefaultUncaughtExceptionHandler em um public static void main(String[] args) throws IOException {Thread object.

Multithreading

Explorando perguntas e respostas de uma entrevista de emprego para um cargo de desenvolvedor Java.  Parte 12 - 3

108. Que mecanismos para trabalhar em um ambiente multithread você conhece?

Os mecanismos básicos para multithreading em Java são:
  • A palavra-chave sincronizada , que é uma forma de um thread bloquear um método/bloco ao entrar, impedindo a entrada de outros threads.

  • A palavra-chave volátil garante acesso consistente a uma variável acessada por diferentes threads. Ou seja, quando este modificador é aplicado a uma variável, todas as operações para atribuir e ler essa variável tornam-se atômicas. Em outras palavras, os threads não copiarão a variável para sua memória local e a alterarão. Eles mudarão seu valor original.

  • Executável — Podemos implementar esta interface (que consiste em um único método run() ) em alguma classe:

    public class CustomRunnable implements Runnable {
       @Override
       public void run() {
           // Some logic
       }
    }

    E uma vez criado um objeto dessa classe, podemos iniciar um novo thread passando nosso objeto para o construtor Thread e então chamando o método start() :

    Runnable runnable = new CustomRunnable();
    new Thread(runnable).start();

    O método start executa o método run() implementado em um thread separado.

  • Thread — Podemos herdar esta classe e substituir seu método run :

    public class CustomThread extends Thread {
       @Override
       public void run() {
           // Some logic
       }
    }

    Podemos iniciar um novo thread criando um objeto desta classe e então chamando o método start() :

    new CustomThread().start();

  • Simultaneidade — Este é um pacote de ferramentas para trabalhar em um ambiente multithread.

    Isso consiste de:

    • Coleções Simultâneas — Esta é uma coleção de coleções criadas explicitamente para trabalhar em um ambiente multithread.

    • Filas — Filas especializadas para um ambiente multithread (bloqueante e não bloqueador).

    • Sincronizadores — São utilitários especializados para trabalhar em um ambiente multithread.

    • Executores — Mecanismos para criação de pools de threads.

    • Locks — Mecanismos de sincronização de threads mais flexíveis que os padrão (sincronizado, aguardar, notificar, notificarAll).

    • Atomics — Classes otimizadas para multithreading. Cada uma de suas operações é atômica.

109. Conte-nos sobre a sincronização entre threads. Para que servem os métodos wait(), notify(), notifyAll() e join()?

A sincronização entre threads envolve a palavra-chave sincronizada . Este modificador pode ser colocado diretamente no bloco:
synchronized (Main.class) {
   // Some logic
}
Ou diretamente na assinatura do método:
public synchronized void move() {
   // Some logic }
Como eu disse anteriormente, sincronizado é um mecanismo para bloquear um bloco/método para outros threads assim que um thread entra. Vamos pensar em um bloco/método de código como uma sala. Algum fio se aproxima da sala, entra e tranca a porta com a chave. Quando outros threads se aproximam da sala, eles veem que a porta está trancada e esperam nas proximidades até que a sala fique disponível. Assim que o primeiro thread termina seus negócios na sala, ele destranca a porta, sai da sala e libera a chave. Mencionei uma chave algumas vezes por uma razão – porque algo análogo realmente existe. Este é um objeto especial que possui um estado ocupado/livre. Todo objeto em Java possui tal objeto, então quando usamos o bloco sincronizado , precisamos usar parênteses para indicar o objeto cujo mutex será bloqueado:
Cat cat = new Cat();
synchronized (cat) {
   // Some logic
}
Também podemos usar um mutex associado a uma classe, como fiz no primeiro exemplo ( Main.class ). Afinal, quando usamos sincronizado em um método, não especificamos o objeto que queremos bloquear, certo? Neste caso, para métodos não estáticos, o mutex que será bloqueado é o objeto this , ou seja, o objeto atual da classe. Para métodos estáticos, o mutex associado à classe atual ( this.getClass(); ) está bloqueado. wait() é um método que libera o mutex e coloca o thread atual no estado de espera, como se estivesse conectado ao monitor atual (algo como uma âncora). Por causa disso, este método só pode ser chamado a partir de um bloco ou método sincronizado . Caso contrário, o que estaria esperando e o que seria liberado?). Observe também que este é um método da classe Object . Bem, não um, mas três:
  • wait() coloca o thread atual no estado de espera até que outro thread chame o método notify() ou notifyAll() neste objeto (falaremos sobre esses métodos mais tarde).

  • wait(long timeout) coloca o thread atual no estado de espera até que outro thread chame o método notify() ou notifyAll() neste objeto ou o intervalo de tempo especificado por timeout expire.

  • wait(long timeout, int nanos) é como o método anterior, mas aqui nanos permite especificar nanossegundos (um tempo limite mais preciso).

  • notify() permite ativar um thread aleatório aguardando no bloco de sincronização atual. Novamente, esse método só pode ser chamado em um bloco ou método sincronizado (afinal, em outros lugares não haveria ninguém para acordar).

  • notifyAll() desperta todos os threads que aguardam no monitor atual (também usado apenas em um bloco ou método sincronizado ).

110. Como interrompemos um tópico?

A primeira coisa a dizer aqui é que quando run() é executado até o fim, o thread termina automaticamente. Mas às vezes queremos encerrar um thread antes do previsto, antes que o método seja concluído. Então, o que fazemos? Talvez possamos usar o método stop() no objeto Thread ? Não! Esse método está obsoleto e pode causar falhas no sistema. Explorando perguntas e respostas de uma entrevista de emprego para um cargo de desenvolvedor Java.  Parte 12 - 4Bem, e então? Existem duas maneiras de fazer isso: Primeiro , use seu sinalizador booleano interno. Vejamos um exemplo. Temos nossa implementação de um thread que deve exibir uma determinada frase na tela até que o thread pare completamente:
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;
   }
}
Chamar o método stopRunningThread() define o sinalizador interno como falso, fazendo com que o método run() seja encerrado. Vamos chamá-lo em main :
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 assim no console:
Programa iniciando... O thread está executando alguma lógica... O thread está executando alguma lógica... O thread está executando alguma lógica... O thread está executando alguma lógica... O thread está executando alguma lógica... A thread está executando alguma lógica... Programa parando... A thread parou!
Isso significa que nosso thread foi iniciado, imprimiu várias mensagens no console e depois foi interrompido com sucesso. Observe que o número de mensagens exibidas varia de lançamento para lançamento. E às vezes o thread auxiliar pode não exibir absolutamente nada. O comportamento específico depende de quanto tempo o thread principal fica suspenso. Quanto mais tempo ele dorme, menos provável é que o thread auxiliar não consiga exibir nada. Com um tempo de suspensão de 1 ms, você quase nunca verá as mensagens. Mas se você definir para 20 ms, as mensagens quase sempre serão exibidas. Quando o tempo de suspensão é curto, o thread simplesmente não tem tempo para iniciar e fazer seu trabalho. Em vez disso, ele é interrompido imediatamente. Uma segunda maneira é usar o método interrompido() no objeto Thread . Ele retorna o valor do sinalizador interno interrompido, que é falso por padrão. Ou seu método interrupt() , que define esse sinalizador como true (quando o sinalizador for true , o thread deve parar de ser executado). Vejamos um exemplo:
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!");
       }
   }
}
Executando em principal :
System.out.println("Program starting...");
Thread thread = new CustomThread();
thread.start();
Thread.sleep(3);
thread.interrupt();
System.out.println("Program stopping...");
O resultado da execução disso é o mesmo do primeiro caso, mas gosto mais dessa abordagem: escrevemos menos código e usamos mais funcionalidades padrão prontas. Bom, é isso por hoje!
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION