JavaRush /Blogue Java /Random-PT /Núcleo Java. Perguntas para uma entrevista, parte 3
Vadim625
Nível 27

Núcleo Java. Perguntas para uma entrevista, parte 3

Publicado no grupo Random-PT
Nos dois artigos anteriores, discutimos algumas perguntas importantes que são feitas com mais frequência em entrevistas. É hora de seguir em frente e examinar o restante das questões.
Núcleo Java.  Perguntas da entrevista, parte 3 - 1

Cópia profunda e cópia superficial

Uma cópia exata do original é seu clone. Em Java, isso significa ser capaz de criar um objeto com uma estrutura semelhante ao objeto original. O método clone()fornece essa funcionalidade. A cópia superficial copia o mínimo de informação possível. Por padrão, a clonagem em Java é superficial, ou seja, Object classnão sabe sobre a estrutura da classe que está copiando. Ao clonar, a JVM faz o seguinte:
  1. Se uma classe tiver apenas membros de tipos primitivos, uma cópia inteiramente nova do objeto será criada e uma referência a esse objeto será retornada.
  2. Se uma classe contiver não apenas membros de tipos primitivos, mas também qualquer outro tipo de classe, as referências aos objetos dessas classes serão copiadas. Portanto, ambos os objetos terão as mesmas referências.
A cópia profunda duplica tudo. A cópia profunda consiste em duas coleções, uma das quais duplica todos os elementos da coleção original. Queremos fazer uma cópia de forma que alterações em qualquer elemento da cópia não afetem a coleção original. A clonagem profunda requer as seguintes regras:
  1. Não há necessidade de copiar dados primitivos separadamente;
  2. Todas as classes membros da classe original devem suportar clonagem. Para cada membro da classe, deve ser chamado super.clone()quando o método for substituído clone();
  3. Se algum membro de uma classe não suportar clonagem, então no método clone, você precisará criar uma nova instância dessa classe e copiar cada um de seus membros com todos os atributos para um novo objeto de classe, um de cada vez.
Saiba mais sobre clonagem aqui

O que é sincronização? Bloqueio em nível de objeto e bloqueio em nível de classe?

A sincronização refere-se ao multithreading. Um bloco de código sincronizado só pode ser executado por um thread por vez. Java permite processar vários threads simultaneamente. Isso pode resultar em dois ou mais threads querendo acessar o mesmo campo. A sincronização ajuda a evitar erros de memória que ocorrem quando os recursos de memória são usados ​​incorretamente. Quando um método é declarado como sincronizado, o thread mantém seu monitor. Se outro thread tentar acessar um método sincronizado neste momento, o thread será bloqueado e aguardará que o monitor fique livre. A sincronização em Java é realizada com a palavra-chave sincronizada especial . Você pode marcar blocos ou métodos individuais em sua classe desta forma. A palavra-chave sincronizada não pode ser usada em conjunto com variáveis ​​ou atributos de classe. O bloqueio em nível de objeto é um mecanismo quando você deseja sincronizar um método não estático ou um bloco de código não estático para que apenas um thread possa executar o bloco de código em uma determinada instância da classe. Isso sempre deve ser feito para tornar o thread da instância da classe seguro. O bloqueio em nível de classe evita que vários threads entrem em um bloco sincronizado para todas as instâncias disponíveis da classe. Por exemplo, se houver 100 instâncias da classe DemoClass, então apenas 1 thread será capaz de executar demoMethod() usando uma das variáveis ​​em um determinado momento. Isso sempre deve ser feito para garantir a segurança do thread estático. Saiba mais sobre sincronização aqui.

Qual é a diferença entre sleep() e wait()?

Sleep()é um método usado para atrasar o processo por alguns segundos. No caso de wait(), o thread fica em estado de espera até chamarmos o método notify()or notifyAll(). A principal diferença é que wait()ele libera o bloqueio do monitor enquanto sleep()não libera o bloqueio. Wait()usado para aplicativos multithread, sleep()usado simplesmente para pausar a execução do thread. Thread.sleep()coloca o thread atual no estado "Não executável" por um determinado período de tempo. O thread salva o estado do monitor que estava antes desse método ser chamado. Se outro thread chamar t.interrupt(), o thread que “adormeceu” irá acordar. Observe que este sleep()é um método estático, o que significa que sempre afeta o thread atual (aquele que executa o método sleep()). Um erro comum é chamar t.sleep()where testá outro thread; mesmo quando o thread atual que chamou o método sleep()não é tum thread. Object.wait()envia o thread atual para o estado "Não executável" por um tempo, assim como sleep(), mas com algumas nuances. Wait()chamado em um objeto, não em um thread; chamamos esse objeto de “objeto de bloqueio”. Antes de chamar lock.wait(), o thread atual deve ser sincronizado com o “objeto de bloqueio”; wait()depois disso, ele libera esse bloqueio e adiciona o thread à “lista de espera” associada a esse bloqueio. Posteriormente, outro thread pode sincronizar com o mesmo objeto de bloqueio e chamar o arquivo lock.notify(). Este método irá "acordar" o thread original, que ainda está esperando. Em princípio, wait()/ notify()pode ser comparado a sleep()/ interrupt(), apenas o thread ativo não precisa de um ponteiro direto para o thread adormecido, ele só precisa conhecer o objeto de bloqueio compartilhado. Leia a diferença detalhada aqui.

É possível atribuir nulo a isso a uma variável de referência?

Não, você não pode. Em Java, o lado esquerdo do operador de atribuição deve ser uma variável. "This" é uma palavra-chave especial que sempre fornece a instância atual da classe. Não é qualquer variável. Da mesma forma, null não pode ser atribuído a uma variável usando a palavra-chave “super” ou qualquer outra palavra-chave semelhante.

Qual é a diferença entre && e &?

&- bit a bit e &&- logicamente.
  1. &avalia os dois lados da operação;
  2. &&avalia o lado esquerdo da operação. Se for verdade, continua a avaliar o lado direito.
Procure aqui uma compreensão mais profunda.

Como substituir os métodos equals() e hachCode()?

hashCode()e equals()os métodos são definidos na classe Object, que é a classe pai dos objetos Java. Por esse motivo, todos os objetos Java herdam a implementação padrão dos métodos. O método hashCode()é usado para obter um número inteiro exclusivo para um determinado objeto. Este número inteiro é usado para determinar a localização de um objeto quando esse objeto precisa ser armazenado, por exemplo, para HashTable. Por padrão, hashCode()retorna integeruma representação do endereço do local da memória onde o objeto está armazenado. O método equls(), como o próprio nome sugere, é usado simplesmente para testar se dois objetos são iguais. A implementação padrão verifica as referências de objetos para ver se são iguais. Abaixo estão diretrizes importantes para recarregar esses métodos:
  1. Sempre use os mesmos atributos de objeto ao gerar hashCode()e equals();
  2. Simetria. Aqueles. xse retornar verdadeiro para alguns objetos y x.equals(y), deverá y.equals(x)retornar verdadeiro;
  3. Reflexividade. Para qualquer objeto x x.equals(x)deve retornar verdadeiro;
  4. Consistência. Para quaisquer objetos xe y x.equals(y)retorna a mesma coisa se as informações usadas nas comparações não mudarem;
  5. Transitividade. Para quaisquer objetos e x, se retornar verdadeiro e retornar verdadeiro, deverá retornar verdadeiro;yzx.equals(y)y.equals(z)x.equals(z)
  6. Sempre que um método for chamado no mesmo objeto durante a execução da aplicação, ele deverá retornar o mesmo número, a menos que as informações utilizadas sejam alteradas. hashCodepode retornar valores diferentes para objetos idênticos em diferentes instâncias do aplicativo;
  7. Se dois objetos forem iguais, segundo equals, então eles hashCodedeverão retornar os mesmos valores;
  8. O requisito oposto é opcional. Dois objetos desiguais podem retornar o mesmo hashCode. No entanto, para melhorar o desempenho, é melhor que objetos diferentes retornem códigos diferentes.
Leia fatos interessantes sobre esses métodos aqui.

Conte-nos sobre modificadores de acesso

Classes, campos, construtores e métodos Java podem ter um dos quatro modificadores de acesso diferentes: private Se um método ou variável estiver marcado como private , somente o código dentro da mesma classe poderá acessar a variável ou chamar o método. O código dentro das subclasses não pode acessar uma variável ou método, nem pode acessá-lo de qualquer outra classe. O modificador de acesso privado é mais frequentemente usado para construtores, métodos e variáveis. default O modificador de acesso padrão é declarado se o modificador não for especificado. Este modificador significa que o acesso aos campos, construtores e métodos de uma determinada classe pode ser obtido por código dentro da própria classe, código dentro de classes do mesmo pacote. As subclasses não podem acessar métodos e variáveis-membro de uma superclasse se forem declaradas como default , a menos que a subclasse esteja no mesmo pacote que a superclasse. protected O modificador protected funciona da mesma forma que default , exceto que as subclasses também podem acessar métodos e variáveis ​​protegidos da superclasse. Esta afirmação é verdadeira mesmo que a subclasse não esteja no mesmo pacote que a superclasse. public O modificador de acesso público significa que todo o código pode acessar a classe, suas variáveis, construtores ou métodos, independentemente de onde o código esteja localizado. Núcleo Java.  Perguntas para uma entrevista, parte 3 - 2

O que é um coletor de lixo? Podemos ligar para ele?

A coleta de lixo é um recurso de gerenciamento automático de memória em muitas linguagens de programação modernas, como Java e linguagens do NET.Framework. Linguagens que usam coleta de lixo geralmente interpretam a coleta de lixo em uma máquina virtual como a JVM. A coleta de lixo tem dois propósitos: qualquer memória não utilizada deve ser liberada e a memória não deve ser liberada se o programa ainda a utilizar. Você pode executar a coleta de lixo manualmente? Não, System.gc()isso lhe dá o máximo de acesso possível. A melhor opção é chamar o método System.gc(), que indicará ao coletor de lixo que ele precisa ser executado. Não há como executá-lo imediatamente, pois o coletor de lixo não é determinístico. Além disso, de acordo com a documentação, OutOfMemoryErrorele não será encaminhado caso a máquina virtual não consiga liberar memória após uma coleta de lixo completa. Saiba mais sobre o coletor de lixo aqui.

O que significa a palavra-chave nativa? Explique em detalhes

A palavra-chave nativa é usada para indicar que o método é implementado em uma linguagem de programação diferente de um arquivo Java. Métodos nativos foram usados ​​no passado. Nas versões atuais do Java, isso é necessário com menos frequência. Atualmente, os métodos nativos são necessários quando:
  1. Você deve chamar uma biblioteca de Java escrita em outra linguagem.
  2. Você precisa de acesso a recursos do sistema ou de hardware que só podem ser acessados ​​usando outra linguagem (geralmente C). Na verdade, muitas funções do sistema que interagem com o computador real (como discos ou dados de rede) só podem ser chamadas pelo método nativo.
As desvantagens de usar bibliotecas de métodos nativos também são significativas:
  1. JNI/JNA pode desestabilizar a JVM, especialmente se você tentar fazer algo complexo. Se o seu método nativo fizer algo errado, existe a possibilidade de falha da JVM. Além disso, coisas ruins podem acontecer se o seu método nativo for chamado a partir de vários threads. E assim por diante.
  2. É mais difícil depurar um programa com código nativo .
  3. O código nativo requer construção separada de estruturas, o que pode criar problemas na portabilidade para outras plataformas.

O que é serialização?

Na ciência da computação, no contexto de armazenamento e transmissão de dados, a serialização é o processo de traduzir uma estrutura de dados ou o estado de um objeto em um formato que pode ser armazenado e recuperado posteriormente em outro ambiente computacional. Após receber uma série de bits, eles são recalculados de acordo com o formato de serialização, podendo ser utilizados para criar um clone semanticamente idêntico do objeto original. Java fornece serialização automática, que requer que o objeto implemente a interface java.io.Serializable. A implementação da interface marca a classe como "serializável". A interface java.io.Serializable não possui métodos de serialização, mas a classe serializável pode opcionalmente definir métodos que serão chamados como parte do processo de serialização/desserialização. Ao fazer alterações nas classes, você precisa considerar quais serão ou não compatíveis com a serialização. Você pode ler as instruções completas aqui. Darei os pontos mais importantes: Mudanças incompatíveis:
  1. Exclua um campo;
  2. Mover uma classe para cima ou para baixo na hierarquia;
  3. Alterar um campo não estático para estático ou não transitório para transitório;
  4. Alterando o tipo de dados primitivo declarado;
  5. Alterar o método WriteObjectpara ReadObjectque eles não escrevam ou leiam mais campos por padrão;
  6. Mudar de classe Serializablepara Externalizableou vice-versa;
  7. Alterar uma classe enum para uma não enum ou vice-versa;
  8. Removendo Serializableou Externalizable;
  9. Adicionando writeReplaceum readResolvemétodo a uma classe.
Mudanças compatíveis:
  1. Adicionando campos;
  2. Adicionando/removendo classes;
  3. Adicionando métodos WriteObject/ReadObject[métodos defaultReadObjectou defaultWriteObjectdevem ser chamados no início];
  4. Removendo métodos WriteObject/ReadObject;
  5. Adição java.io.Serializable;
  6. Alteração de acesso ao campo;
  7. Alterar um campo estático para não estático ou transitório para não transitório .
Links para partes anteriores: Java Core. Perguntas da entrevista, parte 1 Java Core. Perguntas da entrevista, parte 2 Artigo original Bons estudos!
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION