Para quem ouve a palavra Java Core pela primeira vez, esses são os fundamentos fundamentais da linguagem. Com esse conhecimento, você pode ir com segurança para um estágio/estágio.
Essas perguntas o ajudarão a atualizar seus conhecimentos antes da entrevista ou a aprender algo novo por si mesmo. Para adquirir habilidades práticas, estude no JavaRush . Artigo original Links para outras partes: Java Core. Perguntas da entrevista, parte 1 Java Core. Perguntas para uma entrevista, parte 3
Por que o método finalize() deve ser evitado?
Todos conhecemos a afirmação de que um métodofinalize()
é chamado pelo coletor de lixo antes de liberar a memória ocupada por um objeto. Aqui está um exemplo de programa que prova que uma chamada de método finalize()
não é garantida:
public class TryCatchFinallyTest implements Runnable {
private void testMethod() throws InterruptedException
{
try
{
System.out.println("In try block");
throw new NullPointerException();
}
catch(NullPointerException npe)
{
System.out.println("In catch block");
}
finally
{
System.out.println("In finally block");
}
}
@Override
protected void finalize() throws Throwable {
System.out.println("In finalize block");
super.finalize();
}
@Override
public void run() {
try {
testMethod();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class TestMain
{
@SuppressWarnings("deprecation")
public static void main(String[] args) {
for(int i=1;i< =3;i++)
{
new Thread(new TryCatchFinallyTest()).start();
}
}
}
Saída: No bloco try No bloco catch No bloco finalmente No bloco try No bloco catch No bloco finalmente No bloco try No bloco catch No bloco finalmente Surpreendentemente, o método finalize
não foi executado para nenhum thread. Isso prova minhas palavras. Acho que o motivo é que os finalizadores são executados por um thread separado do coletor de lixo. Se a Java Virtual Machine terminar muito cedo, o coletor de lixo não terá tempo suficiente para criar e executar finalizadores. Outras razões para não usar o método finalize()
podem ser:
- O método
finalize()
não funciona com cadeias como construtores. Isso significa que quando você chama um construtor de classe, os construtores da superclasse serão chamados incondicionalmente. Mas no caso do métodofinalize()
isso não acontecerá. O método da superclassefinalize()
deve ser chamado explicitamente. - Qualquer exceção lançada pelo método
finalize
será ignorada pelo encadeamento do coletor de lixo e não será propagada posteriormente, o que significa que o evento não será registrado em seus logs. Isso é muito ruim, não é? -
Você também receberá uma penalidade significativa no desempenho se o método
finalize()
estiver presente em sua classe. Em Effective Programming (2ª ed.), Joshua Bloch disse:
“Sim, e mais uma coisa: há uma grande penalidade de desempenho ao usar finalizadores. Na minha máquina, o tempo para criar e destruir objetos simples é de aproximadamente 5,6 nanossegundos.
Adicionar um finalizador aumenta o tempo para 2.400 nanossegundos. Em outras palavras, é aproximadamente 430 vezes mais lento criar e excluir um objeto com um finalizador.”
Por que o HashMap não deveria ser usado em um ambiente multithread? Isso poderia causar um loop infinito?
Sabemos queHashMap
esta é uma coleção não sincronizada, cuja contraparte sincronizada é HashTable
. Então, quando você está acessando uma coleção e em um ambiente multithread onde todas as threads têm acesso a uma única instância da coleção, então é mais seguro usar HashTable
por motivos óbvios, como evitar leituras sujas e garantir a consistência dos dados. Na pior das hipóteses, esse ambiente multithread causará um loop infinito. Sim, é verdade. HashMap.get()
pode causar um loop infinito. Vamos ver como? Se você olhar o código fonte do método HashMap.get(Object key)
, ele fica assim:
public Object get(Object key) {
Object k = maskNull(key);
int hash = hash(k);
int i = indexFor(hash, table.length);
Entry e = table[i];
while (true) {
if (e == null)
return e;
if (e.hash == hash && eq(k, e.key))
return e.value;
e = e.next;
}
}
while(true)
sempre pode ser vítima de um loop infinito em um ambiente de execução multithread se, por algum motivo, e.next
puder apontar para si mesmo. Isso causará um loop infinito, mas como e.next
ele apontará para si mesmo (ou seja, para e
)? Isso pode acontecer em um método void transfer(Entry[] newTable)
chamado enquanto HashMap
está sendo redimensionado.
do {
Entry next = e.next;
int i = indexFor(e.hash, newCapacity);
e.next = newTable[i];
newTable[i] = e;
e = next;
} while (e != null);
Este trecho de código tende a criar um loop infinito se o redimensionamento ocorrer ao mesmo tempo que outro thread está tentando alterar a instância do mapa ( HashMap
). A única maneira de evitar esse cenário é usar a sincronização no seu código ou, melhor ainda, usar uma coleção sincronizada.
Explique abstração e encapsulamento. Como eles estão conectados?
Em palavras simples , “ A abstração exibe apenas as propriedades de um objeto que são significativas para a visão atual ” . Na teoria da programação orientada a objetos, a abstração envolve a capacidade de definir objetos que representam “atores” abstratos que podem realizar trabalho, alterar e relatar alterações em seu estado e “interagir” com outros objetos no sistema. A abstração em qualquer linguagem de programação funciona de várias maneiras. Isso pode ser percebido a partir da criação de rotinas para definir interfaces para comandos de linguagem de baixo nível. Algumas abstrações tentam limitar a amplitude da representação geral das necessidades de um programador, ocultando completamente as abstrações nas quais são construídas, como os padrões de projeto. Normalmente, a abstração pode ser vista de duas maneiras: A abstração de dados é uma forma de criar tipos de dados complexos e expor apenas operações significativas para interagir com o modelo de dados, ao mesmo tempo que oculta todos os detalhes de implementação do mundo exterior. A abstração de execução é o processo de identificar todas as declarações significativas e expô-las como uma unidade de trabalho. Geralmente usamos esse recurso quando criamos um método para realizar algum trabalho. Confinar dados e métodos dentro de classes em combinação com a ocultação (usando controle de acesso) costuma ser chamado de encapsulamento. O resultado é um tipo de dados com características e comportamento. O encapsulamento também envolve essencialmente ocultação de dados e ocultação de implementação. “Encapsular tudo o que pode mudar” . Esta citação é um princípio de design bem conhecido. Aliás, em qualquer classe, alterações de dados podem ocorrer em tempo de execução e alterações de implementação podem ocorrer em versões futuras. Assim, o encapsulamento se aplica tanto aos dados quanto à implementação. Então eles podem ser conectados assim:- Abstração é principalmente o que uma classe pode fazer [Ideia]
- Encapsulamento é mais Como conseguir esta funcionalidade [Implementação]
Diferenças entre interface e classe abstrata?
As principais diferenças podem ser listadas a seguir:- Uma interface não pode implementar nenhum método, mas uma classe abstrata pode.
- Uma classe pode implementar muitas interfaces, mas só pode ter uma superclasse (abstrata ou não abstrata)
- Uma interface não faz parte de uma hierarquia de classes. Classes não relacionadas podem implementar a mesma interface.
Cat
e Dog
pode herdar da classe abstrata Animal
, e esta classe base abstrata implementará o método void Breathe()
- respirar, que todos os animais executarão da mesma maneira. Quais verbos podem ser aplicados à minha classe e a outras pessoas? Crie uma interface para cada um desses verbos. Por exemplo, todos os animais podem comer, então vou criar uma interface IFeedable
e fazer com que ele Animal
implemente essa interface. Bom o suficiente apenas para implementar uma interface Dog
( capaz de gostar de mim), mas não todas. Alguém disse: a principal diferença é onde você deseja sua implementação. Ao criar uma interface, você pode mover a implementação para qualquer classe que implemente sua interface. Ao criar uma classe abstrata, você pode compartilhar a implementação de todas as classes derivadas em um só lugar e evitar muitas coisas ruins, como duplicação de código. Horse
ILikeable
Como o StringBuffer economiza memória?
A classeString
é implementada como um objeto imutável, o que significa que quando você inicialmente decide colocar algo no objeto String
, a máquina virtual aloca uma matriz de comprimento fixo exatamente do tamanho do seu valor original. Isso será então tratado como uma constante dentro da máquina virtual, o que proporciona uma melhoria significativa no desempenho se o valor da string não mudar. No entanto, se você decidir alterar o conteúdo de uma string de alguma forma, o que a máquina virtual realmente faz é copiar o conteúdo da string original em um espaço temporário, fazer as alterações e salvá-las em uma nova matriz de memória. Assim, fazer alterações no valor de uma string após a inicialização é uma operação cara. StringBuffer
, por outro lado, é implementado como um array de expansão dinâmica dentro da máquina virtual, o que significa que qualquer operação de modificação pode ocorrer em uma célula de memória existente e nova memória será alocada conforme necessário. No entanto, não há como a máquina virtual fazer a otimização StringBuffer
porque seu conteúdo é considerado inconsistente em cada instância.
Por que os métodos wait e notify são declarados na classe Object em vez de Thread?
Os métodoswait
, notify
, notifyAll
são necessários apenas quando você deseja que seus threads tenham acesso a recursos compartilhados e o recurso compartilhado pode ser qualquer objeto Java no heap. Assim, esses métodos são definidos na classe base Object
para que cada objeto possua um controle que permite que as threads aguardem em seu monitor. Java não possui nenhum objeto especial usado para compartilhar um recurso compartilhado. Nenhuma estrutura de dados desse tipo está definida. Portanto, é responsabilidade da classe Object
poder se tornar um recurso compartilhado e fornecer métodos auxiliares como wait()
,,, notify()
. notifyAll()
Java é baseado na ideia de monitores de Charles Hoare. Em Java, todos os objetos possuem um monitor. Threads esperam nos monitores, então para realizar a espera precisamos de dois parâmetros:
- um tópico
- monitorar (qualquer objeto).
wait
). Este é um bom design porque se pudermos forçar qualquer outro thread a esperar em um monitor específico, isso resultará em "invasão", dificultando o design/programação de programas paralelos. Lembre-se de que em Java, todas as operações que interferem em outros threads estão obsoletas (por exemplo, stop()
).
Escreva um programa para criar um impasse em Java e corrigi-lo
Em Javadeadlock
, esta é uma situação em que pelo menos dois threads mantêm um bloco em recursos diferentes e ambos aguardam que o outro recurso fique disponível para concluir sua tarefa. E nenhum deles é capaz de bloquear o recurso que está sendo mantido. Exemplo de programa:
package thread;
public class ResolveDeadLockTest {
public static void main(String[] args) {
ResolveDeadLockTest test = new ResolveDeadLockTest();
final A a = test.new A();
final B b = test.new B();
// Thread-1
Runnable block1 = new Runnable() {
public void run() {
synchronized (a) {
try {
// Добавляем задержку, чтобы обе нити могли начать попытки
// блокирования ресурсов
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// Thread-1 заняла A но также нуждается в B
synchronized (b) {
System.out.println("In block 1");
}
}
}
};
// Thread-2
Runnable block2 = new Runnable() {
public void run() {
synchronized (b) {
// Thread-2 заняла B но также нуждается в A
synchronized (a) {
System.out.println("In block 2");
}
}
}
};
new Thread(block1).start();
new Thread(block2).start();
}
// Resource A
private class A {
private int i = 10;
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
}
// Resource B
private class B {
private int i = 20;
public int getI() {
return i;
}
public void setI(int i) {
this.i = i;
}
}
}
A execução do código acima resultará em um impasse por razões muito óbvias (explicadas acima). Agora precisamos resolver esse problema. Acredito que a solução para qualquer problema está na raiz do próprio problema. No nosso caso, o modelo de acesso a A e B é o principal problema. Portanto, para resolvê-lo, basta alterar a ordem dos operadores de acesso aos recursos compartilhados. Após a alteração ficará assim:
// Thread-1
Runnable block1 = new Runnable() {
public void run() {
synchronized (b) {
try {
// Добавляем задержку, чтобы обе нити могли начать попытки
// блокирования ресурсов
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
// Thread-1 заняла B но также нуждается в А
synchronized (a) {
System.out.println("In block 1");
}
}
}
};
// Thread-2
Runnable block2 = new Runnable() {
public void run() {
synchronized (b) {
// Thread-2 заняла B но также нуждается в А
synchronized (a) {
System.out.println("In block 2");
}
}
}
};
Execute esta classe novamente e agora você não verá o impasse. Espero que isso ajude você a evitar impasses e a se livrar deles caso os encontre.
O que acontece se a sua classe que implementa a interface Serializable contiver um componente não serializável? Como consertar isto?
Neste caso, será lançadoNotSerializableException
durante a execução. Para resolver este problema, existe uma solução muito simples - marque estas caixas transient
. Isso significa que os campos marcados não serão serializados. Se você também deseja armazenar o estado desses campos, então você precisa considerar variáveis de referência, que já implementam o Serializable
. Você também pode precisar usar os métodos readResolve()
e writeResolve()
. Vamos resumir:
- Primeiro, torne seu campo não serializável
transient
. - Primeiro
writeObject
, chamedefaultWriteObject
o thread para salvar todos os não-transient
campos e, em seguida, chame os métodos restantes para serializar as propriedades individuais do seu objeto não serializável. - Em
readObject
, primeiro chamedefaultReadObject
o fluxo para ler todos os não-transient
campos e, em seguida, chame outros métodos (correspondentes aos que você adicionouwriteObject
) para desserializar seu não-transient
objeto.
Explique palavras-chave transitórias e voláteis em Java
"A palavra-chavetransient
é usada para indicar campos que não serão serializados." De acordo com a Especificação da Linguagem Java: As variáveis podem ser marcadas com o indicador transitório para indicar que não fazem parte do estado persistente do objeto. Por exemplo, você pode conter campos derivados de outros campos e é preferível obtê-los programaticamente em vez de restaurar seu estado por meio de serialização. Por exemplo, em uma classe, BankPayment.java
campos como principal
(diretor) e rate
(taxa) podem ser serializados e interest
(juros acumulados) podem ser calculados a qualquer momento, mesmo após a desserialização. Se lembrarmos, cada thread em Java possui sua própria memória local e executa operações de leitura/gravação nesta memória local. Quando todas as operações são concluídas, ele grava o estado modificado da variável na memória compartilhada, de onde todos os threads acessam a variável. Normalmente, este é um thread normal dentro de uma máquina virtual. Mas o modificador volátil informa à máquina virtual que o acesso de um thread a essa variável deve sempre corresponder à sua própria cópia dessa variável com a cópia mestre da variável na memória. Isso significa que toda vez que um thread quiser ler o estado de uma variável, ele deverá limpar o estado da memória interna e atualizar a variável da memória principal. Volatile
mais útil em algoritmos sem bloqueio. Você marca uma variável que armazena dados compartilhados como volátil, não usa bloqueios para acessar essa variável e todas as alterações feitas por um thread ficarão visíveis para outros. Ou se você deseja criar um relacionamento "aconteceu depois" para garantir que os cálculos não sejam repetidos, novamente para garantir que as alterações sejam visíveis em tempo real. Volátil deve ser usado para publicar objetos imutáveis com segurança em um ambiente multithread. A declaração do campo public volatile ImmutableObject
garante que todos os threads sempre vejam a referência atualmente disponível para a instância.
Diferença entre Iterador e ListIterator?
Podemos usar ou paraIterator
iterar sobre os elementos . Mas só pode ser usado para iterar elementos . Outras diferenças são descritas abaixo. Você pode: Set
List
Map
ListIterator
List
- iterar na ordem inversa.
- obtenha índice em qualquer lugar.
- adicione qualquer valor em qualquer lugar.
- defina qualquer valor na posição atual.
GO TO FULL VERSION