Hoje, a maioria das linguagens de programação são orientadas a objetos como Java, C#, Python e C++ e, apesar de terem algumas diferenças na implementação, todas seguem os mesmos princípios e conceitos. Muitos programadores, apesar de utilizarem linguagens orientadas a objetos, não sabem utilizar alguns dos principais conceitos desse paradigma orientado a objetos e, por isso, desenvolvem sistemas com alguns erros conceituais e acabam escrevendo mais código que o necessário, não conseguindo reutilizar o código como seria possível.

Este artigo fará uma revisão dos principais conceitos de orientação a objetos e mostrará diversos exemplos de implementação dos conceitos de orientação a objetos em Java. Os principais conceitos do paradigma orientado a objetos são Classes e Objetos, Associação,Encapsulamento, Herança e Polimorfismo.

Já viu as novidades que a DevMedia preparou nos cursos de Java? Confira!

Classe e Objeto

Uma classe é uma forma de definir um tipo de dado em uma linguagem orientada a objeto. Ela é formada por dados e comportamentos.

Para definir os dados são utilizados os atributos, e para definir o comportamento são utilizados métodos. Depois que uma classe é definida podem ser criados diferentes objetos que utilizam a classe. A Listagem 1 mostra a definição da classe Empresa, que tem os atributos nome, endereço, CNPJ, data de fundação, faturamento, e também o método imprimir, que apenas mostra os dados da empresa.

package com.devmedia.model;
 
import java.util.Date;
 
public class Empresa {
 
      private String nome;
      private String cnpj;
      private String endereco;
      private Date dataFundacao;
      private float faturamento;
      
      public void imprimir() {
            System.out.println("Nome: " + nome);
            System.out.println("CNPJ: " + cnpj);
            System.out.println("Endereço: " + endereco);
            System.out.println("Data de Fundação: " + dataFundacao);
            
      }
      
      public String getNome() {
            return nome;
      }
      public void setNome(String nome) {
            this.nome = nome;
      }
      public String getCnpj() {
            return cnpj;
      }
      public void setCnpj(String cnpj) {
            this.cnpj = cnpj;
      }
      public String getEndereco() {
            return endereco;
      }
      public void setEndereco(String endereco) {
            this.endereco = endereco;
      }
      public Date getDataFundacao() {
            return dataFundacao;
      }
      public void setDataFundacao(Date dataFundacao) {
            this.dataFundacao = dataFundacao;
      }
      public float getFaturamento() {
            return faturamento;
      }
      public void setFaturamento(float faturamento) {
            this.faturamento = faturamento;
      }
      
}
Listagem 1. Classe Empresa

Com a classe definida, podem ser criados diversos objetos do tipo Empresa, por isso a Listagem 2 mostra como criar esses objetos, bastando declarar uma variável com o tipo Empresa e com a palavra reservada new criar um novo objeto. Depois podemos definir os dados para os atributos da classe Empresa e, por fim, chamar o método definido.

package com.devmedia.main;
 
import java.util.Date;
 
import com.devmedia.model.Empresa;
 
public class Main {
 
      public static void main(String[] args) {
            
            Empresa empresa1 = new Empresa();
            empresa1.setNome("Loja 1");
            empresa1.setCnpj("12343232");
            empresa1.setDataFundacao(new Date());
            empresa1.setEndereco("Rua abc, 100");
            empresa1.setFaturamento(50000);
empresa1.imprimir();
            
            Empresa empresa2 = new Empresa();
            empresa2.setNome("Loja 2");
            empresa2.setCnpj("12354432");
            empresa2.setDataFundacao(new Date());
            empresa2.setEndereco("Rua abc, 200");
            empresa2.setFaturamento(50000);
empresa2.imprimir();
 
Empresa empresa3 = new Empresa();
            empresa3.setNome("Posto de Gasolina");
            empresa3.setCnpj("12345434");
            empresa3.setEndereco("Rua afd, 500");
            empresa3.setFaturamento(10000);
            empresa3.setDataFundacao(new Date());
            empresa3.imprimir();
 
      }
 
}
Listagem 2. Definição dos objetos do tipo Empresa

Encapsulamento

O conceito do encapsulamento consiste em “esconder” os atributos da classe de quem for utilizá-la. Isso se deve por dois motivos principais.

Um é para que alguém que for usar a classe não a use de forma errada como, por exemplo, em uma classe que tem um método de divisão entre dois atributos da classe - se o programador java não conhecer a implementação interna da classe, ele pode colocar o valor zero no atributo do dividendo, mas se a classe estiver corretamente encapsulada podemos impedir que o programador faça isso. Esse tipo de implementação é feito via os métodos get e set. A Listagem 3 mostra o código da classe Divisao citada como exemplo.

package com.devmedia.model;
 
public class Divisao {
      
      private int num1;
      private int num2;
      
      public void divisao() {
            System.out.println("A divisao e: " + (num1 / num2));
      }
 
      public int getNum1() {
            return num1;
      }
 
      public void setNum1(int num1) {
            this.num1 = num1;
      }
 
      public int getNum2() {
            return num2;
      }
 
      public void setNum2(int num2) {
            if (num2 == 0) {
                  num2 = 1;
            } else {
                  this.num2 = num2;
            }
      }
      
}
Listagem 3. Classe Divisao

O outro motivo é de manter todo o código de uma determinada classe encapsulada dentro dela mesmo como, por exemplo, se existe uma classe Conta, talvez seja melhor não permitir que um programador acesse o atributo saldo diretamente, nem mesmo com os métodos get e set, mas somente por operações, como saque, depósito e saldo. A Listagem 4 mostra a implementação da classe Conta, onde só é possível acessar a atributo saldo pelas operações disponibilizadas na classe e os outros atributos podem ser acessados via métodos get e set.

package com.devmedia.model;
 
public class Conta {
      
      private String agencia;
      private String numero;
      private float saldo;
      
      public void saque(float valor) {
            if (saldo < valor) {
                  System.out.println("O saldo é insuficiente!");
            } else {
                  saldo -= valor;
            }
      }
      
      public String getAgencia() {
            return agencia;
      }
 
      public void setAgencia(String agencia) {
            this.agencia = agencia;
      }
 
      public String getNumero() {
            return numero;
      }
 
      public void setNumero(String numero) {
            this.numero = numero;
      }
 
      public void deposito(float valor) {
            saldo += valor;
      }
      
      public void saldo() {
            System.out.println("O saldo é: " + saldo);
      }
 
}
Listagem 4. Classe Conta

Associação de Classes

A associação de classes indica quando uma classe tem um tipo de relacionamento "tem um" com outra classe como, por exemplo, uma pessoa tem um carro e isso indica que a classe Pessoa tem uma associação com a classe Carro. Esse tipo de relacionamento entre classes é importante, porque define como as classes interagem entre elas nas aplicações.

A Listagem 5 mostra como implementar uma associação um para um. A associação é feita entre as classes Pessoa e Carro. A classe Carro tem os atributos modelo, placa, ano e valor; e a classe Pessoa tem os atributos nome, endereço, telefone e dataNascimento. Além disso, ela tem um relacionamento com a classe Carro.

package com.devmedia.model;
 
public class Carro {
      
      private String modelo;
      private String placa;
      private int ano;
      private float valor;
      
      public String getModelo() {
            return modelo;
      }
      
      public void setModelo(String modelo) {
            this.modelo = modelo;
      }
      
      public String getPlaca() {
            return placa;
      }
      
      public void setPlaca(String placa) {
            this.placa = placa;
      }
      
      public int getAno() {
            return ano;
      }
      
      public void setAno(int ano) {
            this.ano = ano;
      }
      
      public float getValor() {
            return valor;
      }
      
      public void setValor(float valor) {
            this.valor = valor;
      }
 
}
 
package com.devmedia.model;
 
import java.util.Date;
 
public class Pessoa {
      
      private String nome;
      private String endereco;
      private String telefone;
      private Date dataNascimento;
      
      // Relacionamento com a classe Carro
      private Carro carro;
 
      public String getNome() {
            return nome;
      }
 
      public void setNome(String nome) {
            this.nome = nome;
      }
 
      public String getEndereco() {
            return endereco;
      }
 
      public void setEndereco(String endereco) {
            this.endereco = endereco;
      }
 
      public String getTelefone() {
            return telefone;
      }
 
      public void setTelefone(String telefone) {
            this.telefone = telefone;
      }
 
      public Date getDataNascimento() {
            return dataNascimento;
      }
 
      public void setDataNascimento(Date dataNascimento) {
            this.dataNascimento = dataNascimento;
      }
 
      public Carro getCarro() {
            return carro;
      }
 
      public void setCarro(Carro carro) {
            this.carro = carro;
      }
 
}
Listagem 5. Associação entre as classes Pessoa e Carro

Além do relacionamento um para um, também existem o relacionamento um para N e N para N. Esses relacionamentos são modelados com um vetor de objetos de uma classe para outro como, por exemplo, se uma pessoa pode ter mais de um carro, é necessário mapear esse relacionamento com uma lista de carros na classe Pessoa. A Listagem 6 mostra a alteração necessária na classe Pessoa, mas na classe Carro não é necessária nenhuma alteração.

package com.devmedia.model;
 
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
 
public class Pessoa {
      
      private String nome;
      private String endereco;
      private String telefone;
      private Date dataNascimento;
      
      // Relacionamento com a classe Carro
      private List<Carro> carros = new ArrayList<Carro>();
 
      public String getNome() {
            return nome;
      }
 
      public void setNome(String nome) {
            this.nome = nome;
      }
 
      public String getEndereco() {
            return endereco;
      }
 
      public void setEndereco(String endereco) {
            this.endereco = endereco;
      }
 
      public String getTelefone() {
            return telefone;
      }
 
      public void setTelefone(String telefone) {
            this.telefone = telefone;
      }
 
      public Date getDataNascimento() {
            return dataNascimento;
      }
 
      public void setDataNascimento(Date dataNascimento) {
            this.dataNascimento = dataNascimento;
      }
 
      public List<Carro> getCarros() {
            return carros;
      }
 
      public void setCarros(List<Carro> carros) {
            this.carros = carros;
      }
 
}
Listagem 6. Classe Pessoa com relacionamento um para N

Herança

A herança é um tipo de relacionamento que define que uma classe "é um" de outra classe como, por exemplo, a classe Funcionario que é uma Pessoa, assim um Funcionário tem um relacionamento de herança com a classe Pessoa. Em algumas linguagens, como C, é possível fazer herança múltipla, isto é, uma classe pode herdar de diversas outras classes, mas em Java isso não é permitido, pois cada classe pode herdar de apenas outra classe.

A Listagem 7 mostra como implementar a herança entre classes: a classe Pessoa definida será utilizada como base para as outras classes. Será definida a classe Funcionario, com os atributos salario e matricula, e a classe Aluno com os atributos registroAcademico e curso. Como é possível observar, para implementar herança em Java usaremos a palavra reservada extends.

package com.devmedia.model;
 
public class Funcionario extends Pessoa {
      
      private float salario;
      private String matricula;
      
      public float getSalario() {
            return salario;
      }
      
      public void setSalario(float salario) {
            this.salario = salario;
      }
      
      public String getMatricula() {
            return matricula;
      }
      
      public void setMatricula(String matricula) {
            this.matricula = matricula;
      }
 
}
 
package com.devmedia.model;
 
public class Aluno extends Pessoa {
      
      private String registroAcademico;
      private String curso;
      private float mensalidade;
      
      public String getRegistroAcademico() {
            return registroAcademico;
      }
      
      public void setRegistroAcademico(String registroAcademico) {
            this.registroAcademico = registroAcademico;
      }
      
      public String getCurso() {
            return curso;
      }
      
      public void setCurso(String curso) {
            this.curso = curso;
      }
      
      public float getMensalidade() {
            return mensalidade;
      }
      
      public void setMensalidade(float mensalidade) {
            this.mensalidade = mensalidade;
      }
      
}
Listagem 7. Classes Funcionario e Aluno, filhas da classe Pessoa

Sobre a herança em Java é importante saber que existem quatro tipos de acesso aos atributos:

  1. public: que permite que métodos e atributos sejam acessados diretamente de qualquer classe;
  2. private: que permite que métodos e atributos sejam acessados apenas dentro da classe;
  3. protected: que permite que métodos e atributos sejam acessados apenas dentro da própria classe e em classes filhas;
  4. tipo de acesso padrão, que permite que métodos e atributos sejam acessadas por qualquer classes que esteja no mesmo pacote.

Como usamos a classe Pessoa com herança, uma possibilidade é mudar o tipo de acesso dos atributos da classe Pessoa para protegido. A Listagem 8 mostra a alteração feita.

package com.devmedia.model;
 
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
 
public class Pessoa {
      
      protected String nome;
      protected String endereco;
      protected String telefone;
      protected Date dataNascimento;
      
      // Relacionamento com a classe Carro
      protected List<Carro> carros = new ArrayList<Carro>();
 
      public String getNome() {
            return nome;
      }
 
      public void setNome(String nome) {
            this.nome = nome;
      }
 
      public String getEndereco() {
            return endereco;
      }
 
      public void setEndereco(String endereco) {
            this.endereco = endereco;
      }
 
      public String getTelefone() {
            return telefone;
      }
 
      public void setTelefone(String telefone) {
            this.telefone = telefone;
      }
 
      public Date getDataNascimento() {
            return dataNascimento;
      }
 
      public void setDataNascimento(Date dataNascimento) {
            this.dataNascimento = dataNascimento;
      }
 
      public List<Carro> getCarros() {
            return carros;
      }
 
      public void setCarros(List<Carro> carros) {
            this.carros = carros;
      }
 
}
Listagem 8. Classe Pessoa com os atributos protegidos

Polimorfismo

O Polimorfismo é a possibilidade de em uma hierarquia de classes implementar métodos com a mesma assinatura e, assim, implementar um mesmo código que funcione para qualquer classe dessa hierarquia sem a necessidade de implementações específicas para cada classe. O principal objetivo do polimorfismo é diminuir a quantidade de código escrito, aumentando a clareza e a facilidade de manutenção.

A Listagem 9 mostra um exemplo de polimorfismo, onde a classe Conta tem um método saque que apenas permite o saque caso o cliente tenha saldo em sua conta. A classe ContaEspecial, que é filha da classe Conta, permite que o cliente faça um saque caso tenha saldo ou tenha ainda limite no cheque especial, e a classe ContaPoupanca permite que o cliente faça um saque caso ainda tenha saldo na conta corrente ou saldo na conta poupança.

package com.devmedia.model;
 
public class Conta {
      
      protected String agencia;
      protected String numero;
      protected float saldo;
      
      public void saque(float valor) {
            if (saldo < valor) {
                  System.out.println("O saldo é insuficiente!");
            } else {
                  saldo -= valor;
System.out.println("Saque efetuado com sucesso!");
 
            }
      }
      
      public String getAgencia() {
            return agencia;
      }
 
      public void setAgencia(String agencia) {
            this.agencia = agencia;
      }
 
      public String getNumero() {
            return numero;
      }
 
      public void setNumero(String numero) {
            this.numero = numero;
      }
 
      public void deposito(float valor) {
            saldo += valor;
      }
      
      public void saldo() {
            System.out.println("O saldo é: " + saldo);
      }
 
}
 
package com.devmedia.model;
 
public class ContaEspecial extends Conta {
      
      private float limite;
 
      public void saque(float valor) {
            if (saldo + limite < valor) {
                  System.out.println("O saldo é insuficiente!");
            } else {
                  saldo -= valor;
System.out.println("Saque efetuado com sucesso!");
 
            }
      }
 
      public float getLimite() {
            return limite;
      }
 
      public void setLimite(float limite) {
            this.limite = limite;
      }
 
}
 
package com.devmedia.model;
 
public class ContaPoupanca extends Conta {
      
      private float saldoPoupanca;
      
      public void saque(float valor) {
            if (saldo + saldoPoupanca < valor) {
                  System.out.println("O saldo é insuficiente!");
            } else {                
                  if (saldo < valor) {
                        valor -= saldo;
                        saldo = 0;
                        saldoPoupanca -= valor;
                  } else {
                        saldo -= valor;
                  }
System.out.println("Saque efetuado com sucesso!");
            }
      }
 
    public void depositoPoupanca(float valor) {
            saldoPoupanca += valor;
      }
 
    private float getSaldoPoupanca() {
            return saldoPoupanca;
      }
 
      private void setSaldoPoupanca(float saldoPoupanca) {
            this.saldoPoupanca = saldoPoupanca;
      }
 
}
Listagem 9. Hierarquia de classes Conta

A Listagem 10 mostra o uso das classes, apesar de serem definidos objetos dos três tipos de classes. O método fazSaque pode ser utilizado para qualquer um dos tipos de conta, não dependendo do tipo que é passado como parâmetro para ele, e isso é possível graças ao polimorfismo, pois o método saque das três classes tem a mesma assinatura e em tempo de execução a JVM sabe o método saque de qual classe executar.

package com.devmedia.main;
 
import javax.swing.JOptionPane;
 
import com.devmedia.model.Conta;
import com.devmedia.model.ContaEspecial;
import com.devmedia.model.ContaPoupanca;
 
public class MainConta {
      
      public static void main(String [] args) {
            
            Conta conta = new Conta();
            conta.setAgencia("1234");
            conta.setNumero("1244");
            conta.deposito(3000);
            
            ContaPoupanca contaPoupanca = new ContaPoupanca();
            contaPoupanca.setAgencia("3456");
            contaPoupanca.setNumero("1234");
            contaPoupanca.deposito(3000);
            contaPoupanca.depositoPoupanca(1000);
            
            ContaEspecial contaEspecial = new ContaEspecial();
            contaEspecial.setAgencia("3432");
            contaEspecial.setLimite(1000);
            contaEspecial.setNumero("1434");
            contaEspecial.deposito(3000);
            
            fazSaque(conta);
            fazSaque(contaPoupanca);
            fazSaque(contaEspecial);
            
      }
      
      public static void fazSaque(Conta conta) {
            
            float valor = 
                        Float.parseFloat(
                        JOptionPane.showInputDialog("Digite o valor do saque")
                        );
            conta.saque(valor);
            conta.saldo();
      }
 
}
Listagem 10. Utilização das classes conta

Este artigo mostrou alguns dos principais conceitos do paradigma de programação Orientado a Objetos com o objetivo de ajudar pessoas que estão começando a programar com esse paradigma e auxiliar aqueles que apesar de já programarem com linguagens orientadas a objetos ainda tem algumas dúvidas de como utilizar alguns conceitos desse paradigma.

Espero que este artigo tenha sido útil! Até a próxima.


Links Úteis

  • Você conhece o método chinês?: O método chinês é uma técnica de depuração manual, amplamente utilizada no meio acadêmico, mas especialmente útil quando precisamos analisar um código sem ter o apoio de um editor.
  • Criando meu primeiro projeto no Java: Neste curso você aprenderá a criar o seu primeiro programa com Java, e não, ele não será um simples “Hello, World!”.
  • Delphi Exceptions: Trabalhando com exceções em Delphi: Neste curso você aprenderá a realizar o tratamento de exceções no Delphi, técnica que visa garantir o bom funcionamento da aplicação mesmo na ocorrência de certos erros.

Saiba mais sobre Orientação a Objetos ;)

  • Os pilares da Programação Orientada a Objetos: Neste DevCast veremos quais são os quatro pilares da Orientação a Objetos, seu conceito e sua importância na programação.
  • Orientação a Objetos em C#: Neste Guia de Referência você encontrará todo o conteúdo que precisa para aprender a programar Orientado a Objetos na linguagem C#.
  • Orientação a Objetos em Java: Nesse Guia de Referência você encontrará todo o conteúdo que precisa para aprender a programar Orientado a Objetos em Java, paradigma padrão dessa linguagem e que visa o desenvolvimento com base em objetos.