Meio
São comuns
1. Quais são as vantagens e desvantagens da OOP quando comparada à programação processual/funcional?
Tinha essa pergunta na análise das perguntas do Juinior e por isso eu já respondi. Procure esta pergunta e sua resposta nesta parte do artigo, questões 16 e 17.2. Como a agregação difere da composição?
Na POO, existem vários tipos de interação entre objetos, unidos sob o conceito geral de “Relacionamento Tem-A”. Esse relacionamento indica que um objeto é um componente de outro objeto. Ao mesmo tempo, existem dois subtipos deste relacionamento: Composição - um objeto cria outro objeto e o tempo de vida de outro objeto depende do tempo de vida do criador. Agregação - um objeto recebe um link (ponteiro) para outro objeto durante o processo de construção (neste caso, o tempo de vida do outro objeto não depende do tempo de vida do criador). Para melhor compreensão, vejamos um exemplo específico. Temos uma determinada classe de carro - Car , que por sua vez possui campos internos do tipo - Engine e uma lista de passageiros - List<Passenger> , também possui um método para iniciar o movimento - startMoving() :public class Car {
private Engine engine;
private List<Passenger> passengers;
public Car(final List<Passenger> passengers) {
this.engine = new Engine();
this.passengers = passengers;
}
public void addPassenger(Passenger passenger) {
passengers.add(passenger);
}
public void removePassengerByIndex(Long index) {
passengers.remove(index);
}
public void startMoving() {
engine.start();
System.out.println("Машина начала своё движение");
for (Passenger passenger : passengers) {
System.out.println("В машине есть пассажир - " + passenger.getName());
}
}
}
Neste caso, a Composição é a ligação entre Car e Engine , já que o desempenho do carro depende diretamente da presença do objeto motor, pois se engine = null , então receberemos um NullPointerException . Por sua vez, um motor não pode existir sem uma máquina (por que precisamos de um motor sem máquina?) e não pode pertencer a várias máquinas ao mesmo tempo. Isso significa que se excluirmos o objeto Car , não haverá mais referências ao objeto Engine e em breve ele será excluído pelo Garbage Collector . Como você pode ver, essa relação é muito rígida (forte). Agregação é a ligação entre Carro e Passageiro , já que o desempenho de Carro não depende de forma alguma de objetos do tipo Passageiro e sua quantidade. Eles podem sair do carro - removePassengerByIndex(Long index) ou inserir novos - addPassenger(Passenger passageiro) , apesar disso, o carro continuará funcionando corretamente. Por sua vez, os objetos Passenger podem existir sem um objeto Car . Como você entende, esta é uma conexão muito mais fraca do que vemos na composição. Mas isso não é tudo, um objeto que está conectado por agregação a outro também pode ter uma determinada conexão com outros objetos ao mesmo tempo. Por exemplo, você, como estudante de Java, está matriculado em cursos de inglês, OOP e logaritmos ao mesmo tempo, mas ao mesmo tempo não é uma parte criticamente necessária deles, sem a qual o funcionamento normal é impossível (como como um professor).
3. Quais padrões GoF você usou na prática? Dar exemplos.
Já respondi essa pergunta antes, então vou deixar apenas um link para a análise , veja a primeira pergunta. Também encontrei um maravilhoso artigo com dicas sobre padrões de design, que recomendo fortemente manter em mãos.4. O que é um objeto proxy? Dar exemplos
Um proxy é um padrão de design estrutural que permite substituir objetos substitutos especiais, ou em outras palavras, objetos proxy, em vez de objetos reais. Esses objetos proxy interceptam chamadas para o objeto original, permitindo que alguma lógica seja inserida antes ou depois da chamada ser passada para o original. Exemplos de uso de um objeto proxy:-
Como proxy remoto - usado quando precisamos de um objeto remoto (um objeto em um espaço de endereço diferente) que precisa ser representado localmente. Neste caso, o proxy cuidará da criação, codificação, decodificação da conexão, etc., enquanto o cliente a utilizará como se fosse o objeto original localizado no espaço local.
-
Como proxy virtual - usado quando um objeto que consome muitos recursos é necessário. Nesse caso, o objeto proxy serve como algo como uma imagem de um objeto real que na verdade ainda não existe. Quando uma solicitação real (chamada de método) é enviada para este objeto, só então o objeto original é carregado e o método é executado. Essa abordagem também é chamada de inicialização lenta; isso pode ser muito conveniente, porque em algumas situações o objeto original pode não ser útil e então não haverá custo para criá-lo.
-
Como proxy de segurança - usado quando você precisa controlar o acesso a algum objeto com base nos direitos do cliente. Ou seja, se um cliente sem direitos de acesso tentar acessar o objeto original, o proxy irá interceptá-lo e não permitir.
public interface Processor {
void process();
}
cuja implementação utiliza muitos recursos, mas ao mesmo tempo pode não ser utilizada sempre que o aplicativo for iniciado:
public class HiperDifficultProcessor implements Processor {
@Override
public void process() {
// некоторый сверхсложная обработка данных
}
}
Classe proxy:
public class HiperDifficultProcessorProxy implements Processor {
private HiperDifficultProcessor processor;
@Override
public void process() {
if (processor == null) {
processor = new HiperDifficultProcessor();
}
processor.process();
}
}
Vamos executá-lo em main :
Processor processor = new HiperDifficultProcessorProxy();
// тут тяжеловсеного оригинального an object, ещё не сущетсвует
// но при этом есть an object, который его представляет и у которого можно вызывать его методы
processor.process(); // лишь теперь, an object оригинал был создан
Observo que muitas estruturas usam proxy e, para o Spring , esse é um padrão chave (o Spring é costurado por dentro e por fora). Leia mais sobre esse padrão aqui .
5. Quais inovações foram anunciadas no Java 8?
As inovações trazidas pelo Java 8 são as seguintes:-
Interfaces funcionais foram adicionadas, leia sobre que tipo de animal é esse aqui .
-
Expressões lambda, que estão intimamente relacionadas às interfaces funcionais, leia mais sobre seu uso aqui .
-
Adicionada API Stream para processamento conveniente de coletas de dados, leia mais aqui .
-
Adicionados links para métodos .
-
O método forEach() foi adicionado à interface Iterable .
-
Adicionada uma nova API de data e hora no pacote java.time , análise detalhada aqui .
-
API simultânea aprimorada .
-
Adicionando uma classe wrapper Opcional , que é usada para lidar corretamente com valores nulos, você pode encontrar um excelente artigo sobre este tópico aqui .
-
Adicionando a capacidade das interfaces de usar métodos estáticos e padrão (o que, em essência, aproxima o Java da herança múltipla), mais detalhes aqui .
-
Adicionados novos métodos à classe Collection(removeIf(), spliterator()) .
-
Pequenas melhorias no Java Core.
6. O que é Alta Coesão e Baixo Acoplamento? Dar exemplos.
Alta Coesão ou Alta Coesão é o conceito quando uma determinada classe contém elementos intimamente relacionados entre si e combinados para sua finalidade. Por exemplo, todos os métodos da classe User devem representar o comportamento do usuário. Uma classe terá baixa coesão se contiver elementos não relacionados. Por exemplo, a classe User contendo um método de validação de endereço de e-mail:public class User {
private String name;
private String email;
public String getName() {
return this.name;
}
public void setName(final String name) {
this.name = name;
}
public String getEmail() {
return this.email;
}
public void setEmail(final String email) {
this.email = email;
}
public boolean isValidEmail() {
// некоторая логика валидации емейла
}
}
A classe user pode ser responsável por armazenar o endereço de email do usuário, mas não por validá-lo ou enviar o email. Portanto, para obter alta coerência, movemos o método de validação para uma classe de utilitário separada:
public class EmailUtil {
public static boolean isValidEmail(String email) {
// некоторая логика валидации емейла
}
}
E usamos conforme necessário (por exemplo, antes de salvar o usuário). Low Coupling ou Low Coupling é um conceito que descreve a baixa interdependência entre módulos de software. Essencialmente, a interdependência é como mudar um requer mudar o outro. Duas classes têm um acoplamento forte (ou acoplamento forte) se estiverem intimamente relacionadas. Por exemplo, duas classes concretas que armazenam referências entre si e chamam os métodos uma da outra. Classes fracamente acopladas são mais fáceis de desenvolver e manter. Por serem independentes entre si, podem ser desenvolvidos e testados em paralelo. Além disso, eles podem ser alterados e atualizados sem afetar um ao outro. Vejamos um exemplo de classes fortemente acopladas. Temos algumas turmas de alunos:
public class Student {
private Long id;
private String name;
private List<Lesson> lesson;
}
Que contém uma lista de lições:
public class Lesson {
private Long id;
private String name;
private List<Student> students;
}
Cada lição contém um link para os alunos participantes. Aperto incrivelmente forte, não acha? Como você pode reduzi-lo? Primeiro, vamos garantir que os alunos não tenham uma lista de disciplinas, mas uma lista de seus identificadores:
public class Student {
private Long id;
private String name;
private List<Long> lessonIds;
}
Em segundo lugar, a turma da aula não precisa saber sobre todos os alunos, então vamos deletar completamente a lista deles:
public class Lesson {
private Long id;
private String name;
}
Então ficou muito mais fácil e a conexão ficou bem mais fraca, não acham?
POO
7. Como você pode implementar herança múltipla em Java?
Herança múltipla é um recurso do conceito orientado a objetos onde uma classe pode herdar propriedades de mais de uma classe pai. O problema surge quando existem métodos com a mesma assinatura tanto na superclasse quanto na subclasse. Ao chamar um método, o compilador não pode determinar qual método de classe deve ser chamado e mesmo ao chamar o método de classe que tem precedência. Portanto, Java não suporta herança múltipla! Mas existe uma espécie de brecha, da qual falaremos a seguir. Como mencionei anteriormente, com o lançamento do Java 8, a capacidade de ter métodos padrão foi adicionada às interfaces . Se a classe que implementa a interface não substituir este método, então esta implementação padrão será usada (não é necessário substituir o método padrão, como implementar um abstrato). Neste caso, é possível implementar diferentes interfaces em uma classe e utilizar seus métodos padrão. Vejamos um exemplo. Temos uma interface de flyer, com um método fly() padrão :public interface Flyer {
default void fly() {
System.out.println("Я лечу!!!");
}
}
A interface do walker, com o método walk() padrão :
public interface Walker {
default void walk() {
System.out.println("Я хожу!!!");
}
}
A interface do nadador, com o método swim() :
public interface Swimmer {
default void swim() {
System.out.println("Я плыву!!!");
}
}
Bem, agora vamos implementar tudo isso em uma classe duck:
public class Duck implements Flyer, Swimmer, Walker {
}
E vamos executar todos os métodos do nosso pato:
Duck donald = new Duck();
donald.walk();
donald.fly();
donald.swim();
No console receberemos:
- Renomeie métodos em interfaces para que sejam diferentes entre si.
- Substitua esses métodos controversos na classe de implementação.
- Herde de uma classe que implementa esses métodos controversos (então sua classe usará exatamente sua implementação).
8. Qual é a diferença entre os métodos final, finalmente e finalize()?
final é uma palavra-chave usada para colocar uma restrição em uma classe, método ou variável, uma restrição que significa:- Para uma variável - após a inicialização, a variável não pode ser redefinida.
- Para um método, o método não pode ser substituído em uma subclasse (classe sucessora).
- Para uma classe - a classe não pode ser herdada.
GO TO FULL VERSION