JavaRush /Blogue Java /Random-PT /Núcleo Java. Perguntas da entrevista, parte 2
Andrey
Nível 26

Núcleo Java. Perguntas da entrevista, parte 2

Publicado no grupo Random-PT
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.
Núcleo Java.  Perguntas para uma entrevista, parte 2 - 1
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étodo finalize()é 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 finalizenã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:
  1. 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étodo finalize()isso não acontecerá. O método da superclasse finalize()deve ser chamado explicitamente.
  2. Qualquer exceção lançada pelo método finalizeserá 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 é?
  3. 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 que HashMapesta é 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 HashTablepor 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.nextpuder apontar para si mesmo. Isso causará um loop infinito, mas como e.nextele apontará para si mesmo (ou seja, para e)? Isso pode acontecer em um método void transfer(Entry[] newTable)chamado enquanto HashMapestá 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.
O que você precisa lembrar é o seguinte: “Quando você consegue descrever completamente um conceito em termos de “o que ele faz” sem ter que especificar “como ele faz”, então você deve usar uma interface. Se você precisar incluir alguns detalhes de implementação, então você precisa representar seu conceito em uma classe abstrata." Além disso, dito de outra forma: existem muitas classes que podem ser "agrupadas" e descritas por um único substantivo? Nesse caso, crie uma classe abstrata com o nome deste substantivo e herde classes dele. Por exemplo, Cate Dogpode 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 IFeedablee fazer com que ele Animalimplemente 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. HorseILikeable

Como o StringBuffer economiza memória?

A classe Stringé 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 StringBufferporque 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étodos wait, notify, notifyAllsã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 Objectpara 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 Objectpoder 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).
No design Java, um thread não pode ser definido com precisão; é sempre o thread atual que executa o código. Entretanto, podemos definir um monitor (que é um objeto no qual podemos chamar um método 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 Java deadlock, 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. Núcleo Java.  Perguntas para uma entrevista, parte 2 - 2 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çado NotSerializableExceptiondurante 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, chame defaultWriteObjecto thread para salvar todos os não- transientcampos e, em seguida, chame os métodos restantes para serializar as propriedades individuais do seu objeto não serializável.
  • Em readObject, primeiro chame defaultReadObjecto fluxo para ler todos os não- transientcampos e, em seguida, chame outros métodos (correspondentes aos que você adicionou writeObject) para desserializar seu não- transientobjeto.

Explique palavras-chave transitórias e voláteis em Java

"A palavra-chave transienté 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.javacampos 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. Volatilemais ú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 ImmutableObjectgarante 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 para Iteratoriterar sobre os elementos . Mas só pode ser usado para iterar elementos . Outras diferenças são descritas abaixo. Você pode: SetListMapListIteratorList
  1. iterar na ordem inversa.
  2. obtenha índice em qualquer lugar.
  3. adicione qualquer valor em qualquer lugar.
  4. defina qualquer valor na posição atual.
Boa sorte com seus estudos!! Autor do artigo Lokesh Gupta Artigo original Java Core. Perguntas da entrevista, parte 1 Java Core. Perguntas para uma entrevista, parte 3
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION