Classe abstrata e final

Boa noite (ou bom dia) para todos!

Estou atualmente estudando alguns conceitos de Programação Orientada a Objetos, a linguagem Java e como conceito e linguagem se relacionam.

Enfim, notei que na linguagem Java há 2 palavras reservadas que distinguem como uma Classe pode ser utilizada. São elas:

1 - Abstract: quando uma Classe é abstrata, a mesma não pode ser instanciada em tempo de execução. Nenhum objeto pode ser criado.
2 - Final: quando uma classe é final, a mesma não pode ser herdada. Não pode possuir sub-classes.

O Java não permite a existência de uma classe que possua a seguinte assinatura:

public abstract final class Exemplo { ... }

ou

public final abstract class Exemplo { ... }

Talvez do ponto de vista conceitual, não seja relevante ter uma classe com essa formatação, afinal, não poderia ser herdada e muito menos instanciada. Não teria real valor para abstração. Contudo, elas podem sim ser bem uteis para quando possuímos classes utilitárias.

Uma classe utilitária, geralmente não precisa ser herdada e muito menos instanciada, pois todos os seus métodos são estáticos.

Por curiosidade, caso precisemos ter uma classe “abstract final” podemos simplesmente tornar o construtor da mesma privado. Uma classe com o construtor privado, não pode ser herdada e também não pode ser instanciada.

1 - Uma classe que possui o construtor privado não pode ser herdada, pois no momento que uma sub-classe fosse instanciada, o construtor dessa sub-classe dependeria de criar em memória a super-classe primeiro, para isso o construtor da super-classe teria de ser invocado. Afinal, um objeto filho não pode existir sem ou antes de seus objetos pais.

2 - Uma classe que possui o construtor privado não pode ser instanciada, pois após a construção do objeto hipotético, talvez seu estado não tivesse viabilidade prática para a funcionalidade que deve ser exercida, e que por causa disso, erros em tempo de execução poderiam ser causados por estados arbitrários. Também, se o new não consegue acessar o método de construção da classe pretendida, um objeto não pode ser construído em memória heap e se o objeto não pode ser construído, o new não pode retornar nenhum valor de memória para futura referencia do objeto no contexto em que a instancia foi invocada.

O que vocês acham? Já pararam para pensar sobre isso? Qual a opinião de vocês?

Obrigado pela atenção.

Para classes utilitárias costumo utilizar o final na declaração da classe juntamente com construtor privado.

O abstract só faz sentido se você tiver métodos abstratos e concretos na mesma classe.
Se for tudo abstrato, utilize uma interface.

2 curtidas

Primeiro precisamos entender de verdade o que é uma classe abstrata.

Uma classe abstrata não é apenas uma classe que não pode ser instanciada. Veja como a especificação a define:

https://docs.oracle.com/javase/specs/jls/se14/html/jls-8.html#jls-8.1.1.1

An abstract class is a class that is incomplete, or to be considered incomplete.

Ou seja, uma classe abstrata é uma classe que é ou pode ser considerada incompleta.

Então o racionio pra vc decidir usar uma classe abstrata não é simplesmente “hum… não quero que seja possivel instanciar esta classe, vou torná-la abstrata”, mas sim “Vou fazer essa classe de forma que ela sirva de base para a criação de outras classes de forma que essas classes apenas sobreescrevam ou implementem os métodos dela”.

Agora veja o que a especificação diz sobre classes finais:

https://docs.oracle.com/javase/specs/jls/se14/html/jls-8.html#jls-8.1.1.2

A class can be declared final if its definition is complete and no subclasses are desired or required.

Note que não é só uma questão de “Talvez do ponto de vista conceitual, não seja relevante ter uma classe com essa formatação…”. O conceito das classes abstratas e das finais são completamente opostos.

Já com relação ao uso de construtores privados, a propria especificação nos fala sobre eles lá no finalzinho da seção sobre classes abstradas que linkei ali em cima, veja:

A class type should be declared abstract only if the intent is that subclasses can be created to complete the implementation. If the intent is simply to prevent instantiation of a class, the proper way to express this is to declare a constructor (§8.8.10) of no arguments, make it private , never invoke it, and declare no other constructors. A class of this form usually contains class methods and variables.

Uma classe deve ser declara abstrata somente se a intenção é que subclasses sejam criadas para completar a implementação. Se a intenção é simplesmente evitar instanciação da classe, a forma correta para expressar isso é declarar um contructor sem argumentos, torná-lo privado, nunca invocá-lo e não declarar outros constructors

Um exemplo que temos da própria biblioteca padrão do Java é a classe Math, veja o código fonte dela no OpenJDK:

http://hg.openjdk.java.net/jdk/jdk14/file/1ddf9a99e4ad/src/java.base/share/classes/java/lang/Math.java#l107

Ela só tem um constructor que é privado e sem argumentos.

Agora um exemplo de uso de classe abstrata é a WebSecurityConfigurerAdapter do Spring:

https://docs.spring.io/spring-security/site/docs/current/api/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurerAdapter.html

Ela é basicamente uma forma conveniente de se criar um WebSecurityConfigurer.

1 curtida

@wldomiciano

Achei muito interessante a sua resposta!

Não tinha visto classes abstratas e finais dessa forma

Entendi que:

1 - Classes abstratas são totalmente genéricas. Ou seja, estão incompletas e por isso não estão prontas para serem instanciadas. As classes abstratas são “conceitos” e dependem de suas sub-classes não-abstratas para se tornarem cada vez mais especificas (completar a implementação).

2 - Quando uma classe é totalmente especifica (ou especificidade pretendida pelo desenvolvedor), podemos então ter uma classe final. Uma classe final não tem nada de genérica, e por isso não pode ser herdada (continuar a implementação). Em conceito, uma classe final sempre estará pronta para instancias em tempo de execução, pois já realiza todos os comportamentos que lhe foi atribuída.

3 - Por outro lado, sempre que eu tiver uma classe utilitária (todos os métodos estáticos) posso então utilizar construtores privados, impedindo assim que minha classe seja instanciada e também herdada.

Correto?

Muito obrigado pela sua resposta, com certeza agregou bastante em meus estudos! :smiley:

@wldomiciano

Aaaaaah…

Realizando alguns testes, pude perceber também que não é possível ter um método abstrato dentro de uma classe final. Isso porque, uma classe final já é totalmente especifica e por isso, pronta para ter instancias (desde que o construtor não seja privado). Sendo assim, todos os seus métodos tem que possuir lógica de negócio -> não é possível ter métodos abstratos em classes não-abstratas.

Por outro lado, eu posso ter métodos finais dentro de uma classe abstrata. Isso porque nem todos os métodos de uma classe abstrata devem ser abstratos e por isso, pode ser especificado que nem todos os métodos não-abstratos podem ser sobrescritos pelas classes herdeiras, mantendo sua lógica original.

Explodiu minha mente

Muito obrigado novamente!

@staroski

Aaaaaah, essa era a minha próxima pergunta, quando utilizar classes abstratas e quando utilizar interfaces…

Partindo do principio de que tanto uma interface quanto uma classe abstrata são genéricas o suficiente para não poderem ser instanciadas em tempo de execução, posso utilizar:

  • Uma interface: quando os comportamentos forem totalmente genéricos (todos os métodos abstratos)

  • Uma classe abstrata: comportamentos genéricos (métodos abstratos), mas também específicos (métodos com lógica de negócio) pronto para utilização pelas sub-classes.

  • por isso não posso ter métodos finais dentro de interfaces (métodos finais exigem lógica de negócio)

  • por isso só posso ter métodos abstratos dentro de interfaces

  • por isso posso ter métodos abstratos dentro de classes abstratas

  • por isso posso ter métodos finais dentro de classes abstratas

Que legal isso!

Muito obrigado cara!

1 curtida

Vc está indo pelo caminho certo, mas acho importante vc corrigir algumas coisas na sua forma de pensar.

Vc está usando os termos “classe genérica” e “método genérico” como sinonimos de, respectivamente, classe abstrata e método abstrato.

Isso é errado, pois “genérico” pode ter 2 sentidos:

  • Sentido 1: Classe ou método genérico é uma classe ou método sem um próposito especifico, que pode ser usado para vários fins. E acredito que isso nem seja possivel, porque se vc tem uma classe ou método que faz tudo, algo está bem errado. É importante que classes e métodos desempenhem tarefas o mais especificas possivel.
  • Sentido 2: Vc está se referindo aos Generics do Java. Classe genérica ou método genérico é aquele que possui um ou mais Type Variables.

Por isso é importante que vc se atenha ao significado real:

  • Classe abstrata é uma classe que é (ou pode ser considerada) incompleta. Apenas classes abstratas podem ter métodos abstratos, mas isto não é um requisito, vc poderia muito bem ter uma classe abstrata sem métodos abstratos.
  • Método abstrato é um método que não possui implementação (o que vc chama de lógica de negocio), segundo a especificação, um método não-abstrato pode ser chamado de método concreto.

Dito isso…

Elas não dependem de subclasses para serem “mais especificas”. Elas dependem de subclasses para serem implementadas.

Vc escolhe declarar uma classe como final quando vc não quer que outros a extendam. Vc pode muito bem ter uma classe não-final com todos os método concretos (não-abstratos).

Também não faz muito sentido vc acrescentar a parte sobre “em tempo de execução” porque, em Java, extender uma classe final ou instanciar uma classe abstrata causa erro de compilação.

Correto.

Sobre o assunto Classe Abstrata Vs Interface, seria mais ou menos o seguinte:

Vc usa interface quando vc quer estabelecer um contrato, por exemplo:

interface Attack {
  void execute();
}

class FireBall implements Attack {
  @Override
  public void execute() {
    System.out.println("Attack with a fireball");
  }
}

class SwordBlast implements Attack {
  @Override
  public void execute() {
    System.out.println("Attack with a SwordBlast");
  }
}

class Hero {
  void doAttack(Attack attack) {
    attack.execute();
  }
}

No código acima eu quis mostrar o seguinte:

Meu herói pode ter varios ataques completamente diferentes, então é importante determinar uma forma padrão de executá-los. É por isso que eu criei a interface Attack, para que ela sirva como um contrato, ou uma garantia de que todos os ataques implementarão um método chamado execute(). Eu não preciso que Attack faça mais nada.

Agora imagina esta outra situação:

class Mage extends Hero {
  Mage(String name) {
    super(name);
    this.intelligence = 10;
    this.strength = 5;
  }
}

class Warrior extends Hero {
  Warrior(String name) {
    super(name);
    this.intelligence = 5;
    this.strength = 10;
  }
}

abstract class Hero {
  private String name;
  protected int strength;
  protected int intelligence;

  Hero(String name) {
    this.name = name;
  }

  String getName() {
    return this.name;
  }
}

Quero que vc se atente a 2 pontos no código acima:

  1. Viu como eu declarei variáveis de instancia em Hero e as acessei a partir das subclasses? Não é possivel declarar variaveis de instancia em interfaces.
  2. Viu como Hero tem um contructor que possui lógica (é só uma atribuiçao a uma variavel de instancia, mas não deixa de ser lógica ^^). Não é possivel declarar contructors em interfaces

Então neste segundo exemplo, Hero não pode ser instanciada, pois no cenario que imaginei todo herói precisa, ter uma “profissão” e como eu não quero diminuir repetição de código ao maximo possivel, decidi usar Hero como base para essas “profissões”.

São exemplo bobos, mas acredito que deu pra ilustrar um pouco do raciocionio pra quando escolher uma ou outra.

E eu nem citei outras coisas que classes abstratas pode ter e interfaces não, como:

Por favor, me diga se ficou confuso.

1 curtida

@wldomiciano

ahsuhasa realmente eu estava utilizando o conceito de genérico de forma incorreta. Ficou bem confuso mesmo. Muito obrigado por me corrigir, está agregando bastante em meus estudos!

Então, em resumo:

Classe abstrata: é uma classe que está (ou pode estar) incompleta (métodos abstratos - não implementados). Normalmente servem de base para criação de novas classes (extends). As classes abstratas podem ter métodos abstratos ou concretos. Métodos abstratos possuem somente uma assinatura, enquanto métodos concretos possuem implementação (que é a lógica de negócio que descrevi). Classes comuns ou outras classes abstratas por sua vez, podem herdar dessa classe abstrata para implementar seus métodos abstratos ou então sobrescrever métodos já implementados da super-classe abstrata. Uma classe abstrata não pode ser instanciada.

Métodos abstratos: são métodos que só possuem uma assinatura e nenhuma implementação. Métodos abstratos estão presentes em classes abstratas e interfaces. -> Interfaces não possuem métodos concretos, somente abstratos.

Classes final: são utilizadas quando a classe não necessita que outras a estendam. Seus métodos já estão todos implementados -> métodos abstratos não são permitidos, pois diferente da interface e da classe abstrata, um objeto pode ser criado de uma classe final e por isso, não seria possível utilizar um método abstrato em tempo de execução (pois não possuiria comportamento/implementação).

Quanto ao assunto "Classe abstrata x Interface:

De acordo com o que voce me explicou e com os exemplos que deu, acredito que a diferença está em alguns conceitos:

  • Não é possível ter métodos construtores em interfaces -> interfaces não podem ser instanciadas.
  • Não é possível ter variáveis em interfaces -> somente constantes (public static final …)?

Aaaaah, consegui compreender o seu exemplo de código!

Uma interface serve como um contrato entre a classe que a implementa e a interface implementada. Significa que obrigatoriamente a classe sempre implementará seus métodos.

Assim, de acordo com o exemplo, se caso algum método precise receber como parâmetro algum objeto que possua o método “execute()” (para utilizá-lo), ele saberá que pode receber qualquer classe que implemente a interface “Attack” porque obrigatoriamente as mesmas terão um método “execute()” que pode ser utilizado.

Acho que isso é sobre polimorfismo né? não cheguei nessa parte ainda haushaushas mas é bem interessante esse conceito!

Agradeço novamente pela grande ajuda!

Vc está correto com exceção de uma afirmação:

Em Java, interfaces podem ter métodos concretos privados, métodos concretos estáticos (que podem ser também publicos ou privados) e metodos default (que não podem ser privados). Veja:

class Main implements Test {
  public static void main(String[] args) {
    Test.aStaticMethod();

    Test m = new Main();
    m.aDefaultInstanceMethod();
  }
}

interface Test {
  static void aStaticMethod() {
    System.out.println("aStaticMethod");
  }

  private void aPrivateInstanceMethod() {
    System.out.println("aPrivateInstanceMethod");
  }

  default void aDefaultInstanceMethod() {
    this.aPrivateInstanceMethod();
    System.out.println("aDefaultInstanceMethod");
  }
}
1 curtida

Bom dia cara!

ahsuahsuahsa eu não sabia disso ai não. kkkk quanta informação, então fui dar umas pesquisadas mais afundo e fazer alguns testes:

Método default: método padrão que qualquer classe que implemente essa interface possuirá. -> esses métodos podem ser sobrescritos na sub-classe, adaptando-os. Tudo fica mais organizado e “encapsulado”. Por poder ser herdado, não pode ser privado.

Método private: eu não entendi muito bem a função, mas acredito que sejam métodos auxiliares à outros métodos internos e concretos (public ou private) da própria interface. -> Acredito que esses métodos não possam ser herdados ou sobrescritos pelas sub-classes. Não consegui testar, pois a minha versão ainda é o Java 8 e acho que essa funcionalidade exige o Java 9 :frowning: :frowning: :frowning:

Métodos estáticos e públicos: existem somente na interface e por isso, quando chamados tem de ser “Interface.metodo()”. Por serem públicos, podem ser herdados, mas por serem estáticos, não podem ser sobrescritos nas sub-classes -> é acessível, mas mantém sua implementação original intacta.

-> por serem estáticos, não podem utilizar métodos não-estáticos (para isso, seria necessário instanciar a interface - o que é impossível! -> interfaces não possuem construtores).

Métodos estáticos e privados: kkk também não consegui testar sem ter o Java 9, mas acredito que seja pra resolver o problema que descrevi acima. Um método estático e privado é um método auxiliar para métodos públicos e estáticos da interface.

-> um método estático não pode utilizar um método não-estático sem antes instanciar, mas com interfaces isso é impossível, então pra isso existe métodos estáticos e privados


Mas com todos esses avanços em interfaces, fico me perguntando, porque uma pessoa então escolheria herdar de uma classe ao invés de uma interface??? Uma interface já tem praticamente tudo que uma classe tem e além disso, o Java não permite herança múltipla, mas permite que uma sub-classe implemente várias interfaces. Com as interfaces quase parecidas com as classes (com exceção dos construtores), os desenvolvedores poderiam se aproveitar dessas funcionalidades para tentar criar algum tipo de herança múltipla.

Bem, antes de saber dos métodos concretos, parecia bem clara a diferença entre interfaces e classes. Mas agora, elas são quase parecidas.

Do meu ponto de vista básico, acredito que a diferença esteja exatamente no estado (variáveis internas). Enquanto um objeto pode ter estado (atributos) e comportamentos (métodos), as interfaces só podem ter comportamentos. Interfaces não armazenam estados (só permitem constantes) -> Não é possível criar um objeto diretamente de uma interface

Viiiish

Concordo que uma das grandes diferenças seja que classes podem ter variáveis de instancia e interfaces não, mas tem bem mais que isso.

Classes te permitem um controle maior da visibilidade de seus membros com o modificador de acesso protected e o default (sem modificador, só pode ser acessado por classes do meu pacote).

A possibilidade de criar constructors pode ser essencial em alguns momentos.

A possibilidade de usar blocos inicializadores de instancia e de classe.

Tirando isso, pra mim, o papel de classes e interfaces é muito bem definido.

Eu sempre vou seguir a lógica que descrevi lá em cima: Interfaces são apenas para estabelecer contratos.

Então não importa que interfaces tenham recursos parecidos com classes, eu usarei todos os recursos disponiveis em interfaces com este único propósito: Estabelecer contrato.

1 curtida