Conceitos Fundamentais de Orientação a Objetos em Java

A programação orientada a objetos (POO) é um pilar da linguagem Java, oferecendo estruturas para organizar e gerenciar o código de forma eficiente. Exploraremos alguns dos conceitos essenciais, desde a referência ao objeto atual até a abstração e contratos de interface.

A Palavra-Chave this

A palavra-chave this em Java é uma referência ao objeto atual da classe. É amplamente utilizada para:

  • Diferenciar Variáveis: Quando um parâmetro de método ou uma variável local possui o mesmo nome de uma variável de instância, this.nomeVariavel permite referenciar a variável de instância.
  • Invocar Métodos: Chamar outro método do mesmo objeto.
  • Encadeamento de Construtores: Usar this() para invocar outro construtor da mesma classe, facilitando a reutilização de código entre construtores.
  • Passar Objeto Atual: Fornecer o próprio objeto como argumento para um método.

Membros Estáticos (static)

Membros declarados com a palavra-chave static pertencem à classe, e não a qualquer instância específica dela. Isso significa que eles são compartilhados por todas as instâncias da classe e podem ser acessados diretamente usando o nome da classe.

  • Variáveis Estáticas: São conhecidas como variáveis de classe. Elas têm uma única cópia para todas as instâncias, sendo úteis para dados compartilhados (como contadores de objetos ou constatnes).
  • Métodos Estáticos: Podem ser invocados sem a necessidade de criar uma instância da classe. Métodos estáticos não podem acessar variáveis ou métodos de instância (não estáticos) diretamente, pois não estão associados a um objeto específico. Eles podem acessar apenas outros membros estáticos da mesma classe.

Herança

A herança permite que uma classe (subclasse ou classe filha) adquira as propriedades e comportamentos de outra classe (superclasse ou classe pai). Em Java, utiliza-se a palavra-chave extends para estabelecer essa relação.

  • Sobrescrita de Métodos (@Override): Subclasses podem fornecer uma implementação específica para um método que já é definido em sua superclasse. A anotação @Override é recomendada para garantir que o método realmente sobrescreve um método da superclasse.
  • Invocação do Construtor Pai (super()): O construtor da superclasse é invocado usando a palavra-chave super(). Esta chamada deve ser a primeira instrução no construtor da subclasse.

Conversão de Tipos entre Classes (Casting)

Em uma hierarquia de herança, é possível converter referências de objetos entre superclasses e subclasses.

  • Upcasting (Ascendente): É a conversão de um tipo de subclasse para um tipo de superclasse. É implícito e sempre seguro. Um objeto de uma subclasse pode ser tratado como um objeto de sua superclasse. Métodos sobrescritos serão sempre executados a partir da implementação da subclasse.
  • Downcasting (Descendente): É a conversão de um tipo de superclasse para um tipo de subclasse. É explícito e requer uma conversão de tipo forçada. Pode resultar em uma ClassCastException se o objeto referenciado não for realmente uma instância da subclasse alvo. Para evitar erros, a palavra-chave instanceof deve ser usada para verificar o tipo do objeto antes da conversão.

Um cenário comum para downcasting é quando se trabalha com coleções de objetos de uma superclasse, mas é necessário tratar um objeto específico como sendo de uma subclasse para acessar seus métodos ou atributos únicos.

Sobrecarga de Métodos (Overloading)

A sobrecarga de métodos permite definir múltiplos métodos com o mesmo nome dentro da mesma classe, desde que suas listas de parâmetros sejam difereentes. As diferenças podem ser no número de parâmetros, nos tipos dos parâmetros ou na ordem dos tipos dos parâmetros.


class OperacoesMatematicas {
   // Método somar com dois inteiros
   int somar(int val1, int val2) {
       System.out.println("Somando inteiros...");
       return val1 + val2;
   }

   // Método somar com dois doubles (sobrecarga)
   double somar(double val1, double val2) {
       System.out.println("Somando doubles...");
       return val1 + val2;
   }

   // Método somar com três inteiros (outra sobrecarga)
   int somar(int val1, int val2, int val3) {
       System.out.println("Somando três inteiros...");
       return val1 + val2 + val3;
   }

   // Exemplo de uso
   public static void main(String[] args) {
       OperacoesMatematicas calculadora = new OperacoesMatematicas();
       System.out.println(calculadora.somar(5, 10));        // Chama o método com dois inteiros
       System.out.println(calculadora.somar(3.5, 2.7));     // Chama o método com dois doubles
       System.out.println(calculadora.somar(1, 2, 3));      // Chama o método com três inteiros
   }
}
   

Palavras-Chave final e record

  • final: É usada para criar entidades imutáveis ou para prevenir a herança e a sobrescrita.
    • final em classes: A classe não pode ser estendida (não pode ter subclasses).
    • final em métodos: O método não pode ser sobrescrito por subclasses.
    • final em variáveis: A variável se torna uma constante; seu valor só pode ser atribuído uma vez.
  • record (Java 16+): Introduzida para simplificar a criação de classes de dados imutáveis. Um record gera automaticamente um construtor canônico, métodos acessores (getters), equals(), hashCode() e toString(). Os campos de um record são implicitamente final, garantindo a imutabilidade dos dados.

Polimorfismo

Polimorfismo significa "muitas formas" e, em Java, refere-se à capacidade de um objeto tomar muitas formas. Essencialmente, é a ideia de que uma única interface (ou tipo) pode ter múltiplas implementações ou comportamentos.

As duas principais maneiras de alcançar polimorfismo em Java são através de:

  • Sobrecarga de Métodos: (Conforme explicado acima) Métodos com o mesmo nome, mas diferentes assinaturas de parâmetros, dentro da mesma classe.
  • Sobrescrita de Métodos: Quando uma subclasse fornece sua própria implementação para um método que já existe em sua superclasse. Quando o método é chamado através de uma referência da superclasse que aponta para um objeto da subclasse, a implementação da subclasse é executada.

Classes Abstratas

Uma classe abstrata é uma classe que não pode ser instanciada diretamente. Ela é projetada para ser herdada por outras classes e pode conter métodos abstratos (métodos sem implementação) e métodos concretos (com implementação). Subclasses de uma classe abstrata devem implementar todos os métodos abstratos, a menos que sejam elas mesmas abstratas.


// Definição da classe abstrata
abstract class ElementoVisual {
   // Método abstrato: sem corpo, deve ser implementado pelas subclasses
   abstract void renderizar();

   // Método concreto: possui implementação padrão
   void exibirDetalhes() {
       System.out.println("Este é um elemento visual genérico.");
   }
}

// Subclasse concreta que estende ElementoVisual
class Circulo extends ElementoVisual {
   private double raio;

   public Circulo(double raio) {
       this.raio = raio;
   }

   @Override
   void renderizar() {
       System.out.println("Desenhando um círculo com raio " + this.raio);
   }

   @Override
   void exibirDetalhes() {
       System.out.println("Detalhes do Círculo: Raio = " + this.raio);
   }
}

// Outra subclasse concreta
class Retangulo extends ElementoVisual {
   private double largura;
   private double altura;

   public Retangulo(double largura, double altura) {
       this.largura = largura;
       this.altura = altura;
   }

   @Override
   void renderizar() {
       System.out.println("Desenhando um retângulo de " + this.largura + "x" + this.altura);
   }
}

// Exemplo de uso de classes abstratas e polimorfismo
public class ExemploFiguras {
   public static void main(String[] args) {
       ElementoVisual figura1 = new Circulo(7.5); // Upcasting
       ElementoVisual figura2 = new Retangulo(10.0, 5.0); // Upcasting

       figura1.renderizar(); // Saída: Desenhando um círculo com raio 7.5
       figura1.exibirDetalhes(); // Saída: Detalhes do Círculo: Raio = 7.5

       figura2.renderizar(); // Saída: Desenhando um retângulo de 10.0x5.0
       figura2.exibirDetalhes(); // Saída: Este é um elemento visual genérico. (Método não sobrescrito)
   }
}
   

Interfaces

Uma interface em Java é um contrato que especifica um conjunto de métodos que uma classe deve implementar. Antes do Java 8, as interfaces só podiam ter declarações de métodos abstratos e constantes (public static final). A partir do Java 8, elas podem incluir métodos padrão (default) com implementações e métodos estáticos.

Interfaces são usadas para alcançar abstração e múltiplas heranças de tipo (uma classe pode implementar várias interfaces).


// Definição de uma interface
interface AcaoProgramavel {
   // Método abstrato implícito (sem corpo)
   void executar();

   // Método default (Java 8+): possui implementação padrão
   default void logarInicio() {
       System.out.println("Iniciando execução da ação...");
   }

   // Método estático (Java 8+): pode ser chamado diretamente pela interface
   static void exibirTipoAcao() {
       System.out.println("Esta interface define uma ação genérica que pode ser programada.");
   }
}

// Classe que implementa a interface
class ComandoAbrirArquivo implements AcaoProgramavel {
   private String nomeArquivo;

   public ComandoAbrirArquivo(String nome) {
       this.nomeArquivo = nome;
   }

   @Override
   public void executar() {
       logarInicio(); // Chama o método default da interface
       System.out.println("Abrindo arquivo: " + nomeArquivo + "...");
       // Lógica para abrir o arquivo aqui
   }
}

// Outra classe que implementa a interface
class ComandoSalvarDados implements AcaoProgramavel {
   private String dados;

   public ComandoSalvarDados(String dados) {
       this.dados = dados;
   }

   @Override
   public void executar() {
       logarInicio();
       System.out.println("Salvando dados: " + dados.substring(0, Math.min(dados.length(), 20)) + "...");
       // Lógica para salvar os dados aqui
   }
}

// Exemplo de uso de interfaces
public class GerenciadorDeComandos {
   public static void main(String[] args) {
       AcaoProgramavel.exibirTipoAcao(); // Chamada de método estático da interface

       AcaoProgramavel cmd1 = new ComandoAbrirArquivo("config.ini");
       AcaoProgramavel cmd2 = new ComandoSalvarDados("Conteúdo muito importante para ser salvo.");

       cmd1.executar();
       cmd2.executar();
   }
}
   

Tags: java OrientacaoAObjetos PalavraChaveThis MembrosEstaticos HerancaJava

Publicado em 7-8 18:46