Específicos · Engenharia de Software
Java: JVM/JDK/JRE, POO, coleções, exceções checked × unchecked, streams, Spring
Antes de julgar qualquer item, pergunte que palavra a definição descreve — encapsulamento, herança e polimorfismo são coisas distintas, e a banca troca uma pela outra mais do que faz qualquer…
Altíssima89 itens no tópico
A ideia que organiza o assunto
Java nasceu de uma indireção. O compilador javac não gera código de máquina:
gera bytecode, instruções de uma máquina que não existe fisicamente. Quem
existe é a JVM, escrita uma vez para cada plataforma real, que lê esse
bytecode e o executa. O .class é o mesmo no Windows, no Linux e no mainframe;
o que muda é a JVM debaixo dele. Dessa indireção decorre tudo o que a linguagem
tem de característico — portabilidade, ausência de ponteiros, coletor de lixo,
tempo de partida mais alto.
Mas não é essa a ideia que resolve a prova. Olhe o que a CEBRASPE de fato pergunta neste tópico e você encontra, na maioria esmagadora dos itens, orientação a objetos escrita com sintaxe Java — e, dentro dela, um único formato repetido à exaustão: a definição está correta e o nome que a assina está trocado. “Abstração é a propriedade que permite que um método tenha comportamentos distintos conforme os parâmetros” descreve sobrecarga. “Encapsulamento é o princípio pelo qual objetos de uma mesma superclasse invocam métodos de mesma assinatura com comportamentos distintos” descreve polimorfismo. Nenhuma dessas frases tem erro de conteúdo — têm erro de etiqueta.
Daí o método: leia a definição primeiro, decida sozinho que palavra ela nomeia e só então olhe o substantivo do item. Se você ler o nome antes, ele contamina a leitura e a frase parece coerente. Lido por último, o nome errado salta. Esse único hábito decide mais itens deste tópico do que saber Java.
A segunda metade do tópico são listagens de código pedindo o resultado. Também ali a regra é ler na ordem certa: execute o código com papel antes de olhar o valor que o item anuncia, porque o valor anunciado é sempre plausível.
Por que se usa (e o que custa)
Ganha-se portabilidade sem recompilação e memória gerenciada. O programador não libera objeto, não faz aritmética de endereço e não corrompe o processo com liberação dupla — e é essa ausência de ponteiro que torna o coletor de lixo possível, porque ele pode mover objetos no heap sem invalidar nada. Ganha-se também o ecossistema corporativo que vem junto: Java EE padronizou transação, mensageria, persistência e componentes distribuídos, e o Spring ofereceu uma alternativa mais leve à mesma lista de problemas.
Paga-se em três moedas. A indireção da JVM custa tempo de inicialização e consumo de memória — problema real o bastante para existir o GraalVM native image, que compila antecipadamente para binário nativo e inverte exatamente essas duas grandezas. O coletor automático custa pausas em momentos que você não escolhe. E a verbosidade é concreta: foram precisas as lambdas e a Stream API do Java 8 para escrever em uma linha uma transformação de coleção.
A formulação honesta para a prova: Java troca controle sobre a máquina por previsibilidade e portabilidade. Você não escolhe onde o objeto mora nem quando ele morre.
Como funciona
JDK, JRE e JVM são três círculos concêntricos. A JVM executa bytecode e
abriga o JIT e o coletor de lixo. O JRE é a JVM mais as bibliotecas padrão — o
suficiente para executar. O JDK é o JRE mais as ferramentas de desenvolvimento
(javac, jar, javadoc, jdb) — o necessário para compilar. O caminho é
.java → javac → .class → JVM → instruções nativas, com o JIT promovendo a
código de máquina os trechos mais executados. Para distribuir, empacota-se em
JAR, que é um ZIP com manifesto e aceita qualquer arquivo dentro, inclusive
sem relação com Java.
Os quatro pilares, cada um com uma frase que não serve para nenhum outro:
- Abstração — representar do objeto real apenas o que importa ao problema. É modelagem, não mecanismo de linguagem.
- Encapsulamento — reunir dado e comportamento no mesmo objeto e ocultar o
interior, expondo só um contrato. Em Java: atributo
private, acesso por método. - Herança — a subclasse recebe os membros da superclasse, pode acrescentar novos e pode sobrescrever os herdados. Alteração na classe-pai propaga para as filhas.
- Polimorfismo — a mesma assinatura produzindo comportamentos diferentes conforme o objeto que a recebe.
Herança simples, interfaces à vontade. Uma classe estende no máximo uma
outra — decisão tomada para evitar a ambiguidade do diamante — mas implementa
quantas interfaces quiser. É por isso que interfaces são descritas como a saída
para contornar a restrição de herança única, e é por isso que elas permitem
tratar polimorficamente classes sem parentesco algum: basta o tipo comum.
Desde o Java 8 a interface aceita método concreto (default e static), o que
permite evoluir uma interface já publicada sem quebrar quem a implementa. E todo
campo de interface é implicitamente public static final — atribuir valor a ele
depois nem compila.
Arrays e coleções. Em Java, int[][] é um array de arrays: cada linha é um
objeto próprio, e por isso linhas de tamanhos diferentes são legais. Nas
coleções, o que decide item é o contrato: List mantém ordem de inserção e
aceita repetição; Set recusa duplicata e o HashSet não promete ordem
nenhuma; Map associa chave única a valor. Passar um array (ou qualquer objeto)
a um método passa uma cópia da referência: reatribuir o parâmetro não afeta
o chamador, mas alterar o conteúdo apontado afeta.
Exceções. Tudo desce de Throwable, que se divide em Error (falha da JVM,
não se trata) e Exception. As checked — IOException, SQLException,
ClassNotFoundException — são condições externas previsíveis e o compilador
exige try/catch ou throws. As unchecked descem de RuntimeException
— NullPointerException, ArithmeticException,
ArrayIndexOutOfBoundsException — são defeitos de programação e o compilador
não cobra tratamento. O bloco finally executa em qualquer desfecho, com ou sem
exceção, e é onde se libera recurso.
Lambdas e streams entraram no Java 8, não saíram dele. Um stream encadeia
operações sobre uma sequência: filter seleciona, map aplica uma função a
todos os elementos, sorted ordena, reduce e collect fecham a conta. As
intermediárias são preguiçosas — nada executa até chegar uma terminal.
O ecossistema que o edital cobra junto. Em JDBC, PreparedStatement é
pré-compilado, é a escolha padrão e é a defesa contra injeção de SQL. Em
JPA/Hibernate, a fábrica é única e longa (SessionFactory,
EntityManagerFactory) e a sessão é curta, uma por unidade de trabalho;
@Version habilita o bloqueio otimista e @Transient exclui da persistência.
Em Java EE, o session bean é síncrono e o message-driven bean é o
assíncrono. No Spring, @Controller marca o componente web e @RequestMapping
diz qual requisição ele trata; o Spring Boot autoconfigura e embute o
servidor; o Spring Cloud traz os padrões de sistema distribuído, com
Eureka para descoberta e Zuul como gateway de camada 7.
O que decide os itens
Os quatro pilares, sem sobreposição — a distinção que decide mais itens que todas as outras somadas:
| se o enunciado fala em… | a palavra é |
|---|---|
| representar só o relevante, ignorar o resto | abstração |
| reunir dado e código, ocultar o interior, acesso por método | encapsulamento |
reaproveitar de uma classe-pai, extends, propagar alteração | herança |
| mesma assinatura, comportamentos diferentes | polimorfismo |
Sobrecarga × sobrescrita — metade dos itens de polimorfismo é só isto:
| o que muda | onde | resolvido em | |
|---|---|---|---|
| sobrecarga (overloading) | lista de parâmetros diferente | mesma classe | compilação — ligação estática |
| sobrescrita (overriding) | mesma assinatura, corpo novo | subclasse | execução — ligação tardia |
O tipo de retorno não entra na assinatura: dois métodos que só diferem nele não compilam.
Herança × interface:
extends | implements | |
|---|---|---|
| quantas | uma classe | várias interfaces |
| herda estado? | sim | não (só constantes) |
| serve para | reuso de implementação | contrato e tipo comum |
Polimorfismo não exige superclasse comum — basta tipo comum, que pode ser interface. E herança simples × múltipla se decide pelo número de superclasses diretas, não pelo número de membros efetivamente herdados.
Componente → papel, no ecossistema:
| é | não é | |
|---|---|---|
| Tomcat, Jetty | contêiner de servlets e JSP | servidor de aplicação; não roda EJB |
| GlassFish, WildFly, JBoss EAP | servidor de aplicação JEE completo | contêiner web apenas |
| Hibernate | implementação de ORM | a especificação (essa é a JPA) |
| Hibernate Envers | auditoria e versionamento de entidades | identidade, SSO, autenticação |
| JGroups | comunicação em grupo entre nós do cluster | gestão de usuários |
.htaccess | configuração por diretório do Apache | recurso do NGINX |
| Swagger | ferramental em torno da OpenAPI | a própria especificação |
| Eureka | descoberta de serviços por registro e heartbeat | gateway |
| Zuul | gateway de borda em camada 7 | balanceador de camada 4 |
| MapStruct | gera o mapeador em tempo de compilação | mapeador por reflexão em execução |
Retorno dos métodos JDBC — executeQuery() devolve ResultSet,
executeUpdate() devolve int com as linhas afetadas, execute() devolve
boolean.
Edições da plataforma — SE é a base e o desktop (Swing, AWT); EE é o servidor e a web (servlet, JSP, EJB, JPA) e é extensão da SE, não alternativa; ME é o dispositivo com memória, vídeo e processamento limitados, inclusive set-top box e leitor de blu-ray.
Checked × unchecked — checked descende de Exception sem passar por
RuntimeException e o compilador obriga a tratar; unchecked descende de
RuntimeException e não obriga. Divisão inteira por zero lança
ArithmeticException, que é unchecked; divisão de double por zero não lança
nada e devolve Infinity.
Nos itens com listagem de código, o que decide quase sempre é uma destas
cinco coisas: o caso-base da recursão (devolve 0 ou 1?); o nome enganoso da
variável do for-each (size continua sendo o elemento); print × println;
a++ × ++a; e a referência compartilhada quando um array é passado a um
método.
Números que caem
byte · short · int · long | 8 · 16 · 32 · 64 bits |
float · double | 32 · 64 bits |
char | 16 bits, UTF-16, sem sinal |
| tipos primitivos, ao todo | 8 (String não é um deles) |
faixa do int | −2³¹ a 2³¹−1 |
| classes que se pode estender · interfaces que se pode implementar | 1 · ilimitadas |
| índices de array | de 0 a length − 1 |
| níveis de acesso | 4: private → package (padrão) → protected → public |
capacidade inicial do ArrayList · do HashMap | 10 · 16, com fator de carga 0,75 |
| Java 5 | genéricos, enum, anotações, autoboxing |
| Java 8 | lambda, Stream API, método default em interface |
| Java 11 · 17 · 21 | versões LTS; 17 consolidou record e sealed, 21 trouxe virtual threads |
finally executa | sempre (salvo System.exit ou parada da JVM) |
Como a CEBRASPE derruba você aqui
Troca de termo — quase 40% dos itens errados, e metade deles entre os pilares
de OO. “Abstração é a propriedade que permite que um método de determinado
nome tenha comportamentos distintos” — é sobrecarga. “O encapsulamento em Java
consiste no princípio” pelo qual objetos de uma mesma superclasse invocam
métodos de mesma assinatura com comportamentos distintos — é polimorfismo. “Na
herança por especificação”, quando a subclasse apenas acrescenta e mantém
inalterada a superclasse — é herança por extensão. Polimorfismo entre objetos
“de superclasses diferentes” — sem tipo comum não há polimorfismo. “A herança é
do tipo simples” quando o item descreve uma classe com duas superclasses. A
outra metade troca o nome de um artefato pelo vizinho de prateleira:
@RequestClass no lugar de @RequestMapping; um executeUpdate “cujos valores
de retorno serão tratados por um ResultSet”; DriverManager.getConnection
recebendo a classe do driver em vez da URL JDBC; a Session descrita com a vida
da SessionFactory — “instanciado apenas uma vez na aplicação”, “durante todo o
tempo de execução”; Java que “utiliza ponteiros para o gerenciamento de
endereços de memória”, quando o que existe são referências; objetos alocados “no
heap nativo do sistema operacional”; extends apresentado como anotação, com
“a substituição dos caracteres #XPTO por @extends”. A tabela dos quatro
pilares é a defesa da primeira metade e a tabela de artefatos, a da segunda:
leia a definição, escolha você a palavra, confira o nome por último.
Inversão — um quarto dos itens errados. Uma regra da linguagem afirmada ao
contrário: Java “permite herança múltipla de classes” (de classes é simples; de
interfaces é múltipla); o polimorfismo com “métodos de assinaturas diferentes, e
a escolha do método ocorre em tempo de compilação”; a chamada polimórfica que
“tem sua invocação resolvida em tempo de compilação do código”, quando a ligação
é tardia; os membros da subclasse “automaticamente sobrepostos e eliminados
pelos da classe-pai Veiculo”, com a seta da herança ao contrário; “as
expressões lambda terem sido excluídas do Java 8”, quando foram introduzidas
nele. Dois produtos trocados de lugar: “a JSE é bastante utilizada no
desenvolvimento web, especialmente em aplicações que utilizam HTTP”. Um
comportamento revertido: a virtual thread que “bloqueia a platform thread
subjacente”, quando se desmonta da carrier; a imagem compilada em AOT que
“consomem mais memória e, como consequência, possuem um tempo de inicialização
maior”. E uma recomendação virada do avesso: o PreparedStatement cujo uso
“deve ser feito em casos específicos”, quando é o padrão, e classes que “devem
colaborar e trocar mensagens com a maior parte das outras classes do mesmo
sistema”, que é o oposto de baixo acoplamento. Diante de uma direção afirmada,
formule a direção oposta e veja qual das duas você consegue justificar.
Resultado de código alterado — um em cada nove. O valor anunciado é sempre
plausível, e é isso que engana. Foo.x = 20 sobre um campo de interface não
compila, mas o item diz que “apresentará 20 como resultado”. Um fatorial de 5
vale 120 e o item promete que “retornará o resultado numérico 20”. Um
for (int size : x) sobre {1, 3, 7, 22, 51} imprime 1372251, e o item diz
que o resultado “será 6”, porque você leu size como tamanho. A recursão de
fun(3) cujo caso-base devolve 0 soma 3 + 2 + 0 e vale 5, e o item conta com
um caso-base igual a 1. Nunca aceite o número: identifique primeiro o papel de cada trecho —
caso-base, escopo do bloco static, pré-incremento contra pós-incremento — e só
depois some.
Atribuição errada dentro do ecossistema. Ação real, produto errado:
“GlassFish e TomCat são exemplos de servidores de aplicação JEE que suportam a
tecnologia EJB” — o Tomcat é contêiner de servlets; o Envers que “permite
realizar gerenciamento de identidade por meio de JPA no WildFly ou JBoss” —
Envers faz auditoria de entidades; o JGroups “destinada a gerenciar grupos de
usuários com as respectivas aplicações em seus bancos de dados” — faz
comunicação em grupo entre nós; o .htaccess “utilizado de forma nativa no
servidor NGINX” — é do Apache; o Spring Boot que existe “a fim de otimizar seu
código, a partir de pequenos ajustes e trocas para deixar mais rápido o
resultado do código” — é autoconfiguração. Quando a função descrita for
plausível, confira se ela é daquele produto ou do vizinho de prateleira.
Generalização e restrição inventada. O mapeamento que “exige que todas as
propriedades dos objetos de origem e destino tenham exatamente o mesmo nome” —
convenção sobre configuração admite configuração explícita. O bloqueio otimista
que atua “independentemente de controle de versão aplicado à entidade” — sem
@Version não há o que comparar. A subclasse que pode acrescentar membros
“desde que mantenha intactos os membros herdados” — pode sobrescrever. “A
inicialização de um array bidimensional deve ter todos os elementos com as
mesmas quantidades” — em Java o array de arrays é irregular por natureza. E as
instâncias que compartilham os atributos da classe “assim como os conteúdos
desses atributos” — a estrutura é da classe, o estado é de cada objeto. Diante de todo, sempre, apenas,
independentemente de e desde que, procure o caso que a palavra exclui.
Causa inventada. Pouco frequente, mas limpa quando aparece: o código “não será compilado, pois há dois métodos construtores” — dois construtores são sobrecarga, e sobrecarga é legal; a empresa terá problemas “devido ao fato de o Hibernate ser considerado inapropriado para a execução de trabalhos em uma arquitetura altamente escalável” — a premissa é falsa. Item que justifica um porquê pede segunda leitura no porquê, não na conclusão.
Erros clássicos
Achar que a JVM compila e o JDK executa. É o contrário: o javac, que vem no
JDK, compila para bytecode; a JVM executa o bytecode. Quem só vai executar não
precisa do JDK. E a portabilidade está no bytecode — a JVM é justamente a parte
que existe em versão diferente para cada plataforma.
Achar que Java tem ponteiros. Tem referências: sem aritmética de endereço, sem conversão para inteiro, sem liberação manual.
Confundir o heap da JVM com o heap do sistema operacional. O coletor gerencia a memória da própria máquina virtual. Item que fale em coletar objetos “no heap nativo do sistema operacional” trocou a camada.
Confundir sobrecarga com sobrescrita. Parâmetros diferentes é sobrecarga, decidida na compilação; assinatura idêntica na subclasse é sobrescrita, decidida na execução. Se o item juntar “polimorfismo” e “tempo de compilação” na mesma frase, um dos dois está fora do lugar.
Achar que Set guarda a ordem de inserção. O HashSet não guarda ordem
nenhuma; o que ele garante é ausência de duplicatas. Ordem de inserção é do
LinkedHashSet, ordenação é do TreeSet.
Tratar NullPointerException como checked. Tudo que desce de
RuntimeException é unchecked. O compilador só cobra tratamento das checked.
Esquecer que passar um array a um método passa a referência. O método recebe
uma cópia da referência, não do conteúdo: alterar A[0] lá dentro altera o array
do chamador.
Chamar Session de objeto único da aplicação. Único é a SessionFactory; a
Session nasce e morre com a unidade de trabalho.
Confundir o @Transient da JPA com o transient da linguagem. A anotação
exclui da persistência; a palavra reservada exclui da serialização.
LidoPraticado