Exercícios — O Paradigma Orientado a Objetos e a Máquina Java

Aula 1 — Programação Orientada a Objetos

Autor

Marcos M. Raimundo — Instituto de Computação, UNICAMP

Aula Soluções

Questões discursivas

  1. Em um sistema de e-commerce, a classe CalculadoraDeFrete foi modificada para suportar envios internacionais, e essa alteração causou falhas no módulo RelatoriosFinanceiros, que acessava diretamente variáveis internas da calculadora para projetar custos. Usando o acrônimo TRUE, identifique quais propriedades foram violadas, e explique como um design Transparent teria evitado o problema.

  2. Analise o trecho de código procedural abaixo, que gerencia o saldo de um usuário:

    if (usuario.getSaldo() >= valorCompra && usuario.getStatus() == Status.ATIVO) {
        double novoSaldo = usuario.getSaldo() - valorCompra;
        usuario.setSaldo(novoSaldo);
    }

    Explique por que esse código viola o princípio Tell, Don’t Ask, e como mover essa lógica para dentro do objeto Usuario preserva as invariantes de negócio.

  3. Em Java, “absolutamente tudo é passado por valor”. Se declararmos Produto p = new Produto("TV", 50.0) e passarmos p para um método que executa p.setPreco(90.0), a alteração é vista por quem chamou o método. Mas se esse mesmo método executar p = new Produto("Geladeira", 3000.0), a variável de quem chamou não muda. Explique essa aparente contradição usando o conceito de “cópia do endereço de memória”, indicando onde cada variável vive (Stack ou Heap).

Questões de Verdadeiro/Falso

Cada bloco de 4 itens trata do mesmo tema. A questão só é considerada correta se todos os 4 itens forem julgados corretamente (deixar em branco tem penalidade de 20% da nota da questão).

NotaTeste 1 — TRUE e o custo de mudança
  • □ Um código pode ser 100% funcional e passar em todos os testes automatizados e, ainda assim, falhar completamente no critério TRUE proposto por Sandi Metz.
  • □ Uma correção rápida (quick fix) que funciona perfeitamente, mas ensina ao próximo programador que ler o código a ignorar os padrões de design do sistema, ainda satisfaz a propriedade Exemplary.
  • □ Um código Reasonable impõe que qualquer mudança tenha sempre o mesmo custo fixo, independente do benefício.
  • □ Rigidez, fragilidade e imobilidade descrevem, na prática, o mesmo problema estrutural visto de ângulos diferentes — um sistema que elimina a rigidez necessariamente também deixa de sofrer de fragilidade e imobilidade.
NotaTeste 2 — Espaço do Problema vs. Paradigma Procedural
  • □ Reorganizar um programa procedural em vários arquivos e módulos, sem alterar o fato de que funções externas continuam manipulando diretamente as structs de dados, já elimina o risco de efeito colateral apontado no paradigma procedural.
  • □ Uma classe cujos atributos são todos declarados public, mas cujos métodos que os utilizam ficam definidos dentro da mesma classe, já realiza plenamente a proposta orientada a objetos de unir dados e comportamento.
  • □ No exemplo do desconto, a visão orientada a objetos calcula o desconto por fora e depois grava o resultado no objeto Produto.
  • □ Mesmo que um sistema orientado a objetos declare todos os atributos de suas classes como public, ele deixa de correr o risco de dados globais manipulados livremente por funções externas, simplesmente por estar estruturado em classes.
NotaTeste 3 — Estado, Comportamento e Identidade
  • □ Se o Estado de um objeto Produto mudar (por exemplo, após aplicarDesconto), sua Identidade também muda, pois o objeto passa a representar uma versão diferente de si mesmo.
  • □ Dois objetos com o mesmo estado são necessariamente o mesmo objeto na memória.
  • □ Um objeto sem nenhum método público, apenas atributos, ainda pode ter Comportamento, desde que seu Estado seja suficientemente rico e detalhado.
  • □ Um objeto ContaBancaria com saldo negativo por falha de sistema e sem nome de titular perde sua Identidade, deixando de ser uma instância única na memória.
NotaTeste 4 — Encapsulamento e Ocultamento de Informação
  • □ Uma classe cujos atributos são todos private, mas cujos métodos públicos apenas devolvem e recebem esses atributos sem qualquer validação (getters/setters triviais), está tão protegida contra fragilidade quanto uma classe com validação de invariantes, como a que valida o preço em Produto.
  • □ Em uma equipe pequena e de confiança, em que nenhum código malicioso jamais seria escrito, o Ocultamento de Informação deixa de trazer benefício, pois sua função é impedir invasões externas.
  • □ Um objeto que expõe os métodos públicos getSaldo() e setSaldo(double), permitindo que código externo leia o saldo, decida se deve subtrair o valor de uma compra, e então grave o novo saldo de volta, está aplicando corretamente o princípio Tell, Don’t Ask.
  • □ Uma classe cujos atributos são todos public, mas que ainda define métodos como aplicarDesconto() ao lado desses atributos, preserva a autonomia do objeto, pois dados e comportamento continuam fisicamente dentro da mesma classe.
NotaTeste 5 — Classe vs. Objeto/Instância
  • □ Se duas equipes de desenvolvimento diferentes escreverem new Produto(...) no mesmo sistema, ambas estão, de fato, criando cópias físicas independentes da classe Produto, e não apenas instâncias que compartilham o mesmo bytecode.
  • □ Se um sistema instanciar 10.000 objetos Cliente ao carregar os dados de um banco, a memória consumida para armazenar o bytecode dos métodos de Cliente cresce proporcionalmente a esses 10.000 objetos.
  • □ Ao reiniciar a JVM e executar o mesmo programa novamente, o bytecode das classes já utilizadas na execução anterior continua disponível no Metaspace, sem precisar ser recarregado a partir do arquivo .class.
  • □ Se a classe Cliente tiver um campo static que conta quantas instâncias já foram criadas, esse contador vive na Heap, dentro de uma das instâncias de Cliente, e não no Metaspace junto com o restante dos membros estáticos da classe.
NotaTeste 6 — JVM, Bytecode e Portabilidade (WORA)
  • □ O javac compila o código-fonte Java diretamente para instruções nativas da CPU hospedeira.
  • □ Se a JVM instalada em uma determinada máquina tiver um bug de implementação e interpretar um opcode do Bytecode de forma diferente do especificado, o mesmo arquivo .class ainda produzirá exatamente o mesmo resultado em qualquer ambiente, pois a portabilidade do Bytecode independe da JVM usada.
  • □ Um arquivo .class compilado em uma máquina Windows pode ser executado em um servidor Linux sem qualquer JVM instalada, desde que o processador de ambas as máquinas seja da mesma arquitetura (por exemplo, x86-64).
  • □ Mesmo sem nenhuma JVM instalada no ambiente hospedeiro, o Bytecode de um programa Java consegue ser executado diretamente pelo sistema operacional, já que foi compilado uma vez pelo javac.
NotaTeste 7 — O Compilador JIT
  • □ O JIT compila o programa inteiro para código nativo assim que a aplicação é iniciada.
  • □ Um método chamado apenas uma única vez durante toda a execução do programa é considerado um hotspot pelo JIT e recebe prioridade de compilação para código nativo.
  • □ Em uma aplicação de vida muito curta, como uma função serverless que roda por poucos milissegundos e termina, a compilação JIT tende a trazer mais vantagem de desempenho do que a compilação estática (AOT).
  • □ Como o JIT compila os hotspots para código nativo, um programa Java em execução deixa de ter qualquer trecho de código sendo interpretado pela JVM a partir desse ponto, comportando-se como um binário totalmente compilado.
NotaTeste 8 — Stack, Heap e Alcançabilidade
  • □ No método processarPedido() do exemplo da aula, se enviarEmailConfirmacao(p1) criasse internamente um novo objeto Email com new, esse objeto Email seria armazenado na mesma área de memória (Stack) que a variável local p1.
  • □ Se um objeto for criado com new dentro de um método, mas a variável que o referencia for declarada static em vez de local, o objeto passa a ser alocado no Metaspace junto com a classe, em vez da Heap.
  • □ No método processarPedido(), se a variável p1 fosse declarada static em vez de local, o objeto Pedido que ela referencia se tornaria elegível para coleta assim que o método terminasse, exatamente como aconteceria com uma variável local comum.
  • □ Se um objeto A for referenciado apenas por um objeto B, e o objeto B, por sua vez, também não for alcançável a partir de nenhuma GC Root, o objeto A ainda é considerado alcançável, pois existe pelo menos uma referência apontando para ele.
NotaTeste 9 — Retenção Obsoleta e Recursos do Sistema
  • □ O Java é imune a qualquer forma de acúmulo indevido de objetos na memória, graças ao Garbage Collector.
  • □ Se o método registrar() do MonitorDeVendas removesse cada Pedido do cacheInfinito imediatamente após inseri-lo, essa estrutura estática deixaria de causar retenção obsoleta, mesmo continuando a existir como GC Root.
  • □ Um objeto Scanner usado para ler um arquivo, se simplesmente saísse de escopo ao fim de um método sem ser explicitamente fechado, teria seu identificador de arquivo do sistema operacional liberado no mesmo instante em que o objeto se tornasse elegível para coleta pelo GC.
  • □ Se o bloco try de um try-with-resources terminar sem lançar nenhuma exceção, o método close() do recurso não é chamado, pois close() serve apenas para lidar com falhas.
NotaTeste 10 — Modificadores de Acesso
  • □ Se o método depositar(double valor) da classe ContaBancaria fosse declarado private, o código externo que hoje chama conta.depositar(100) continuaria compilando normalmente, só deixaria de funcionar em tempo de execução.
  • □ Uma classe ContaBancaria com o atributo saldo declarado public, mas com um método privado validarSaldo() chamado internamente antes de qualquer operação, ainda impede que código externo execute conta.saldo = -5000 diretamente.
  • □ Um projeto Java com várias classes no mesmo pacote (package), mas sem nenhuma relação de herança entre elas, ainda impede completamente que um atributo private de uma classe seja acessado diretamente por outra classe desse mesmo pacote.
  • □ Se um atributo private for acessado por outra classe através de reflection (pacote java.lang.reflect), a JVM impede essa leitura da mesma forma rígida com que o compilador impede o acesso direto em código-fonte comum.
NotaTeste 11 — Escopo, Sombreamento e this
  • □ Se um construtor Produto(String nome) não tiver nenhum parâmetro com o mesmo nome de um atributo da classe, ainda é possível ocorrer sombreamento entre uma variável local declarada dentro do próprio corpo do construtor e algum atributo da classe.
  • □ Se um construtor tiver um parâmetro chamado nome, igual ao atributo nome da classe, e o corpo do construtor for nome = nome;, o valor do atributo nome da instância passa a ser igual ao valor do parâmetro depois que essa linha é executada.
  • □ Se um método de instância usar apenas o nome simples de um atributo (sem o prefixo this.) e não houver nenhum parâmetro ou variável local com o mesmo nome naquele escopo, o Java ainda assim falha em compilar, pois toda referência a um atributo exige obrigatoriamente o prefixo this..
  • □ Se dois objetos p1 e p2 da classe Produto chamarem o mesmo método aplicarDesconto(10) ao mesmo tempo, em duas threads diferentes, ambos os objetos serão alterados simultaneamente, pois this é compartilhado entre as duas execuções do método.
NotaTeste 12 — Passagem de Parâmetros: Primitivos vs. Referências
  • □ Se um método Java recebesse um array com 10 milhões de elementos como parâmetro, a JVM copiaria fisicamente todos os 10 milhões de valores para a Stack do método antes de executá-lo.
  • □ Se um método receber um parâmetro int e multiplicá-lo por 2 internamente, a variável original que foi passada para o método também dobra de valor depois que a chamada retorna.
  • □ Ao passar dois objetos Produto diferentes (a TV e a Geladeira) para dois parâmetros do mesmo método, cada parâmetro guarda uma cópia de um endereço de memória diferente, mesmo que os dois objetos tivessem, por coincidência, exatamente os mesmos valores de nome e preco.
  • □ Se, dentro do método sabotarProduto(Produto prod), a reatribuição prod = new Produto("Geladeira", 3000.0) ocorresse ANTES da chamada prod.setPreco(999.0), o objeto TV original do chamador ainda assim acabaria com o preço alterado para 999.0.