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.
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étodoclone()
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 class
não sabe sobre a estrutura da classe que está copiando. Ao clonar, a JVM faz o seguinte:
- 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.
- 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.
- Não há necessidade de copiar dados primitivos separadamente;
- 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ídoclone()
; - 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.
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 t
está outro thread; mesmo quando o thread atual que chamou o método sleep()
não é t
um 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.
&
avalia os dois lados da operação;&&
avalia o lado esquerdo da operação. Se for verdade, continua a avaliar o lado direito.
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 integer
uma 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:
- Sempre use os mesmos atributos de objeto ao gerar
hashCode()
eequals()
; - Simetria. Aqueles.
x
se retornar verdadeiro para alguns objetosy
x.equals(y)
, deveráy.equals(x)
retornar verdadeiro; - Reflexividade. Para qualquer objeto
x
x.equals(x)
deve retornar verdadeiro; - Consistência. Para quaisquer objetos
x
ey
x.equals(y)
retorna a mesma coisa se as informações usadas nas comparações não mudarem; - Transitividade. Para quaisquer objetos e
x
, se retornar verdadeiro e retornar verdadeiro, deverá retornar verdadeiro;y
z
x.equals(y)
y.equals(z)
x.equals(z)
- 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.
hashCode
pode retornar valores diferentes para objetos idênticos em diferentes instâncias do aplicativo; - Se dois objetos forem iguais, segundo
equals
, então eleshashCode
deverão retornar os mesmos valores; - 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.
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.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, OutOfMemoryError
ele 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:- Você deve chamar uma biblioteca de Java escrita em outra linguagem.
- 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.
- 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.
- É mais difícil depurar um programa com código nativo .
- 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 interfacejava.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:
- Exclua um campo;
- Mover uma classe para cima ou para baixo na hierarquia;
- Alterar um campo não estático para estático ou não transitório para transitório;
- Alterando o tipo de dados primitivo declarado;
- Alterar o método
WriteObject
paraReadObject
que eles não escrevam ou leiam mais campos por padrão; - Mudar de classe
Serializable
paraExternalizable
ou vice-versa; - Alterar uma classe enum para uma não enum ou vice-versa;
- Removendo
Serializable
ouExternalizable
; - Adicionando
writeReplace
umreadResolve
método a uma classe.
- Adicionando campos;
- Adicionando/removendo classes;
- Adicionando métodos
WriteObject/ReadObject
[métodosdefaultReadObject
oudefaultWriteObject
devem ser chamados no início]; - Removendo métodos
WriteObject/ReadObject
; - Adição
java.io.Serializable
; - Alteração de acesso ao campo;
- Alterar um campo estático para não estático ou transitório para não transitório .
GO TO FULL VERSION