Orientação a Objetos com Java: conceitos essenciais para concursos

Por
Publicado em
10 min. de leitura

Fala aí, Padawan! Hoje vamos mergulhar em um dos assuntos mais importantes para quem estuda programação e Tecnologia da Informação para concursos públicos: Programação Orientada a Objetos, ou POO, utilizando Java. Esse tema aparece com muita frequência em provas porque envolve conceitos fundamentais como classes, objetos, encapsulamento, herança, polimorfismo, abstração, interfaces e métodos. A organização deste artigo segue a linha didática do material de referência sobre complexidade assintótica, partindo dos conceitos básicos, passando por exemplos práticos e chegando a questões para fixação.

Terças e quintas de TI. Conteúdo prático e artigos especializados para acelerar seu conhecimento. Acesse agora!

O que é Programação Orientada a Objetos?

A Programação Orientada a Objetos é um paradigma de programação, ou seja, uma maneira de organizar e construir programas. Em vez de pensar apenas em uma sequência de comandos que o computador deve executar, organizamos o sistema em torno de objetos, que representam elementos do mundo real ou conceitos importantes para o programa. Uma pessoa, um veículo, uma conta bancária ou até mesmo uma questão de concurso podem ser representados como objetos.

Imagine, por exemplo, um sistema de uma biblioteca. Poderíamos ter objetos representando livros, usuários, funcionários e empréstimos. Cada objeto possuiria seus próprios dados e comportamentos. Um objeto Livro, por exemplo, poderia possuir título, autor e número de páginas, além de comportamentos como emprestar ou devolver. Essa organização facilita a manutenção, a reutilização e a compreensão do código.

Classes: os moldes dos objetos

Em Java, normalmente criamos objetos a partir de classes. Uma classe funciona como uma espécie de molde ou projeto. Pense na planta de uma casa: a planta não é a casa propriamente dita, mas define como as casas construídas a partir dela deverão ser. De maneira semelhante, uma classe define quais características e comportamentos os seus objetos poderão possuir.

Observe esta classe simples:

public class Pessoa {

    String nome;

    int idade;

    void apresentar() {

        System.out.println("Meu nome é " + nome);

    }

}

Nesse exemplo, Pessoa é a classe. As variáveis nome e idade representam características das pessoas, enquanto apresentar() representa um comportamento. Em Programação Orientada a Objetos, essas características são normalmente chamadas de atributos, e os comportamentos são implementados por meio de métodos.

Objetos: as instâncias das classes

Se a classe é o molde, o objeto é aquilo que efetivamente construímos a partir desse molde. Tecnicamente, podemos dizer que um objeto é uma instância de uma classe.

Veja:

Pessoa aluno = new Pessoa();

aluno.nome = "Lucas";

aluno.idade = 25;

aluno.apresentar();

Na primeira linha, Pessoa aluno declara uma variável chamada aluno, capaz de referenciar objetos da classe Pessoa. A expressão new Pessoa() cria efetivamente um novo objeto. Depois disso, atribuímos valores aos atributos nome e idade. Finalmente, executamos o método apresentar() utilizando a notação aluno.apresentar().

Padawan, guarde esta relação porque ela é bastante explorada em provas: classe é o modelo; objeto é uma instância desse modelo.

Atributos: representando o estado do objeto

Os atributos armazenam informações relacionadas ao objeto. Em uma classe ContaBancaria, por exemplo, poderíamos possuir atributos como número da conta, titular e saldo. O conjunto de valores dos atributos em determinado momento representa o estado do objeto.

Veja:

public class ContaBancaria {

    String titular;

    double saldo;

}

Se criarmos duas contas diferentes, ambas serão objetos da mesma classe, mas poderão possuir estados diferentes.

ContaBancaria conta1 = new ContaBancaria();

conta1.titular = "Ana";

conta1.saldo = 1000.0;

ContaBancaria conta2 = new ContaBancaria();

conta2.titular = "Carlos";

conta2.saldo = 500.0;

Os objetos conta1 e conta2 pertencem à mesma classe, mas possuem valores diferentes em seus atributos.

Métodos: representando comportamentos

Um método representa uma ação que pode ser executada por um objeto ou pela própria classe. Podemos pensar nele como uma função que pertence a uma classe.

Vamos melhorar nossa conta bancária:

public class ContaBancaria {

    String titular;

    double saldo;

    void depositar(double valor) {

        saldo = saldo + valor;

    }

    void exibirSaldo() {

        System.out.println("Saldo: " + saldo);

    }

}

O método depositar() recebe um valor e o adiciona ao saldo. Já exibirSaldo() mostra o saldo atual. Dessa maneira, além de possuir informações, os objetos também sabem executar determinadas operações.

Construtores: preparando o objeto

Um construtor é um elemento especial utilizado no momento em que um objeto é criado. Em Java, o construtor possui o mesmo nome da classe e não declara tipo de retorno, nem mesmo void.

Observe:

public class Pessoa {

    String nome;

    int idade;

    public Pessoa(String nome, int idade) {

        this.nome = nome;

        this.idade = idade;

    }

}

Agora podemos criar uma pessoa assim:

Pessoa pessoa = new Pessoa("Maria", 30);

Quando executamos new Pessoa(“Maria”, 30), o construtor é chamado e inicializa os atributos. A palavra-chave this representa o objeto atual. Portanto, this.nome significa “o atributo nome deste objeto”.

Uma pegadinha comum em concursos é afirmar que construtores possuem retorno void. Isso está errado. Construtores não possuem tipo de retorno.

Encapsulamento: protegendo os dados

O encapsulamento é um dos pilares mais importantes da orientação a objetos. Sua ideia central é esconder detalhes internos de uma classe e controlar como seus dados podem ser acessados ou modificados.

Em Java, isso é normalmente realizado com modificadores de acesso, principalmente private, combinado com métodos públicos.

public class ContaBancaria {

    private double saldo;

    public void depositar(double valor) {

        if (valor > 0) {

            saldo += valor;

        }

    }

    public double getSaldo() {

        return saldo;

    }

}

Observe que saldo foi declarado como private. Isso significa que um código externo à classe não poderá modificar diretamente o atributo usando algo como conta.saldo = -5000. Para alterar o saldo, deverá utilizar um método autorizado pela classe.

O método depositar() ainda possui uma regra: somente adiciona valores positivos. O encapsulamento, portanto, não serve apenas para “esconder variáveis”; ele ajuda a proteger o estado interno do objeto e preservar suas regras de negócio.

Getters e setters

Os famosos getters e setters são métodos utilizados para acessar e modificar atributos encapsulados. Um getter normalmente devolve o valor de um atributo, enquanto um setter permite alterar esse valor.

public class Pessoa {

    private String nome;

    public String getNome() {

        return nome;

    }

    public void setNome(String nome) {

        this.nome = nome;

    }

}

O método getNome() devolve o atributo nome. Já setNome() recebe um novo valor e o atribui ao objeto.

Mas cuidado, Padawan: encapsulamento não significa obrigatoriamente criar getter e setter para todos os atributos. Em alguns casos, um atributo pode possuir apenas leitura ou sequer precisar ser acessado diretamente externamente.

Herança: reutilizando características

A herança permite criar uma classe baseada em outra classe existente. A classe mais específica pode aproveitar atributos e métodos da classe mais geral.

Considere:

public class Animal {

    public void dormir() {

        System.out.println("Animal dormindo");

    }

}

Podemos criar:

public class Cachorro extends Animal {

    public void latir() {

        System.out.println("Au au!");

    }

}

A palavra-chave extends estabelece a herança. Assim, Cachorro herda os comportamentos acessíveis de Animal.

Podemos fazer:

Cachorro cachorro = new Cachorro();

cachorro.dormir();

cachorro.latir();

Mesmo que dormir() não tenha sido declarado dentro da classe Cachorro, o método está disponível graças à herança.

Nesse relacionamento, Animal pode ser chamado de superclasse, classe-base ou classe-pai, enquanto Cachorro pode ser chamado de subclasse, classe derivada ou classe-filha.

Java permite herança múltipla de classes?

Aqui existe uma bela casca de banana de prova, Padawan. Java não permite herança múltipla entre classes.

Isto não é permitido:

class Filho extends Pai1, Pai2 {

}

Uma classe Java pode estender diretamente apenas uma outra classe. Porém, uma classe pode implementar diversas interfaces, assunto que veremos daqui a pouco.

Sobrescrita de métodos

A sobrescrita, ou method overriding, ocorre quando uma subclasse fornece uma nova implementação para um método que já foi definido em sua superclasse.

Veja:

public class Animal {

    public void emitirSom() {

        System.out.println("Som de animal");

    }

}

Agora:

public class Cachorro extends Animal {

    @Override

    public void emitirSom() {

        System.out.println("Au au!");

    }

}

A anotação @Override informa que estamos sobrescrevendo um método existente na superclasse.

Se executarmos:

Cachorro cachorro = new Cachorro();

cachorro.emitirSom();

Teremos:

Au au!

A implementação utilizada será aquela definida pela classe Cachorro.

Polimorfismo: várias formas para uma mesma referência

O termo polimorfismo significa, de maneira simplificada, “muitas formas”. Na orientação a objetos, ele permite tratar objetos de diferentes classes relacionadas por meio de um tipo mais geral.

Considere:

Animal animal = new Cachorro();

Observe algo interessante. A variável possui tipo Animal, mas o objeto criado é um Cachorro.

Agora:

animal.emitirSom();

Se Cachorro sobrescreveu o método emitirSom(), a implementação executada será a de Cachorro.

Esse comportamento é conhecido como polimorfismo de subtipo, associado à herança e à sobrescrita de métodos.

Um exemplo completo de polimorfismo

Veja estas classes:

class Animal {

    public void emitirSom() {

        System.out.println("Som genérico");

    }

}

class Cachorro extends Animal {

    @Override

    public void emitirSom() {

        System.out.println("Au au!");

    }

}

class Gato extends Animal {

    @Override

    public void emitirSom() {

        System.out.println("Miau!");

    }

}

Podemos escrever:

Animal animal1 = new Cachorro();

Animal animal2 = new Gato();

animal1.emitirSom();

animal2.emitirSom();

A saída será:

Au au!

Miau!

Linha por linha, temos o seguinte raciocínio:

Animal animal1 = new Cachorro(); cria um objeto Cachorro, mas o referencia utilizando o tipo mais geral Animal.

Animal animal2 = new Gato(); faz o mesmo com um objeto Gato.

animal1.emitirSom(); chama o método do objeto real, que é um Cachorro.

animal2.emitirSom(); chama a implementação da classe Gato.

É justamente essa capacidade de um mesmo comando resultar em comportamentos diferentes que torna o polimorfismo tão útil.

Abstração: concentrando-se no essencial

A abstração consiste em representar apenas as características relevantes de determinado elemento, escondendo detalhes que não precisam ser conhecidos por quem o utiliza.

Imagine dirigir um carro. Para acelerar, você pressiona o pedal. Você não precisa conhecer todos os cálculos da central eletrônica, o funcionamento completo da injeção de combustível ou todos os detalhes mecânicos envolvidos. O mecanismo complexo foi abstraído atrás de uma operação simples.

Em Java, podemos aplicar abstração usando, entre outros recursos, classes abstratas e interfaces.

Classes abstratas

Uma classe abstrata é declarada utilizando a palavra-chave abstract. Ela pode servir como uma estrutura comum para outras classes e não pode ser instanciada diretamente.

Exemplo:

public abstract class Funcionario {

    private String nome;

    public Funcionario(String nome) {

        this.nome = nome;

    }

    public String getNome() {

        return nome;

    }

    public abstract double calcularSalario();

}

Observe o método:

public abstract double calcularSalario();

Ele não possui implementação nessa classe. Isso significa que uma subclasse concreta deverá fornecer a implementação apropriada.

Por exemplo:

public class FuncionarioCLT extends Funcionario {

    private double salarioMensal;

    public FuncionarioCLT(String nome, double salarioMensal) {

        super(nome);

        this.salarioMensal = salarioMensal;

    }

    @Override

    public double calcularSalario() {

        return salarioMensal;

    }

}

A chamada super(nome) executa o construtor da superclasse Funcionario.

Outra pegadinha importante: uma classe abstrata pode possuir métodos concretos, isto é, métodos completamente implementados. Ela não precisa ser formada apenas por métodos abstratos.

Interfaces

Uma interface funciona como um contrato. Ela especifica comportamentos que determinada classe se compromete a oferecer.

Veja:

public interface Autenticavel {

    boolean autenticar(String senha);

}

Uma classe pode implementar essa interface:

public class Usuario implements Autenticavel {

    private String senha;

    public Usuario(String senha) {

        this.senha = senha;

    }

    @Override

    public boolean autenticar(String senha) {

        return this.senha.equals(senha);

    }

}

A palavra-chave utilizada é implements.

Podemos testar:

Usuario usuario = new Usuario(“1234”);

System.out.println(usuario.autenticar("1234"));

O resultado será:

true

Uma vantagem importante das interfaces em Java é que uma classe pode implementar mais de uma interface:

class Usuario implements Autenticavel, Registravel {

}

Portanto, cuidado com a questão clássica: Java não possui herança múltipla de classes, mas permite que uma classe implemente múltiplas interfaces.

Sobrecarga versus sobrescrita

Aqui temos uma das comparações favoritas das bancas: sobrecarga e sobrescrita.

A sobrecarga, ou overloading, acontece quando existem métodos com o mesmo nome, mas com listas de parâmetros diferentes.

Exemplo:

public class Calculadora {

    public int somar(int a, int b) {

        return a + b;

    }

    public double somar(double a, double b) {

        return a + b;

    }

    public int somar(int a, int b, int c) {

        return a + b + c;

    }

}

Todos os métodos se chamam somar, mas possuem parâmetros diferentes. Isso é sobrecarga.

Já a sobrescrita, ou overriding, envolve herança e ocorre quando uma subclasse redefine um método herdado.

Para memorizar:

Sobrecarga → mesmo nome, parâmetros diferentes.

Sobrescrita → subclasse redefine comportamento herdado.

Outro detalhe importante: apenas mudar o tipo de retorno não é suficiente para realizar sobrecarga.

Associação, agregação e composição

Além da herança, objetos podem se relacionar de outras maneiras. Uma associação representa uma relação entre objetos. Um professor, por exemplo, pode estar associado a uma disciplina.

A agregação representa uma relação entre todo e parte na qual a parte pode existir independentemente do todo. Por exemplo, uma equipe possui jogadores, mas um jogador pode continuar existindo mesmo que determinada equipe deixe de existir.

Já a composição representa um relacionamento mais forte. A parte costuma ter seu ciclo de vida fortemente dependente do objeto principal. Em uma modelagem, por exemplo, uma casa pode ser composta por cômodos pertencentes àquela casa.

Essas diferenças aparecem bastante em questões teóricas de UML e Orientação a Objetos.

Os principais pilares da Orientação a Objetos

Para a sua prova, Padawan, memorize especialmente estes quatro conceitos:

Abstração: representa apenas os aspectos importantes de um objeto.

Encapsulamento: protege detalhes internos e controla o acesso ao estado do objeto.

Herança: permite criar classes especializadas a partir de classes mais gerais.

Polimorfismo: permite utilizar uma referência mais geral para trabalhar com objetos de diferentes tipos relacionados, produzindo comportamentos específicos.

Dependendo do autor e da banca, você poderá encontrar pequenas diferenças na forma de organizar esses conceitos, mas esses quatro aparecem com enorme frequência em materiais sobre POO.

Exemplo prático completo

Vamos criar um pequeno sistema de funcionários:

abstract class Funcionario {

    private String nome;

    public Funcionario(String nome) {

        this.nome = nome;

    }

    public String getNome() {

        return nome;

    }

    public abstract double calcularSalario();

}

class Analista extends Funcionario {

    private double salario;

    public Analista(String nome, double salario) {

        super(nome);

        this.salario = salario;

    }

    @Override

    public double calcularSalario() {

        return salario;

    }

}

class Gerente extends Funcionario {

    private double salario;

    private double bonus;

    public Gerente(String nome, double salario, double bonus) {

        super(nome);

        this.salario = salario;

        this.bonus = bonus;

    }

    @Override

    public double calcularSalario() {

        return salario + bonus;

    }

}

public class Principal {

    public static void main(String[] args) {

        Funcionario f1 = new Analista("Ana", 7000);

        Funcionario f2 = new Gerente("Carlos", 10000, 2000);

        System.out.println(f1.getNome() + ": " + f1.calcularSalario());

        System.out.println(f2.getNome() + ": " + f2.calcularSalario());

    }

}

Vamos destrinchar os pontos mais importantes.

abstract class Funcionario cria uma classe abstrata que servirá como base para diferentes tipos de funcionário.

private String nome aplica encapsulamento ao atributo.

O construtor Funcionario(String nome) permite inicializar o nome do funcionário.

O método abstrato calcularSalario() obriga subclasses concretas a definir sua própria maneira de calcular o salário.

Analista extends Funcionario cria uma relação de herança.

Gerente extends Funcionario também cria uma especialização de Funcionario.

A anotação @Override mostra que estamos sobrescrevendo calcularSalario().

Finalmente:

Funcionario f1 = new Analista(“Ana”, 7000);

Funcionario f2 = new Gerente(“Carlos”, 10000, 2000);

temos polimorfismo. As referências são do tipo Funcionario, mas os objetos pertencem às classes Analista e Gerente.

Quando calcularSalario() é chamado, cada objeto executa sua própria implementação.

Agora chegou a parte que interessa, Padawan! Vamos testar o que aprendemos. Que a força esteja com você!

(2024) CEBRASPE – 2024 – CNJ – Analista Judiciário – Área Apoio Especializado – Especialidade: Analista de Sistemas

Acerca das tecnologias que atendem a PDPJ-Br, julgue o próximo item.

Em Java, uma subclasse herda os membros, campos e métodos de sua superclasse direta, podendo acrescentar novos membros, desde que mantenha intactos os membros herdados. 

Gabarito: Errado.

Justificativa: Em Java, uma subclasse pode herdar membros de sua superclasse, mas não precisa manter intactos todos os métodos herdados. Ela pode sobrescrever (override) métodos herdados para fornecer uma nova implementação. Além disso, membros private da superclasse não são herdados diretamente pela subclasse.

(2024) CEBRASPE – 2024 – CNJ – Técnico Judiciário – Área Apoio Especializado – Especialidade: Programação de Sistemas

Julgue o item seguinte, relativos a Java.

Em Java, as classes possuem herança múltipla, de modo que cada classe pode ser derivada de mais de uma superclasse direta. 

Gabarito: Errado.

Justificativa: Java não permite herança múltipla de classes. Uma classe pode estender diretamente apenas uma única superclasse, utilizando a palavra-chave extends.

(2025) CEBRASPE- BANRISUL – Técnico em Tecnologia da Informação II – Desenvolvimento de Software

Assinale a opção em que é citada a propriedade da programação orientada a objetos evidenciada no código Java precedente. 

public class aluno { 

private String nome; 

private int idade; 

public String getNome() { return nome; } 

public void setNome(String nome) {

this.nome = nome; }

public int getIdade() { return idade; } 

public void setIdade(int idade) {

if (idade > 0) { this.idade = idade; }

 } 

}
  1. composição (composition
  2. Interface
  3. herança 
  4. polimorfismo 
  5. encapsulamento

Gabarito: E) Encapsulamento.

Justificativa: O código protege os atributos nome e idade ao declará-los como private, impedindo o acesso direto por outras classes. O acesso e a alteração desses valores ocorrem por meio dos métodos públicos getNome(), setNome(), getIdade() e setIdade().

Gabarito: E) encapsulamento.

Justificativa:
O código evidencia o encapsulamento porque os atributos nome e idade foram declarados como private. Isso impede que outras classes acessem ou alterem esses dados diretamente. O acesso ocorre por meio dos métodos públicos getters e setters, como getNome(), setNome(), getIdade() e setIdade().

Análise das alternativas

A) Composição — Errada.
Composição é uma relação do tipo “tem um”, em que um objeto contém outro objeto como parte de sua estrutura. Exemplo: uma classe Casa contendo objetos da classe Comodo. O código apresentado não mostra nenhuma relação entre objetos desse tipo.

B) Interface — Errada.
Uma interface é declarada em Java com a palavra-chave interface e normalmente define um contrato que uma classe pode implementar com implements. No código, temos apenas uma classe chamada aluno, sem nenhuma interface.

C) Herança — Errada.
Herança ocorre quando uma classe deriva de outra, normalmente usando a palavra-chave extends. Como o código não apresenta nenhuma classe sendo estendida, não há herança.

D) Polimorfismo — Errada.
Polimorfismo permite que uma mesma referência ou método apresente comportamentos diferentes conforme o objeto utilizado. Normalmente aparece associado à herança, sobrescrita de métodos ou interfaces. Nada disso está sendo demonstrado no código.

E) Encapsulamento — Correta.
Os atributos foram declarados como private e são acessados por métodos públicos. Além disso, o método setIdade() controla a alteração do atributo, permitindo apenas valores maiores que zero:

if (idade > 0) {

    this.idade = idade;

}

Isso demonstra justamente a ideia de proteger o estado interno do objeto e controlar seu acesso.

(2024) CEBRASPE – 2024 – CNJ – Técnico Judiciário – Área Apoio Especializado – Especialidade: Programação de Sistemas

Julgue o item seguinte, relativos a Java.


O polimorfismo em Java permite processar objetos que derivam da mesma superclasse, direta ou indiretamente; cada objeto pode executar ações diferentes a partir da mesma chamada. 

Gabarito: Certo.

Justificativa:
O polimorfismo em Java permite que objetos de classes diferentes, mas relacionadas por uma mesma superclasse ou interface, sejam tratados por um tipo comum. Quando um método é sobrescrito, a mesma chamada pode executar comportamentos diferentes conforme o objeto real referenciado.

Por
Publicado em
10 min. de leitura