Métodos Overloaded (Sobrecarregados)


Métodos sobrecarregados permitem o reuso do mesmo nome do método em uma classe, mas com argumentos diferentes (e opcionalmente um tipo de retorno diferente).
Regras para métodos sobrecarregados:
·         Métodos sobrecarregados devem mudar a lista de argumentos;
·         Métodos sobrecarregados podem mudar o tipo de retorno;
·         Métodos sobrecarregados podem mudar o modificador de acesso;
·         Métodos sobrecarregados podem declarar novos ou menos exceções;
·         Um método pode ser sobrecarregado na mesma classe ou em uma subclasse; nesse ultimo caso, somente se o método for herdado da superclasse.


public class MetSobrecarregados {
     
      public void inserir() throws Exception { }
     
      public void inserir(String nome) { }
     
      protected int inserir(int cod) {
            return 0;
      }
     
      /* mesmo que tenha mudado o tipo de retorno
       * e o modificador de acesso, é necessário
       * mudar a lista de argumentos */
      private boolean inserir() {
            return true;
      }
     
}


Invocando Métodos Sobrecarregados

Quando existem métodos sobrecarregados o compilador irá decidir qual método chamar de acordo com os argumentos passados para o método; por exemplo, se existem dois métodos sobrecarregados, um com dois parâmetros int e outro com dois parâmetros float, o compilador irá optar pelo primeiro se os argumentos forem do tipo int, e pelo segundo se os argumentos forem do tipo float.


public class TestOverload {

      public int somar(int x, int y) {
            return x + y;
      }
     
      public double somar(double x, double y) {
            return x + y;
      }
     
      public static void main(String[] args) {
            TestOverload t = new TestOverload();
            int a = 1;
            int b = 2;
            System.out.println(t.somar(a, b)); //3
            System.out.println(t.somar(2.5, 2));//4.5
      }
     
}


Para casos com variáveis de referência como parâmetros em métodos sobrecarregados, ocorrem diferentes situações. Se os objetos de um tipo forem instanciados como esse mesmo tipo, o compilador irá executar os métodos da mesma forma que ocorre com variáveis primárias. Porém, se um objeto estiver instanciado com outro tipo            (A a = new B();), o método sobrecarregado que será executado é o que tiver o tipo da referência (A) como parâmetro, e não o que tiver o tipo do objeto (B).


class Forma { }


class Quadrado extends Forma { }


public class TestOverload2 {

      void preencher(Forma forma) {
            System.out.println("Forma");
      }
     
      void preencher(Quadrado quadrado) {
            System.out.println("Quadrado");
      }
     
      public static void main(String[] args) {
            TestOverload2 t = new TestOverload2();
            Forma forma = new Forma();
            Quadrado quadrado = new Quadrado();
            Forma forma2 = new Quadrado();
            t.preencher(forma); //imprime Forma
            t.preencher(quadrado); //imprime Quadrado
            /* irá invocar a versão com o tipo Forma,
             * pois, apesar de estar instanciado como
             * Quadrado, o que conta é o tipo da
             * referência (Forma) e não o tipo do
             * objeto (Quadrado) */
            t.preencher(forma2); //imprime Forma
      }          
     
}


Fonte: SCJP Sun Certifi ed Programmer for Java 6 Study Guide

Métodos Sobrescritos (Overridden)


Qualquer classe que herda um método de uma superclasse tem a oportunidade de sobrescrever o método (exceto métodos marcados como final). O benefício de sobrepor é a capacidade de definir um comportamento que é específico para um tipo de subclasse particular.
Para métodos abstratos que são herdados de uma superclasse não existe escolha; deve-se implementar os métodos na subclasse, com exceção se a subclasse também seja uma classe abstrata.

public class Pessoa {  
      public void cadastrar() {
            System.out.println("Cadastro de Pessoa");
      }
}

public class PFisica extends Pessoa {   
      //método sobrescrito de Pessoa
      public void cadastrar() {
            System.out.println("Cadastro de PFisica");
      }
}

Polimorficamente é possível declarar um objeto de um tipo e referencia-lo com um de seus subtipos, por exemplo: ao declarar um objeto Pessoa, que é superclasse de PFisica, é possível instanciar esse objeto como Pessoa ou PFisica. Caso seja instanciado como Pessoa, este objeto terá acesso a todos os métodos de Pessoa. Porém, se esse objeto (do tipo Pessoa) estiver instanciado como PFisica e tentar acessar qualquer método que exista em Pessoa, o compilador irá tentar acha-lo primeiro na classe de referência (PFisica), caso não encontre, acessará o método da classe Pessoa.

public class Pessoa {  
      public void cadastrar() {
            System.out.println("Cadastro de Pessoa");
      }
}

public class PFisica extends Pessoa {
     
      //método sobrescrito de Pessoa
      public void cadastrar() {
            System.out.println("Cadastro de PFisica");
      }
     
      public void atualizar() {
            System.out.println("Atual. de PFisca");
      }

}

public class AppPessoa {
      public static void main(String[] args) {
            Pessoa pessoa = new Pessoa();
            Pessoa pessoa2 = new PFisica();
            pessoa.cadastrar();
            pessoa2.cadastrar();
            //Pessoa não possui o método atualizar()
            pessoa.atualizar();
            /* Apesar de PFisica possuir o método
             * atualizar(), a referência é da classe
 * Pessoa, que não possui o método */
            pessoa2.atualizar();
      }    
}

Um método sobrescrito não pode ter um modificador de acesso mais restrito do que o método original; por exemplo: não se pode sobrescrever um método public marcando-o como protected; porém, é possível sobrescrever um método protected como public.

public class Pessoa {       
      public void excluir() {
            System.out.println("Excluir Pessoa");
      }
}

public class PJuridica extends Pessoa {
      /* um método sobrescrito não pode ter um
       * modificador mais restrito que o método
       * original */
      protected void excluir() {
            System.out.println("Excluir PJuridica");
      }    
}

Regras para sobrescrever métodos:
·   A lista de argumentos deve combinar exatamente com a do método sobrescrito. Se não combinar, pode resultar em uma sobrecarga de método não intencional;
·   O tipo de retorno deve ser o mesmo, ou de um subtipo dele;
·   O nível de acesso não pode ser mais restrito que o nível do método original;
·   O nível de acesso pode ser menos restrito que o nível do método original;
·   Métodos de instância só podem ser sobrescritos se eles forem herdados na subclasse. Isso significa que: uma subclasse dentro de um mesmo pacote que a superclasse, pode sobrescrever qualquer método desta que não esteja marcado como private ou final; uma subclasse em um pacote diferente pode sobrescrever apenas métodos não-final marcados como public ou protected;
·   O método sobrescrito pode lançar qualquer exceção não verificada (runtime), independente de se o método sobrescrito declara a exceção;
·   O método sobrescrito não deve lançar exceções verificadas que são novas ou a mais que o método original; por exemplo, um método que declara uma FileNotFoundException não pode ser sobrescrito por um método que declara uma SQLException, Exception ou qualquer outra exceção verificada (não runtime), exceto se a exceção for uma subclasse de FileNotFoundException;
·   O método sobrescrito pode lançar menos exceções que as do método original;
·   Não se pode sobrescrever um método marcado como final;
·   Não se pode sobrescrever um método marcado como static;
·   Se um método não pode ser herdado, então ele não pode ser sobrescrito.

Invocando a versão da superclasse

Podem existir casos em que o método original deve ser executado antes do método sobrescrito. Um modo fácil de fazer isso é utilizando a palavra super seguido do nome do método da superclasse no início do corpo do método original.
A chamada do método da superclasse pode ser feita em qualquer parte do código dos métodos da subclasse.

public class Carro {   
      public void ligar() {
            System.out.println("Ligar carro");
      }
}

public class Fusca extends Carro {
      public void ligar() {
            super.ligar();
            System.out.println("Ligar fusca");
      }    
}

Para o compilador, em tempo de compilação, quando existe um objeto referenciando sua subclasse e chamando um método sobrescrito, apenas no momento da compilação, o compilador irá assumir como método de execução o método da superclasse, mesmo que o método sobrescrito na subclasse não declare exceções enquanto o método original declare. Porém, nesse caso, em tempo de execução na verdade o método chamado será o da subclasse.

public class Carro {         
      public void desligar() throws Exception {
            System.out.println("Desligar carro");
      }
}

public class Fusca extends Carro {
      public void desligar() { //sem exceção
            System.out.println("Desligar fusca");
      }    
}

public class AppCarro {
      public static void main(String[] args) {
            Carro carro = new Fusca();
            Fusca fusca = new Fusca();
            fusca.desligar();
            /* nesse caso, o método a ser executado em
             * tempo de execução seria o desligar de
             * Fusca, mas a titulo de compilação é o
             * de Carro, por isso gera erro de
             * compilação, já que o método desligar de
             * Carro declara uma exceção que não está
             * sendo tratada aqui;   */
            carro.desligar();
      }    
}

Fonte: SCJP Sun Certifi ed Programmer for Java 6 Study Guide

Polimorfismo


Qualquer objeto Java que pode passar por mais de um teste IS-A pode ser considerado um caso de polimorfismo. À exceção de objetos do tipo Object, todos os objetos Java são polimórficos, pois eles passam por um teste IS-A com seu próprio tipo e por um com a classe Object.
A única maneira de acessar um objeto é através de uma variável de referência:
·         Uma variável de referência (objeto) só pode ser de um tipo, e uma vez declarada, aquele tipo jamais poderá ser mudado, embora o objeto possa mudar a referência;
·         Uma referência é uma variável, então ela pode ser transferida para outros objetos, a não ser que a referência esteja declarada como final;
·         Um tipo de variável de referência determina os métodos que podem ser invocados no objeto que a variável está referenciada;
·         Uma variável de referência pode se referir a qualquer objeto do mesmo tipo que a referência declarada, ou a qualquer subtipo do tipo declarado;
·         Uma variável de referência pode ser declarada como um tipo de classe ou um tipo de interface. Se a variável é declarada como um tipo interface, ela pode fazer referência a qualquer objeto de qualquer classe que implementa a interface.
Polimorfismo significa que uma chamada de método pode ser executada de várias formas (ou polimorficamente). Quem decide "a forma" é o objeto que recebe a chamada. Invocações de métodos polimórficos são aplicadas somente para métodos de instância.
Se um objeto a chama um método grita() de um objeto b, então o objeto b decide a forma de implementação do método. Então a chamada b.grita() vai ser um grito humano se b for um humano e será um grito de macaco, se o objeto b for um macaco. A forma como b vai escolher qual método vai executar vai depender de onde (qual classe) veio essa referência de b, ou seja, se b está instanciado como Humano ou Macaco.
Existem quatro formas de receber essa referência:
·         O objeto AppGritar  cria o objeto b:

public class Gritador {
      public void grita() {
            System.out.println("Gritando..");
      }    
}

public class Humano extends Gritador {
      public void grita() {
            System.out.println("Humano gritando");
      }    
}

public class Macaco extends Gritador {
      public void grita() {
            System.out.println("Macaco gritando");
      }    
}

public class AppGritar {
      public static void main(String[] args) {
          Gritador g;
          g = new Humano();
          // chamada polimórfica
          g.grita(); // imprime: Humando gritando
          g = new Macaco();
          // chamada polimórfica
          g.grita(); // imprime: Macaco gritando
      }
}

·         O objeto AppGritar  recebe o objeto g de um objeto r:

public class Retorna {
      public Humano retornaHumano() {
            return new Humano();
      }    
      public Macaco retornaMacaco() {
            return new Macaco();
      }    
}

public class AppGritar {
      public static void main(String[] args) {
          Retorna r = new Retorna();
          Gritador g = r.retornaHumano();
          // chamada polimórfica
          g.grita();  // imprime: Humando gritando
          g = r.retornaMacaco();
          // chamada polimórfica
          g.grita(); // imprime: Macaco gritando
      }
}

·         O objeto AppGritar recebe o objeto g numa chamada de método:

public class AppGritar {
      public static void main(String[] args) {
            Humano humano = new Humano();
            chamaGritar(humano); // Humando gritando
            Macaco macaco = new Macaco();
            chamaGritar(macaco); // Macaco gritando
      }
      static void chamaGritar(Gritador gritador) {
            /* dependendo da instância que vier como
             * parâmetro, chama Humano ou Macaco */
            gritador.grita(); // chamada polimórfica
      }
}

·         Através de uso de interfaces (sem herança):

public interface Gritador2 {
      void grita();
}

public class Humano2 implements Gritador2 {
      public void grita() {
          System.out.println("AAAAAAHHHHH");
        }
}

public class Macaco2 implements Gritador2 {
      public void grita() {
          System.out.println("IIIHHHIIIIHHH");
        }
}

public class AppGritar2 {
      public static void main(String[] args) {
          Gritador2 g;
          g = new Humano2();
          // chamada polimórfica
          g.grita(); // imprime: Humando gritando
          g = new Macaco2();
          // chamada polimórfica
          g.grita(); // imprime: Macaco gritando
      }    
}


Fonte: SCJP Sun Certifi ed Programmer for Java 6 Study Guide