Herança


A herança permite que as classes compartilhem atributos e operações baseados em um relacionamento, ou seja, permite a criação de subclasses que herdam alguns dos atributos e das operações da classe pai (super-classe ou classe base). É usada na intenção de reaproveitar código ou comportamento generalizado ou especializar operações e atributos.
A palavra-chave instanceof retorna true se a variável de referência que está sendo testada é do tipo da que está sendo comparada, ou pertence a sua hierarquia.

public class Heranca {

      public static void main(String[] args) {
            Heranca heranca = new Heranca();
            if (heranca instanceof Object)
                  System.out.println("Herança herda "+
                                    "de Object");
      }
     
}

Toda classe Java é uma subclasse da classe Object. Por causa disso, toda classe em Java terá métodos como equals, clone, notify, wait, pois são herdados da classe Object.
Em Java é possível criar relações de herança estendendo classes. As duas maiores razões para utilizar herança são: para promover o reuso de código e para utilizar polimorfismo.
Uma classe não pode herdar de mais de uma classe, ou seja, uma classe só pode ter uma superclasse imediata, mas pode ter múltiplos ancestrais.
Uma abordagem comum do reuso de código é criar versões de classes bastante genéricas com a intenção de criar subclasses mais específicas que herdem delas.  A reutilização de código através da herança significa que métodos com funcionalidades genéricas, que podem ser aplicados a uma gama de diferentes tipos de classes, não tem que ser reimplementados. Isso significa que toda a subclasse especializada tem por garantia todas as capacidades da superclasse.

public class Pessoa {

      public void falar() {
            System.out.println("Pessoa falando");
      }
     
}

public class Pessoa_Fisica extends Pessoa {

      public void informarCPF() {
            System.out.println("Informando CPF");
      }
     
}

public class AppPessoa {
     
      public static void main(String[] args) {
            Pessoa_Fisica pf = new Pessoa_Fisica();
            //herdou o método falar de Pessoa
            pf.falar();
            pf.informarCPF();
      }
     
}

O segundo uso de herança é o polimorfismo, que torna qualquer subclasse compatível com a superclasse. Isso quer dizer que 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.
Por exemplo: 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. Dessa forma, a chamada b.grita() vai ser um grito humano se b possui uma referência para o objeto Humano e será um grito de macaco, se o objeto b possuir uma referência para o objeto Macaco.

public class Profissao {

      public void trabalhar() {
            System.out.println("Trabalhando..");
      }
     
}

public class Professor extends Profissao {

      public void trabalhar() {
            System.out.println("Ensinando..");
      }
     
}

public class Mecanico extends Profissao {

      public void trabalhar() {
            System.out.println("Consertando..");
      }
     
}

public class Test_Profissao {

      public static void main(String[] args) {
            Profissao profissao;
            profissao = new Professor();
            profissao.trabalhar(); //ensinando
            profissao = new Mecanico();
            profissao.trabalhar(); //consertando
      }
     
}

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

Encapsulamento


A Orientação a Objetos propõe uma organização de software em termos de coleção de objetos discretos incorporando estrutura e comportamento próprios. Esta abordagem de organização é essencialmente diferente do desenvolvimento tradicional de software, onde estruturas de dados e rotinas são desenvolvidas de forma apenas fracamente acopladas.
Encapsulamento consiste em separar os aspectos externos de um objeto, os quais são acessíveis a outros objetos, dos detalhes internos de implementação do objeto, os quais permanecem escondidos dos outros objetos. O uso de encapsulamento evita que um programa torne-se tão interdependente que uma pequena mudança tenha grandes efeitos colaterais.

class Quadrado {
     
      public int lado;
     
}

public class App {
     
      public static void main(String[] args) {
            Quadrado quadrado = new Quadrado();
            /* isso aparentemente não causa erro, mas
             * não é uma boa implementação de
             * encapsulamento, pois, dessa forma, é
             * possível inserir valores não 
             * desejáveis, que a classe talvez não 
 * esteja preparada para receber */
            quadrado.lado = 1;
            quadrado.lado = -1;
      }
     
}

Para ter uma classe que possa ser alterada sem gerar muitos danos em outras classes que a estão utilizando, é necessário incluir encapsulamento, para isso deve-se:
·            Manter as variáveis de instância protegidas (com algum modificador de acesso, normalmente private);
·            Tornar público os métodos de acesso (getters e setters), forçando o código de chamada para usar esses métodos em vez de acessar diretamente a variável de instância;
·            Usar a convenção JavaBean para os getters e setters.

class Retangulo {
     
      //proteger as variáveis de instâncias
      private int lado;

      //métodos getters e setters públicos
      public int getLado() {
            return lado;
      }
      public void setLado(int lado) {
            //tratamento do dado que será settado
            if (lado < 0)
                  this.lado = 0;
            else
                  this.lado = lado;
      }
     
}

public class App2 {

      public static void main(String[] args) {
            Retangulo retangulo = new Retangulo();
            retangulo.setLado(1);
      }
     
}

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

Declarando Enums


Java permite restringir uma variável a ter uma de apenas alguns valores pré-definidos, em outras palavras, um valor a partir de uma lista enumerada.
Os componentes básicos de um enum são suas constantes.

enum Refrigerante { COCA, FANTA, SODA

Enums podem ser declarados como classes separadas, ou como um membro de classe, entretanto, eles não devem ser declarados dentro de um método. Aqueles que não estão dentro de uma classe podem ser declarados apenas com os modificadores public ou deafult. Utiliza-se a palavra-chave enum para declará-los e é opcional o uso de ponto-e-vírgula no final da declaração.

package dec_enum;

enum Refrigerante {//não pode ser private ou protected
BIG, HUGE, OVERWHELMING

class Bebida {
      Refrigerante refri;
}


public class Bebida_Test {

      /* tb pode estar dentro de uma classe e pode
       * ter qualquer modificador de acesso; o
 * ponto-e-virgula é opcional na declaração */
protected enum Test { Test1, Test2, Test3 };
     
      public static void main(String[] args) {
            Bebida drink = new Bebida ();
            drink.refri = Refrigerante.BIG;                      System.out.println(drink.refri);
            System.out.println(Test.Test1);
      }
     
}

Enums são representados como instâncias static e final, o que significa que são constantes. Cada enum conhece seu index ou posição, o que significa que a ordem em que são declarados importa. Pode-se dizer que um enum é um array de algum tipo.

Declarando construtores, métodos e variáveis em um Enum

Pelo fato de um enum ser um tipo especial de classe, é possível adicionar construtores, variáveis de instância, métodos e um corpo de classe constante específica.
Todo enum possui um método estático chamado values(), que retorna um array com todos os enums na ordem em que foram declarados.

package declare_class_members;

enum Refri {
      //8,10,16 são passados para o construtor do enum
      COCA(2), FANTA(3), SODA(6);

      Refri(int qtd) { //construtor do enum
            this.qtd = qtd;
      }

      private int qtd; //variável de insância do enum

      public int getQtd() {//método get dentro do enum
            return qtd;
      }
}

class Bebida {

      Refri refri;

      public static void main(String[] args) {
            Bebida drink1 = new Bebida();
            drink1.refri = Refri.COCA;
            Bebida drink2 = new Bebida();
            drink2.refri = Refri.FANTA;
            for (Refri cs : Refri.values())
                System.out.print(cs+" "+cs.getQtd());
      }
}

Pontos a serem considerados sobre enums: não é possível invocar um construtor enum diretamente, pois o construtor enum é invocado automaticamente, com os argumentos definidos depois do valor constante; pode-se definir mais de um argumento para o construtor, e é possível sobrecarregar os construtores enums, assim como se pode sobrecarregar um construtor de uma classe normal; utiliza-se corpo de classe constante específica quando é necessária uma constante particular para substituir um método definido em um enum.

package dec_enum;

enum Refri2 { //possui 3 constantes
      COCA(8), //não possui corpo
      FANTA(10){ //corpo para essa constante
            /* sobrescrita (apenas para essa 
 * constante) do método declarado 
 * no enum */
            public String getCode() {
                  return "C";
            }
      }, 
      SODA(16) { //corpo para essa constante
            /* sobrescrita (apenas para essa
 * constante) do método declarado 
 * no enum */
            public String getCode() {
                  return "A";
            }
      }; //qdo segue mais código o (;) é obrigatório
     
      Refri2(int qtd) {
            this.qtd = qtd;
      }

      private int qtd;
      public String name;

      public int getQtd() {
            return qtd;
      }

      /* esse método é sobrescrito pelas constantes
       * FANTA e SODA */
      public String getCode() {
            return "B";
      }
}

class Bebida2 {

      Refri2 refri;

      public static void main(String[] args) {
            Bebida2 drink1 = new Bebida2();
            drink1.refri = Refri2.COCA;
            drink1.refri.name = "Teste1";
            Bebida2 drink2 = new Bebida2();
            drink2.refri = Refri2.FANTA;
            drink2.refri.name = "Teste2";
            for (Refri2 cs : Refri2.values()) {
                  System.out.println(cs.getQtd());
                  System.out.println(cs.name);
                  System.out.println(cs.getCode());
            }
      }
}

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