Oi! Mesmo o navio mais rápido simplesmente flutuará sobre as ondas se não tiver um rumo. Se você está lendo este artigo agora, definitivamente tem um objetivo. O principal é não sair do curso e, em vez disso, apostar tudo para se tornar um desenvolvedor Java. Hoje quero continuar minha revisão de perguntas para desenvolvedores Java para ajudar a preencher algumas de suas lacunas na teoria. Explorando perguntas e respostas de uma entrevista de emprego para um cargo de desenvolvedor Java.  Parte 11 - 1

97. Alguma regra se aplica ao substituir equals()?

Ao substituir o método equals(), você deve cumprir as seguintes regras:
  • reflexividade - para qualquer valor x , x.equals(x) deve sempre retornar verdadeiro (onde x != null ).

  • simetria - para quaisquer valores x e y , x.equals(y) deve retornar true somente se y.equals(x) retornar true .

  • transitividade — para quaisquer valores x , y e z , se x.equals(y) retornar true e y.equals(z) também retornar true , então x.equals(z) deverá retornar true .

  • consistência — para quaisquer valores x e y , chamar repetidamente x.equals(y) sempre retornará o mesmo valor, desde que os campos usados ​​para comparar os dois objetos não tenham mudado entre cada chamada.

  • comparação nula - para qualquer valor x , chamar x.equals(null) deve retornar false .

98. O que acontece se você não substituir equals() e hashCode()?

Neste caso, hashCode() retornará um número gerado com base no endereço da célula de memória onde o objeto está armazenado. Em outras palavras, quando o método hashCode() original é chamado em dois objetos com exatamente os mesmos campos, o resultado será diferente (porque eles são armazenados em locais de memória diferentes). O método equals() original compara referências, ou seja, indica se as referências apontam para o mesmo objeto. Em outras palavras, a comparação utiliza o operador == e sempre retornará falso para objetos diferentes, mesmo quando seus campos forem idênticos. true é retornado apenas ao comparar referências ao mesmo objeto. Às vezes faz sentido não substituir esses métodos. Por exemplo, você deseja que todos os objetos de uma determinada classe sejam únicos – substituir esses métodos só poderia prejudicar a garantia existente de códigos hash exclusivos. O importante é compreender as nuances desses métodos, sejam eles substituídos ou não, e usar a abordagem que a situação exigir.

99. Por que o requisito de simetria é satisfeito apenas se x.equals(y) retornar verdadeiro?

Esta pergunta é um pouco estranha. Se o objeto A é igual ao objeto B, então o objeto B é igual ao objeto A. Se B não é igual ao objeto A, então como o inverso poderia ser possível? Isso é bom senso.

100. O que é uma colisão HashCode? Como você lida com isso?

Uma colisão HashCode ocorre quando dois objetos diferentes possuem o mesmo HashCode . Como isso é possível? Bem, o código hash é mapeado para um número inteiro, que varia de -2147483648 a 2147483647. Ou seja, pode ser um entre aproximadamente 4 bilhões de números inteiros diferentes. Essa faixa é enorme, mas não infinita. Isso significa que há situações em que dois objetos completamente diferentes podem ter o mesmo código hash. É altamente improvável, mas é possível. Uma função hash mal implementada pode tornar códigos hash idênticos mais frequentes, retornando números em um intervalo pequeno, aumentando assim a chance de colisões. Para reduzir colisões, você precisa ter uma boa implementação do método HashCode que distribua uniformemente os valores e minimize a chance de valores repetidos.

101. O que acontece se o valor de um elemento participante do contrato hashCode mudar?

Se um elemento envolvido no cálculo de um código hash for alterado, o código hash do objeto deverá mudar (se a função hash for boa). É por isso que você deve usar objetos imutáveis ​​como chaves em um HashMap , já que seu estado interno (campos) não pode ser alterado após a criação. E segue-se que seu código hash muda após a criação. Se você usar um objeto mutável como chave, quando os campos do objeto mudarem, seu código hash mudará e você poderá perder o par chave-valor correspondente no HashMap . Afinal, ele será armazenado no bucket associado ao código hash original, mas depois que o objeto for alterado, você irá procurá-lo em um bucket diferente.

102. Escreva os métodos equals() e hashCode() para uma classe Student que possui campos String name e int age.

public class Student {
int age;
String name;

 @Override
 public boolean equals(final Object o) {
   if (this == o) {
     return true;
   }
   if (o == null || this.getClass() != o.getClass()) {
     return false;
   }

   final Student student = (Student) o;

   if (this.age != student.age) {
     return false;
   }
   return this.name != null ? this.name.equals(student.name) : student.name == null;
 }

 @Override
 public int hashCode() {
   int result = this.age;
   result = 31 * result + (this.name != null ? this.name.hashCode() : 0);
   return result;
 }
}
é igual a():
  • Primeiro, comparamos as referências diretamente, porque se as referências apontam para o mesmo objeto, de que adianta continuar verificando a igualdade? Já sabemos que o resultado será verdadeiro .

  • Verificamos se há null e se os tipos de classe são iguais porque se o parâmetro for null ou de outro tipo, então os objetos não podem ser iguais e o resultado deve ser false .

  • Convertemos o parâmetro para o mesmo tipo (afinal, e se for um objeto do tipo pai).

  • Comparamos os campos primitivos (uma comparação usando =! será suficiente). Se não forem iguais, retornamos false .

  • Verificamos o campo não primitivo para ver se ele é nulo e usamos o método equals (a classe String substitui o método, portanto realizará a comparação corretamente). Se ambos os campos forem nulos ou equals retornar true , paramos a verificação e o método retorna true .

hashCode() :
  • Definimos o valor inicial do código hash igual ao valor do campo de idade do objeto .

  • Multiplicamos o código hash atual por 31 (para uma maior dispersão de valores) e depois adicionamos o código hash do campo String não primitivo (se não for nulo).

  • Devolvemos o resultado.

  • Substituir o método dessa forma significa que objetos com o mesmo nome e valores int sempre retornarão o mesmo código hash.

103. Qual é a diferença entre usar "if (obj instanceof Student)" e "if (getClass() == obj.getClass())"?

Vamos dar uma olhada no que cada expressão faz:
  • instanceof verifica se a referência do objeto no lado esquerdo é uma instância do tipo no lado direito ou um de seus subtipos.

  • "getClass() == ..." verifica se os tipos são iguais.

Em outras palavras, getClass() retorna a identidade específica da classe, mas instanceof retorna true mesmo que o objeto seja apenas um subtipo, o que pode nos dar mais flexibilidade ao usar o polimorfismo. Ambas as abordagens são promissoras se você entender exatamente como funcionam e aplicá-las nos lugares certos.

104. Dê uma breve descrição do método clone().

O método clone() pertence à classe Object . Sua finalidade é criar e retornar um clone (cópia) do objeto atual. Explorando perguntas e respostas de uma entrevista de emprego para um cargo de desenvolvedor Java.  Parte 11 - 2Para usar este método, você precisa implementar a interface do marcador Cloneable :
Student implements Cloneable
E substitua o próprio método clone() :
@Override
protected Object clone() throws CloneNotSupportedException {
 return super.clone();
}
Afinal, ele está protegido na classe Object , ou seja, só ficará visível dentro da classe Student e não será visível para classes externas.

105. Que considerações especiais você precisa ter em mente em relação ao método clone() e às variáveis ​​de referência em um objeto?

Quando os objetos são clonados, apenas os valores primitivos e o valor das referências do objeto são copiados. Isso significa que se um objeto tiver um campo que faça referência a outro objeto, então apenas a referência será clonada — esse outro objeto referenciado não será clonado. Isso é chamado de cópia superficial. Então, e se você precisar de uma cópia completa, onde cada objeto aninhado é clonado? Como você pode ter certeza de que não são meras cópias de referências, mas sim cópias completas de objetos distintos que ocupam endereços de memória distintos no heap? Na verdade, é tudo muito simples — para cada classe referenciada internamente, você precisa substituir o método clone() e adicionar a interface do marcador Cloneable . Depois de fazer isso, a operação de clonagem não copiará referências a objetos existentes, mas sim copiará os objetos referenciados, pois agora eles também têm a capacidade de copiar a si mesmos.

Exceções

106. Qual é a diferença entre um erro e uma exceção?

As exceções, assim como os erros, são subclasses de Throwable . No entanto, eles têm suas diferenças. O erro indica um problema que ocorre principalmente devido à falta de recursos do sistema. E nosso aplicativo não deve ver esses tipos de problemas. Exemplos desses erros incluem falha do sistema e erro de falta de memória. Os erros ocorrem principalmente em tempo de execução, pois não são verificados. Explorando perguntas e respostas de uma entrevista de emprego para um cargo de desenvolvedor Java.  Parte 11 - 3As exceções são problemas que podem ocorrer em tempo de execução e em tempo de compilação. Esses problemas geralmente surgem no código que escrevemos como desenvolvedores. Isso significa que estas exceções são mais previsíveis e mais dependentes de nós. Por outro lado, os erros são mais aleatórios e mais independentes de nós. Em vez disso, eles dependem de problemas no sistema em que nosso aplicativo está sendo executado.

107. Qual é a diferença entre verificado, não verificado, exceção, lançamento e lançamento?

Como eu disse anteriormente, uma exceção é um erro de tempo de execução ou de compilação que ocorre no código escrito pelo desenvolvedor (devido a alguma situação anormal). Verificado é o que chamamos de exceções que um método deve sempre tratar usando o mecanismo try-catch ou relançar para o método de chamada. A palavra-chave throws é usada no cabeçalho de um método para indicar as exceções que o método pode lançar. Em outras palavras, nos fornece um mecanismo para lançar exceções ao método de chamada. Exceções não verificadas não precisam ser tratadas. Eles tendem a ser menos previsíveis e menos prováveis. Dito isto, você pode lidar com eles se quiser. Usamos throw ao lançar manualmente uma exceção, por exemplo:
throw new Exception();

108. Qual é a hierarquia de exceções?

A hierarquia de exceções é muito extensa. Há muito para descrever adequadamente aqui. Então, em vez disso, consideraremos apenas seus ramos principais: Explorando perguntas e respostas de uma entrevista de emprego para um cargo de desenvolvedor Java.  Parte 11 - 4 Aqui, no topo da hierarquia, vemos a classe Throwable , que é o ancestral geral da hierarquia de exceções e, por sua vez, se divide em:
  • Erros — problemas críticos e não verificados.
  • Exceções — exceções que podem ser verificadas.
As exceções são divididas em várias exceções de tempo de execução não verificadas e várias exceções verificadas.

109. O que são exceções verificadas e não verificadas?

Como eu disse antes:
  • Exceções verificadas são exceções que você deve tratar de alguma forma. Ou seja, você deve manipulá-los em um bloco try-catch ou lançá-los para o método acima. Para fazer isso, após listar os argumentos do método na assinatura do método, use throws <tipo de exceção> para indicar que o método pode lançar aquela exceção. Isso é como um aviso, informando ao método chamador que ele deve assumir a responsabilidade de lidar com essa exceção.

  • Exceções não verificadas não precisam ser tratadas, pois não são verificadas em tempo de compilação e geralmente são mais imprevisíveis. Sua principal diferença com as exceções verificadas é que manipulá-las usando um bloco try-catch ou relançando é opcional e não obrigatório.

101. Escreva um exemplo onde você usa um bloco try-catch para capturar e tratar uma exceção.

try{                                                 // Start of the try-catch block
 throw new Exception();                             // Manually throw an exception
} catch (Exception e) {                              // Exceptions of this type and its subtypes will be caught
 System.out.println("Oops! Something went wrong =("); // Display the exception
}

102. Escreva um exemplo onde você captura e trata suas próprias exceções personalizadas.

Primeiro, vamos escrever nossa própria classe de exceção que herda Exception e sobrescreve seu construtor que recebe uma mensagem de erro como argumento:
public class CustomException extends Exception {

 public CustomException(final String message) {
   super(message);
 }
}
A seguir, lançaremos um manualmente e o pegaremos, assim como fizemos no exemplo da pergunta anterior:
try{
 throw new CustomException("Oops! Something went wrong =(");
} catch (CustomException e) {
 System.out.println(e.getMessage());
}
Mais uma vez, quando executamos nosso código, obtemos a seguinte saída:
Ops! Algo deu errado =(
Explorando perguntas e respostas de uma entrevista de emprego para um cargo de desenvolvedor Java.  Parte 11 - 5Bem, isso é tudo por hoje! Vejo você na próxima parte!