Estrutura da classe Java e perguntas da entrevista de inicialização
*1. Introdução *
Estrutura e inicialização de classe são os princípios básicos com os quais todos os programadores Java devem estar familiarizados. Este artigo fornece respostas para algumas das perguntas da entrevista sobre o tópico que você pode encontrar.
====* Q1. Descreva o significado da palavra-chave final quando aplicada a uma classe, método, campo ou variável local. *
A palavra-chave final possui vários significados diferentes quando aplicada a diferentes construções de idioma:
-
Uma classe final é uma classe que não pode ser subclassificada
-
Um método final é um método que não pode ser substituído em subclasses
-
Um campo final é um campo que precisa ser inicializado no bloco construtor ou inicializador e não pode ser modificado depois disso. *Uma variável final é uma variável que pode ser atribuída (e deve ser atribuída) apenas uma vez e nunca é modificada depois disso.
====* Q2. O que é um método padrão? *
Antes do Java 8, as interfaces só podiam ter métodos abstratos, ou seja, métodos sem corpo. A partir do Java 8, os métodos de interface podem ter uma implementação padrão. Se uma classe de implementação não substituir esse método, a implementação padrão será usada. Esses métodos são adequadamente marcados com uma palavra-chave default.
Um dos casos de uso de destaque de um método default é adicionar um método a uma interface existente. Se você não marcar esse método de interface como default, todas as implementações existentes nessa interface serão interrompidas. A adição de um método com uma implementação default garante a compatibilidade binária do código legado com a nova versão dessa interface.
Um bom exemplo disso é a interface Iterator, que permite que uma classe seja um alvo do loop for-each. Essa interface apareceu pela primeira vez no Java 5, mas no Java 8 recebeu dois métodos adicionais, forEach e spliterator. Eles são definidos como métodos padrão com implementações e, portanto, não quebram a compatibilidade com versões anteriores:
public interface Iterable<T> {
Iterator<T> iterator();
default void forEach(Consumer<? super T> action) {/* */}
default Spliterator<T> spliterator() {/* */}
}
*Q3. O que são membros da classe estática? *
Estático campos e métodos de uma classe não estão vinculados a uma instância específica de uma classe. Em vez disso, eles estão vinculados ao próprio objeto de classe. A chamada de um método static ou o endereçamento de um campo static é resolvida no momento da compilação porque, ao contrário dos métodos e campos da instância, não precisamos seguir a referência e determinar um objeto real ao qual nos referimos.
====* Q4. Uma classe pode ser declarada abstrata se não tiver nenhum membro abstrato? Qual poderia ser o objetivo de tal classe? *
Sim, uma classe pode ser declarada abstract mesmo que não contenha nenhum membro abstract. Como uma classe abstrata, ela não pode ser instanciada, mas pode servir como objeto raiz de alguma hierarquia, fornecendo métodos que podem ser úteis para suas implementações.
====* Q5. O que é encadeamento de construtor? *
O encadeamento de construtores é uma maneira de simplificar a construção de objetos, fornecendo vários construtores que se chamam em sequência.
O construtor mais específico pode receber todos os argumentos possíveis e pode ser usado para a configuração de objeto mais detalhada. Um construtor menos específico pode chamar o construtor mais específico, fornecendo alguns de seus argumentos com valores padrão. No topo da cadeia, um construtor sem argumento poderia instanciar um objeto com valores padrão.
Aqui está um exemplo com uma classe que modela um desconto em porcentagens disponíveis dentro de um determinado período de dias. Os valores padrão de 10% e 2 dias serão usados se não os especificarmos ao usar um construtor no-arg:
public class Discount {
private int percent;
private int days;
public Discount() {
this(10);
}
public Discount(int percent) {
this(percent, 2);
}
public Discount(int percent, int days) {
this.percent = percent;
this.days = days;
}
}
====* Q6. O que é substituição e sobrecarga de métodos? Como eles são diferentes? *
A substituição de um método é feita em uma subclasse quando você define um método com a mesma assinatura que na superclasse. Isso permite que o tempo de execução escolha um método, dependendo do tipo de objeto real no qual você chama o método. Os métodos toString, equals e hashCode são substituídos com frequência nas subclasses.
A sobrecarga de um método acontece na mesma classe. A sobrecarga ocorre quando você cria um método com o mesmo nome, mas com diferentes tipos ou número de argumentos. Isso permite que você execute um determinado código, dependendo dos tipos de argumentos que você fornece, enquanto o nome do método permanece o mesmo.
Aqui está um exemplo de sobrecarga na classe abstrata java.io.Writer. Os métodos a seguir são nomeados write, mas um deles recebe um int enquanto outro recebe um char array.
public abstract class Writer {
public void write(int c) throws IOException {
//...
}
public void write(char cbuf[]) throws IOException {
//...
}
}
====* Q7. Você pode substituir um método estático? *
Não, você não pode. Por definição, você pode substituir um método apenas se sua implementação for determinada em tempo de execução pelo tipo de instância real (um processo conhecido como pesquisa dinâmica de método). A implementação do método static é determinada no tempo de compilação usando o tipo de referência, portanto, substituir não faria muito sentido de qualquer maneira. Embora você possa adicionar à subclasse um método static com a mesma assinatura exata da superclasse, isso não é tecnicamente substituível.
====* Q8. O que é uma classe imutável e como você pode criar uma? *
Uma instância de uma classe imutável não pode ser alterada depois de criada. Alterando, queremos dizer mutando o estado, modificando os valores dos campos da instância. As classes imutáveis têm muitas vantagens: elas são seguras para threads e é muito mais fácil argumentar sobre elas quando você não tem um estado mutável a considerar.
Para tornar uma classe imutável, você deve garantir o seguinte:
-
Todos os campos devem ser declarados private e final; isso deduz que eles devem ser inicializados no construtor e não devem ser alterados desde então;
-
A classe não deve ter setters ou outros métodos que modifiquem os valores dos campos;
-
Todos os campos da classe que foram passados pelo construtor também devem ser imutáveis, ou seus valores devem ser copiados antes da inicialização do campo (ou então podemos mudar o estado dessa classe mantendo-os e modificando-os); *Os métodos da classe não devem ser substituíveis; todos os métodos devem ser final ou o construtor deve ser private e invocado apenas pelo método static factory.
====* Q9. Como você compara dois valores de enum: com igual a () ou com ==? *
Na verdade, você pode usar os dois. Os valores enum são objetos, portanto eles podem ser comparados com equals () _, mas também são implementados como constantes _static sob o capô, para que você possa compará-los com _ == _. Isso é principalmente uma questão de estilo de código, mas se você deseja economizar espaço de caracteres (e possivelmente pular uma chamada de método desnecessária), você deve comparar as enumerações com _ == _.
====* Q10. O que é um bloco inicializador? O que é um bloco de inicializador estático? *
Um bloco inicializador é um bloco de código entre chaves no escopo da classe que é executado durante a criação da instância. Você pode usá-lo para inicializar campos com algo mais complexo do que as linhas de inicialização in-loco.
Na verdade, o compilador apenas copia esse bloco dentro de cada construtor, portanto, é uma boa maneira de extrair código comum de todos os construtores.
Um bloco inicializador estático é um bloco de código entre chaves, com o modificador static à sua frente. É executado uma vez durante o carregamento da classe e pode ser usado para inicializar campos estáticos ou para alguns efeitos colaterais.
====* Q11. O que é uma interface de marcador? Quais são os exemplos notáveis de interfaces de marcador em Java? *
Uma interface de marcador é uma interface sem nenhum método. Geralmente é implementado por uma classe ou estendido por outra interface para significar uma determinada propriedade. As interfaces de marcador mais conhecidas na biblioteca Java padrão são as seguintes:
-
Serializable é usado para expressar explicitamente que essa classe pode ser serializada;
-
Cloneable permite clonar objetos usando o método clone (sem a interface Cloneable no lugar, esse método lança uma CloneNotSupportedException); *Remote é usado no RMI para especificar uma interface cujos métodos podem ser chamados remotamente.
====* Q12. O que é um Singleton e como ele pode ser implementado em Java? *
Singleton é um padrão de programação orientada a objetos. Uma classe singleton pode ter apenas uma instância, geralmente globalmente visível e acessível.
Existem várias maneiras de criar um singleton em Java. A seguir, é apresentado o exemplo mais simples com um campo static inicializado no local. A inicialização é segura para threads, pois é garantido que os campos static sejam inicializados de maneira segura para threads. O construtor é private, portanto, não há como o código externo criar mais de uma instância da classe.
public class SingletonExample {
private static SingletonExample instance = new SingletonExample();
private SingletonExample() {}
public static SingletonExample getInstance() {
return instance;
}
}
Mas essa abordagem pode ter uma séria desvantagem - a instância seria instanciada quando essa classe for acessada pela primeira vez. Se a inicialização dessa classe for uma operação pesada, e provavelmente gostaríamos de adiá-la até que a instância seja realmente necessária (possivelmente nunca), mas, ao mesmo tempo, mantenha-a segura para threads. Nesse caso, devemos usar uma técnica conhecida como* bloqueio com verificação dupla *.
Q13. O que é um Var-Arg? Quais são as restrições em um Var-Arg? Como você pode usá-lo dentro do corpo do método?
Var-arg é um argumento de tamanho variável para um método. Um método pode ter apenas um var-arg e deve vir por último na lista de argumentos. É especificado como um nome de tipo seguido por reticências e um nome de argumento. Dentro do corpo do método, um var-arg é usado como uma matriz do tipo especificado.
Aqui está um exemplo da biblioteca padrão - o método Collections.addAll que recebe uma coleção, um número variável de elementos e adiciona todos os elementos à coleção:
public static <T> boolean addAll(
Collection<? super T> c, T... elements) {
boolean result = false;
for (T element : elements)
result |= c.add(element);
return result;
}
Q14. Você pode acessar um método substituído de uma superclasse? Você pode acessar um método substituído de uma super superclasse de maneira semelhante?
Para acessar um método substituído de uma superclasse, você pode usar a palavra-chave super. Mas você não tem uma maneira semelhante de acessar o método substituído de uma superclasse.
Como um exemplo da biblioteca padrão, a classe LinkedHashMap estende HashMap e reutiliza principalmente sua funcionalidade, adicionando uma lista vinculada a seus valores para preservar a ordem da iteração. LinkedHashMap reutiliza o método clear de sua superclasse e limpa as referências de cabeçalho e cauda de sua lista vinculada:
public void clear() {
super.clear();
head = tail = null;
}