JavaRush /Blogue Java /Random-PT /Análise de perguntas e respostas de entrevistas para dese...

Análise de perguntas e respostas de entrevistas para desenvolvedor Java. Parte 12

Publicado no grupo Random-PT
Olá! Conhecimento é poder. Quanto mais conhecimento você tiver antes da primeira entrevista, mais confiante se sentirá. Análise de perguntas e respostas de entrevistas para desenvolvedor Java.  Parte 12 - 1Com um bom conhecimento, será difícil confundir e, ao mesmo tempo, poderá surpreender agradavelmente o seu entrevistador. Portanto, hoje, sem mais delongas, continuaremos fortalecendo sua base teórica examinando mais de 250 questões para um desenvolvedor Java . Análise de perguntas e respostas de entrevistas para desenvolvedor Java.  Parte 12 - 2

103. Quais são as regras para verificação de exceções em herança?

Se bem entendi a pergunta, eles estão perguntando sobre as regras para trabalhar com exceções durante a herança, e 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 as exceções no método de superclasse/interface.
Ou seja, se tivermos uma determinada interface Animal com um método que lança IOException :
public  interface Animal {
   void voice() throws IOException;
}
Na implementação desta interface, não podemos lançar uma exceção lançada mais geral (por exemplo, Exception , Throwable ), mas podemos substituí-la por uma exceção descendente, como FileNotFoundException :
public class Cat implements Animal {
   @Override
   public void voice() throws FileNotFoundException {
// некоторая реализация
   }
}
  • O construtor da subclasse deve incluir em seu bloco throws todas as classes de exceção lançadas pelo construtor da superclasse que é chamado quando o objeto é criado.
Suponha que o construtor da classe Animal gere muitas exceções:
public class Animal {
  public Animal() throws ArithmeticException, NullPointerException, IOException {
  }
Então o herdeiro da classe também deve indicá-los no construtor:
public class Cat extends Animal {
   public Cat() throws ArithmeticException, NullPointerException, IOException {
       super();
   }
Ou, como no caso dos métodos, você pode especificar não as mesmas exceções, mas sim exceções mais gerais. No nosso caso, bastará especificar uma exceção mais geral - Exception , pois este é o ancestral comum de todas as três exceções consideradas:
public class Cat extends Animal {
   public Cat() throws Exception {
       super();
   }

104. Você poderia escrever um código para quando o bloco final não será executado?

Primeiro, vamos lembrar o que é finalmente . Anteriormente, vimos o mecanismo para capturar exceções: o bloco try descreve a área de captura, enquanto o (s) bloco(s) catch é o código que funcionará quando uma exceção específica for lançada. Finalmente é o terceiro bloco de código depois de finalmente que é intercambiável com catch , mas não é mutuamente exclusivo. A essência deste bloco é que o código nele sempre funciona, independentemente do resultado do try ou catch (independentemente de uma exceção ter sido lançada ou não). Os casos de sua falha são muito raros e são anormais. O caso mais simples de falha é quando o método System.exit(0) é chamado no código acima , o que encerra o programa (extingue-o):
try {
   throw new IOException();
} catch (IOException e) {
   System.exit(0);
} finally {
   System.out.println("Данное сообщение не будет выведенно в консоль");
}
Existem também algumas outras situações em que finalmente não funcionará:
  • Encerramento anormal do programa causado por problemas críticos do sistema, ou pela queda de algum Error que irá “travar” a aplicação (um exemplo de erro pode ser o mesmo StackOwerflowError que ocorre quando a memória da pilha transborda).
  • Quando o thread deamon passa pelo bloco ry...finalmente e paralelamente a isso o programa termina. Afinal, o thread deamon é um thread para ações em segundo plano, ou seja, não é prioritário e obrigatório, e a aplicação não vai esperar o término de seu trabalho.
  • O loop infinito mais comum, em try ou catch , uma vez no qual o fluxo permanecerá lá para sempre:

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

Essa pergunta é bastante popular em entrevistas para iniciantes, portanto, vale a pena lembrar algumas dessas situações excepcionais. Análise de perguntas e respostas de entrevistas para desenvolvedor Java.  Parte 12 - 3

105. Escreva um exemplo de tratamento de múltiplas exceções em um bloco catch

1) Talvez a pergunta tenha sido feita incorretamente. Pelo que entendi, esta questão refere-se a múltiplas capturas para um bloco try :
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);
}
Se uma exceção ocorrer em um bloco try , os blocos catch tentarão alternadamente capturá-la de cima para baixo. Se um determinado bloco catch for bem-sucedido, ele terá o direito de lidar com a exceção, enquanto o restante dos blocos abaixo não será mais capazes de tentar capturá-lo e processá-lo à sua maneira. Portanto, exceções mais restritas são colocadas em uma posição superior na cadeia de blocos de captura e exceções mais amplas são colocadas em uma posição inferior. Por exemplo, se uma exceção da classe Exception for capturada em nosso primeiro bloco catch , as exceções verificadas não poderão entrar nos blocos restantes (os blocos restantes com descendentes de Exception serão absolutamente inúteis). 2) A pergunta foi feita corretamente, neste caso nosso processamento ficará assim:
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
   }
Tendo capturado uma exceção através de catch , tentamos descobrir seu tipo específico através do método instanceof , que é usado para verificar se um objeto pertence a um determinado tipo, para que posteriormente possamos restringi-lo a este tipo sem consequências negativas. Ambas as abordagens consideradas podem ser usadas na mesma situação, mas eu disse que a pergunta está incorreta porque não chamaria a segunda opção de boa e nunca a vi em minha prática, enquanto ao mesmo tempo o primeiro método com multicatches recebeu ampla distribuição atenção. espalhando. Análise de perguntas e respostas de entrevistas para desenvolvedor Java.  Parte 12 - 4

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

Já usei várias vezes acima, mas mesmo assim repetirei esta palavra-chave - throw . Exemplo de uso (forçando uma exceção):
throw new NullPointerException();

107. O método principal pode lançar uma exceção de lançamento? Se sim, para onde será transferido?

Primeiramente quero ressaltar que main nada mais é do que um método regular, e sim, ele é chamado pela máquina virtual para iniciar a execução do programa, mas além disso, pode ser chamado a partir de qualquer outro código. Ou seja, também está sujeito às regras usuais de especificação de exceções verificadas após lançamentos :
public static void main(String[] args) throws IOException {
Assim, exceções também podem ocorrer nele. Se main não foi chamado em algum método, mas foi iniciado como um ponto de inicialização do programa, então a exceção lançada por ele será tratada pelo interceptor .UncaughtExceptionHandler . Esse manipulador é um por thread (ou seja, um manipulador em cada thread). Se necessário, você pode criar seu próprio manipulador e configurá-lo usando o método setDefaultUncaughtExceptionHandler chamado no objeto Thread .

Multithreading

Análise de perguntas e respostas de entrevistas para desenvolvedor Java.  Parte 12 - 5

108. Quais ferramentas você conhece para trabalhar com multithreading?

Ferramentas básicas/básicas para usar multithreading em Java:
  • Sincronizado é um mecanismo para fechar (bloquear) um método/bloco quando um thread entra nele, de outros threads.
  • Volátil é um mecanismo que garante acesso consistente a uma variável por diferentes threads, ou seja, com a presença deste modificador em uma variável, todas as operações de atribuição e leitura devem ser atômicas. Em outras palavras, os threads não copiarão esta variável para sua memória local e a alterarão, mas alterarão seu valor original.
Leia mais sobre volátil aqui .
  • Runnable é uma interface que pode ser implementada (em particular, seu método run) em uma determinada classe:
public class CustomRunnable implements Runnable {
   @Override
   public void run() {
       // некоторая логика
   }
}
E tendo criado um objeto desta classe, você pode iniciar um novo thread definindo este objeto no construtor do novo objeto Thread e chamando seu 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 é uma classe, herdada da qual (enquanto substitui o método run ):
public class CustomThread extends Thread {
   @Override
   public void run() {
       // некоторая логика
   }
}
E ao criar um objeto desta classe e iniciá-lo usando o método start() , iniciaremos assim um novo thread:
new CustomThread().start();
  • Simultaneidade é um pacote com ferramentas para trabalhar em um ambiente multithread.
Isso consiste de:
  • Coleções Simultâneas - um conjunto de coleções especializadas 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 são mecanismos para criar pools de threads.
  • Bloqueios - mecanismos de sincronização de threads (mais flexíveis que os padrão - sincronizados, aguardados, notificados, notificados).
  • Atomics são classes otimizadas para execução multithread; cada operação é atômica.
Leia mais sobre o pacote simultâneo aqui .

109. Fale sobre sincronização entre threads. Para que são usados ​​os métodos wait(), notify() - notifyAll() join()?

Pelo que entendi a pergunta, a sincronização entre threads tem a ver com o modificador de chave -synchronous . Este modificador pode ser colocado diretamente próximo ao bloco:
synchronized (Main.class) {
   // некоторая логика
}
Ou diretamente na assinatura do método:
public synchronized void move() {
   // некоторая логика}
Como eu disse anteriormente, sincronizado é um mecanismo que permite fechar um bloco/método de outras threads quando uma thread já entrou nele. Pense em um bloco/método como uma sala. Algum riacho, chegando até ele, entrará nele e o trancará, outros riachos, chegando à sala e vendo que está fechado, esperarão perto dele até que esteja livre. Feito o seu trabalho, o primeiro fio sai da sala e libera a chave. E não foi à toa que falei constantemente sobre a chave, porque ela existe mesmo. Este é um objeto especial que possui um estado ocupado/livre. Este objeto é anexado a todo objeto Java, portanto, ao usar um bloco sincronizado , precisamos indicar entre parênteses o objeto cujo mutex queremos fechar a porta:
Cat cat = new Cat();
synchronized (cat) {
   // некоторая логика
}
Você também pode usar um mutex de classe, como fiz no primeiro exemplo ( Main.class ). Quando usamos sincronizado em um método, não especificamos o objeto que queremos fechar, certo? Neste caso, para um método não estático, ele fechará no mutex do this object , ou seja, o objeto atual desta classe. O estático fechará no mutex da classe atual ( this.getClass(); ). Você pode ler mais sobre mutex aqui . Bem, leia sobre sincronizado aqui . Wait() é um método que libera o mutex e coloca o thread atual em modo 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 deveria ser liberado e o que deveria ser esperado). Observe também que este é um método da classe Object . Mais precisamente, não um, mas até três:
  • Wait() - coloca o thread atual em modo de espera até que outro thread chame o método notify() ou notifyAll() para este objeto (falaremos sobre esses métodos mais tarde).

  • Espera (tempo limite longo) - coloca o thread atual no modo de espera até que outro thread chame o método notify() ou notifyAll() neste objeto ou o tempo limite especificado expire .

  • Espera (tempo limite longo, int nanos) - semelhante ao anterior, apenas nanos permite especificar nanossegundos (configuração de tempo mais precisa).

  • Notify() é um método que permite ativar um thread aleatório do bloco de sincronização atual. Novamente, ele só poderá ser chamado em um bloco ou método sincronizado (afinal, em outros locais não terá ninguém para descongelar).

  • NotifyAll() é um método que ativa todos os threads em espera no monitor atual (também usado apenas em um bloco ou método sincronizado ).

110. Como parar o fluxo?

A primeira coisa a dizer é que quando o método run() é totalmente executado , o thread é automaticamente destruído. Mas às vezes você precisa matá-lo antes do previsto, antes que esse método seja concluído. Então, o que devemos fazer? Talvez o objeto Thread deva ter um método stop() ? Não importa como seja! Este método é considerado desatualizado e pode causar falhas no sistema. Análise de perguntas e respostas de entrevistas para desenvolvedor Java.  Parte 12 - 6Bem, e então? Existem duas maneiras de fazer isso: A primeira é usar seu sinalizador booleano interno. Vejamos um exemplo. Temos nossa própria implementação de thread que deve exibir uma determinada frase na tela até que ela pare completamente:
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;
   }
}
Ao usar o método stopRunning() , o sinalizador interno se torna falso e o método run para de ser executado. Vamos executá-lo em main :
System.out.println("Начало выполнения программы");
CustomThread thread = new CustomThread();
thread.start();
Thread.sleep(3);
// пока наш основной поток спит, вспомогательный  CustomThread работает и выводит в коноль своё сообщение
thread.stopRunningThread();
System.out.println("Конец выполнения программы");
Como resultado, veremos algo assim no console:
Início da execução do programa 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... O thread está executando alguma lógica... Fim da execução do programa A thread está parada!
Isso significa que nosso thread funcionou, gerou um certo número de mensagens no console e foi interrompido com sucesso. Observo que o número de mensagens emitidas varia de execução para execução; às vezes, o thread adicional nem produzia nada. Como percebi, isso depende do tempo de suspensão do thread principal; quanto mais tempo, menor a chance de o thread adicional não produzir nada. Com um tempo de suspensão de 1 ms, as mensagens quase nunca são enviadas, mas se você definir para 20 ms, quase sempre funciona. Talvez, quando o tempo é curto, o thread simplesmente não tenha tempo de iniciar e iniciar seu trabalho e seja imediatamente interrompido. A segunda maneira é usar o método interrompido() no objeto Thread , que retorna o valor do sinalizador de interrupção interno (este sinalizador é falso por padrão ) e seu outro método interrupt() , que define este sinalizador como verdadeiro (quando este flag é verdadeiro , o thread deve interromper seu trabalho). Vejamos um exemplo:
public class CustomThread extends Thread {

   @Override
   public void run() {
       {
           while (!Thread.interrupted()) {
               System.out.println("Поток выполняет некую логику...");
           }
           System.out.println("Поток остановлен!");
       }
   }
}
Execute em principal :
System.out.println("Начало выполнения программы");
Thread thread = new CustomThread();
thread.start();
Thread.sleep(3);
thread.interrupt();
System.out.println("Конец выполнения программы");
O resultado da execução será o mesmo do primeiro caso, mas gosto mais dessa abordagem: escrevemos menos código e usamos mais funcionalidades padrão prontas. Análise de perguntas e respostas de entrevistas para desenvolvedor Java.  Parte 12 - 7É aí que vamos parar hoje.Análise de perguntas e respostas de entrevistas para desenvolvedor Java.  Parte 12 - 8
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION