MindView Inc.

Pensando em Java, 3ª ed. Revisão 4.0


[ Viewing Hints ] [ Book Home Page ] [ Free Newsletter ]
[ Seminars ] [ Seminars on CD ROM ] [ Consulting ]

Anterior Próximo Página Inicial Índice Conteúdo

8: Interfaces & Inner Classes



Interfaces e classes internas fornecem modos mais sofisticados de organizar e controlar objetos em seu sistema.



C++, por exemplo, não contém tais mecanismos, embora o hábil programador possa simular este. O fato dele existir em Java indica que ele foram considerados importantes o suficiente para prover suporte direto através de comandos da linguagem. Comentários (em inglês)



No Capítulo 7 vocâ aprendeu sobre a palavra chave abstract ,que lhe permite criar um ou mais métodos numa classe sem definições-você fornece parte do interface sem providenciar a correspondente implementação, que é criada pela classe herdeira. A palavra chave interface produz uma classe completamente abstrata, que não fornece nenhuma implementação. Você aprenderá que interface é mais que apenas uma classe abstrata levada ao extremo, porque ela permite executar uma variação da "múltlipla herança" do C++, pela criação de uma classe que pode ser transformada para mais de um tipo base.Comentários (em inglês)



A princípio, as classes internas parecem apenas um mecanismo de ocultação de código: você cria classes dentro de outras classes. Entretanto, você aprenderá que as classes internas possibilitam mais que isso-elas conhecem e podem se comunicar com as classes que as contêm-e que o tipo de código que você pode escrever com classes internas é mais claro e elegante, embora seja um novo conceito para a maioria. É preciso um certo tempo para ficar confortável com projetos usando classes internas.Comentários (em inglês)

Interfaces



A palavra chave interface leva o conceito de abstract um passo adiante. Você poderia pensar nela como uma classe abstrata “pura”. Isso permite ao criador estabelecer o formato de uma classe: nomes de métodos, listas de argumento, e tipos de retorno, porém sem os corpos dos métodos. Uma interface também pode conter campos, mas estes são static e final implicitamente. Uma interface provê apenas um formato, mas nenhuma implementação. Comentários (em inglês)



Uma interface diz: “Isto é com o que vão parecer todas as classes que implementam essa determinada interface.” Assim, qualquer código que use uma determinada interface sabe que métodos podem ser chamados para aquela interface, e isso é tudo. Assim, a interface é usada para estabelecer um “protocolo” entre classes. (Algumas linguagens de programação orientadas a objeto têm uma palavra chave chamada protocol para fazer a mesma coisa.) Comentários (em inglês)



Para criar uma interface, use a palavra chave interface ao invés da palavra chave class. Como uma classe, você pode adicionar a palavra chave public antes da palavra chave interface (mas somente se aquela interface está definida em um arquivo de mesmo nome) ou deixá-la sem para lhe dar acesso a package, então isto é útil somente com a mesma package. Comentários (em inglês)



Para fazer uma classe que obedeça a uma particular interface (ou grupo de interfaces), use a palavra chave implements, que diz, “A interface é que mostra como, mas agora eu direi como deve funcionar.” Ou então que, ela se mostra como herança. O diagrama para o exemplo instrument mostra isto:

PEJ322.png



Você pode ver nas classes Woodwind e Brass que uma vez que você implementou uma interface, aquela implementação se torna uma classe ordinária que pode ser extendida da maneira regular. Comentários (em inglês)



Você pode escolher declarar explicitamente as declarações de método de uma interface como public, mas eles são public somente se você não o disser. Então quando você implement uma interface, os métodos da interface devem ser definidos como public. Caso contrário, eles seriam padronizados para acesso a package, e você estaria reduzindo a acessibilidade da um método durante a herança, oque não é permitido pelo compilador Java.Comentários (em inglês)



Você pode ver isto na versão modificada do exemplo Instrument. Note que todos os métodos na interface são estritamente uma declaração, que é só o que o compilador permite. Em adição, nenhum dos métodos em Instrument é declarado como public, mas eles são automaticamente public mesmo assim:



//: c08:music5:Music5.java
// Interfaces.
package c08.music5;
import com.bruceeckel.simpletest.*;
import c07.music.Note;

interface Instrument {
  // Constante em tempo de compilação:
  int I = 5; // static e final
  // Não pode ter definições de método:
  void play(Note n); // Pública automaticamente
  String what();
  void adjust();
}

class Wind implements Instrument {
  public void play(Note n) {
    System.out.println("Wind.play() " + n);
  }
  public String what() { return "Wind"; }
  public void adjust() {}
}

class Percussion implements Instrument {
  public void play(Note n) {
    System.out.println("Percussion.play() " + n);
  }
  public String what() { return "Percussion"; }
  public void adjust() {}
}

class Stringed implements Instrument {
  public void play(Note n) {
    System.out.println("Stringed.play() " + n);
  }
  public String what() { return "Stringed"; }
  public void adjust() {}
}

class Brass extends Wind {
  public void play(Note n) {
    System.out.println("Brass.play() " + n);
  }
  public void adjust() {
    System.out.println("Brass.adjust()");
  }
}

class Woodwind extends Wind {
  public void play(Note n) {
    System.out.println("Woodwind.play() " + n);
  }
  public String what() { return "Woodwind"; }
}

public class Music5 {
  private static Test monitor = new Test();
  // Não se preocupe com o tipo, mesmo os tipos novos 
  // adicionados ao sistema ainda funcionam corretamente:
  static void tune(Instrument i) {
    // ...
    i.play(Note.MIDDLE_C);
  }
  static void tuneAll(Instrument[] e) {
    for(int i = 0; i < e.length; i++)
      tune(e[i]);
  }
  public static void main(String[] args) {
    // Fazendo upcast durante a adição ao :
    Instrument[] orchestra = {
      new Wind(),
      new Percussion(),
      new Stringed(),
      new Brass(),
      new Woodwind()
    };
    tuneAll(orchestra);
    monitor.expect(new String[] {
      "Wind.play() Middle C",
      "Percussion.play() Middle C",
      "Stringed.play() Middle C",
      "Brass.play() Middle C",
      "Woodwind.play() Middle C"
    });
  }
} ///:~




O resto do código funciona do mesmo jeito. Não importa se você está fazendo upcast para uma classe “regular” chamada Instrument, uma classe abstract chamada Instrument, ou a uma interface chamada Instrument. O comportamento é o mesmo. De fato, você pode ver no método tune( ) que não há qualquer evidência sobre se Instrument é uma classe “regular”, uma classe abstract, ou uma interface. Este é o objetivo: Cada abordagem dá ao programador controle diferente sobre a maneira como os objetos são criados e usados. Comentários (em inglês)

“Herança múltipla” em Java



A interface não é simplesmente uma forma “mais pura” de uma classe abstract. Ela tem uma proposta mais elevada. Porque uma interface não tem implementação no todo—isto é, não há memória associada com uma interface—não há nada que previna muitas interfaces de serem combinadas. Isto é válido porque há horas em que você precisa dizer “Um x é um a e um b e um c.” Em C++, este ato de combinar interfaces de classe múltiplas é chamado herança múltipla, e ela carrega alguma difícil bagagem especial porque cada classe pode ter uma implementação. Em Java, você pode executar a mesma ação, mas somente uma das classes podem ter uma implementação, assim os problemas vistos em C++ não ocorrem com Java quando combinando interfaces:

PEJ323.png



Em uma classe derivada, você não é forçado a ter uma classe básica que é ou uma abstract ou “concreta” (uma sem métodos abstract). Se você faz herança de uma não-interface, você pode herdar de uma somente. Todo o resto dos elementos básicos devem ser interfaces. Você coloca todos os nomes de interface depois da palavra chave implements e os separa com vírgulas. Você pode ter tantas interfaces quantas quiser; cada uma se torna um tipo independente que você pode fazer um upcast. O exemplo seguinte mostra uma classe concreta combinada com diversas interfaces para produzir uma nova classe: Comentários (em inglês)



//: c08:Adventure.java
// Multiplas interfaces.

interface CanFight {
  void fight();
}

interface CanSwim {
  void swim();
}

interface CanFly {
  void fly();
}

class ActionCharacter {
  public void fight() {}
}

class Hero extends ActionCharacter
    implements CanFight, CanSwim, CanFly {
  public void swim() {}
  public void fly() {}
}

public class Adventure {
  public static void t(CanFight x) { x.fight(); }
  public static void u(CanSwim x) { x.swim(); }
  public static void v(CanFly x) { x.fly(); }
  public static void w(ActionCharacter x) { x.fight(); }
  public static void main(String[] args) {
    Hero h = new Hero();
    t(h); // Trata-o como um CanFight
    u(h); // Trata-o como um CanSwim
    v(h); // Trata-o como um CanFly
    w(h); // Trata-o como um ActionCharacter
  }
} ///:~




Você pode ver que Hero combina a classe concreta ActionCharacter com as interfaces CanFight, CanSwim, e CanFly. Quando você combina uma classe concreta com interfaces desta forma, a classe concreta deve vir primeiro, depois as interfaces. (Caso contrário o compilador dará um erro.) Comentários (em inglês)



Note que a assinatura de fight( ) é a mesma na interface CanFight e na classe ActionCharacter, e que fight( ) não é provida com uma definição em Hero. A regra para uma interface é que você pode herdar dela (como você verá brevemente), mas então você tem outra interface. Se você quer criar um objeto de um tipo novo, ele deve ser de uma classe com todas as definições providenciadas. Embora Hero não providencie explicitamente uma definição para fight( ), a definição vem com ActionCharacter, assim ela é automaticamente providenciada e é possível criar objetos de Hero. Comentários (em inglês)



Na classe Adventure, você pode ver que há quatro métodos que tomam como argumentos as várias interfaces e a classe concreta. Quando um objeto Hero é criado, ele pode ser passado para qualquer destes métodos, oque significa que ele está sofrendo um upcast para cada interface em volta. Por causa da maneira como interfaces são projetadas em Java, isto funciona sem qualquer esforço particular da parte do programador. Comentários (em inglês)



tenha em mente que a principal razão para interfaces é mostrada no exemplo anterior: estar apto a fazer upcast para mais de um tipo básico. Contudo, uma segunda razão para o uso de interfaces é a mesma para o uso de classes básicas abstract : prevenir o programador cliente de fazer um objeto desta classe e estabelecer que ela é somente uma interface. Isto traz uma questão: Você deveria usar uma interface ou uma classe abstract? Uma interface dá a você os benefícios de uma classe abstract e os benefícios de uma interface, assim se é possível criar sua classe básica sem quaisquer definições de método ou variáveis membro, você deveria sempre preferir interfaces a classes abstract. De fato, se você sabe de algo que vai uma classe básica, sua primeira escolha deveria ser fazê-la como uma interface, e somente se você for forçado a ter definições de métodos ou variáveis membro deveria alterar para uma classe abstract, ou se necessária uma classe concreta.Comentários (em inglês)

Colisões de nomes quando combinando interfaces



Você pode encontrar uma pequena armadilha quando implementando interfaces múltiplas. No exemplo anterior, ambos CanFight e ActionCharacter tem um idêntico método void fight( ). Isto não é um problema, porque o método é idêntico em ambos os casos. Mas oque acontece se não fosse? Aqui está um exemplo:



//: c08:InterfaceCollision.java

interface I1 { void f(); }
interface I2 { int f(int i); }
interface I3 { int f(); }
class C { public int f() { return 1; } }

class C2 implements I1, I2 {
  public void f() {}
  public int f(int i) { return 1; } // sobrecarregada
}

class C3 extends C implements I2 {
  public int f(int i) { return 1; } // sobrecarregada
}

class C4 extends C implements I3 {
  // Idêntico, sem problemas:
  public int f() { return 1; }
}

// Os métodos diferem apenas pelo tipo de retorno:
//! class C5 extende C implementa I1 {}
//! interface I4 extende I1, I3 {} ///:~




A dificuldade ocorre por causa do override, implementação, e sobrecarga que adquirem uma desagradável mistura juntos, e métodos sobrecarregados não podem diferir somente pelo tipo de retorno. Quando as últimas duas linhas estão descomentadas, a mensagem de erro diz tudo:



InterfaceCollision.java:23: f( ) in C cannot implement f( ) in I1; attempting to use incompatible return type
found : int
required: void
InterfaceCollision.java:24: interfaces I3 and I1 are incompatible; both define f( ), but with different return type



Usando os mesmos nomes de métodos em diferentes interfaces, cuja intenção é serem geralmente combinadas, causa confusão na legibilidade do código, inclusive. Tente evitá-lo. Comentários (em inglês)

Extendendo uma interface
com herança



Você pode adicionar facilmente declarações de um novo método para uma interface usando herança,e você pode também combinar diversas interfaces dentro de uma nova interface com herança. Em ambos os casos você obterá uma nova interface, como visto neste exemplo:



//: c08:HorrorShow.java
// Extendendo uma interface com herança.

interface Monster {
  void menace();
}

interface DangerousMonster extends Monster {
  void destroy();
}

interface Lethal {
  void kill();
}

class DragonZilla implements DangerousMonster {
  public void menace() {}
  public void destroy() {}
}

interface Vampire extends DangerousMonster, Lethal {
  void drinkBlood();
}

class VeryBadVampire implements Vampire {
  public void menace() {}
  public void destroy() {}
  public void kill() {}
  public void drinkBlood() {}
}

public class HorrorShow {
  static void u(Monster b) { b.menace(); }
  static void v(DangerousMonster d) {
    d.menace();
    d.destroy();
  }
  static void w(Lethal l) { l.kill(); }
  public static void main(String[] args) {
    DangerousMonster barney = new DragonZilla();
    u(barney);
    v(barney);
    Vampire vlad = new VeryBadVampire();
    u(vlad);
    v(vlad);
    w(vlad);
  }
} ///:~




DangerousMonster é uma simples extensão de Monster que produz uma nova interface. Isto é implementado em DragonZilla. Comentários (em inglês)



A sintaxe usada em Vampire funciona somente quando herdando interfaces. Normalmente, você pode usar extends com apenas uma única classe, mas como uma interface pode ser feita de multiplas outras interfaces, extends pode se referir a múltiplas interfaces básicas quando construindo uma interface nova. Como você pode ver , os nomes de interface são simplesmente separados com vírgulas. Comentários (em inglês)

Agrupando constantes



Por causa que quaisquer campos que você coloca dentro de uma interface é automaticamente static e final, a interface é uma ferramenta conveniente para criar grupos de valores constantes, muito melhor que se você fizesse com um enum em C ou C++. Por exemplo:



//: c08:Months.java
// Usando interfaces para criar grupos de constantes.
package c08;

public interface Months {
  int
    JANUARY = 1, FEBRUARY = 2, MARCH = 3,
    APRIL = 4, MAY = 5, JUNE = 6, JULY = 7,
    AUGUST = 8, SEPTEMBER = 9, OCTOBER = 10,
    NOVEMBER = 11, DECEMBER = 12;
} ///:~




Note que o estilo Java é usar todas as letras maiúsculas (com sublinhados para separar palavras múltiplas em um identificador simples) para finals static que tem inicializadores constantes. Comentários (em inglês)



Os campos em uma interface são automaticamente public, então é desnecessário especificar isso. Comentários (em inglês)



Você pode usar constantes do lado de fora da package pela importação de c08.* ou c08.Months como você poderia com qualquer outra package, e referenciar os valores com expressões como Months.JANUARY. Naturalmente, oque você obtem é só um int, então não há a segurança de tipos extras que o enum de C++ tem, mas esta (usada comumente) técnica é certamente uma melhoria nos valores de codificação fixos em seus programas. (Esta abordagem é frequentemente mencionada como estar usando “números mágicos,” e ela produz código muito difícil de manter.) Comentários (em inglês)



Se você quer fazer uma segurança extra de tipo, você pode construir uma classe como esta:[33]



//: c08:Month.java
// Um sistema mais robusto que enumeration.
package c08;
import com.bruceeckel.simpletest.*;

public final class Month {
  private static Test monitor = new Test();
  private String name;
  private Month(String nm) { name = nm; }
  public String toString() { return name; }
  public static final Month
    JAN = new Month("January"),
    FEB = new Month("February"),
    MAR = new Month("March"),
    APR = new Month("April"),
    MAY = new Month("May"),
    JUN = new Month("June"),
    JUL = new Month("July"),
    AUG = new Month("August"),
    SEP = new Month("September"),
    OCT = new Month("October"),
    NOV = new Month("November"),
    DEC = new Month("December");
  public static final Month[] month =  {
    JAN, FEB, MAR, APR, MAY, JUN,
    JUL, AUG, SEP, OCT, NOV, DEC
  };
  public static final Month number(int ord) {
    return month[ord - 1];
  }
  public static void main(String[] args) {
    Month m = Month.JAN;
    System.out.println(m);
    m = Month.number(12);
    System.out.println(m);
    System.out.println(m == Month.DEC);
    System.out.println(m.equals(Month.DEC));
    System.out.println(Month.month[3]);
    monitor.expect(new String[] {
      "January",
      "December",
      "true",
      "true",
      "April"
    });
  }
} ///:~




Month é uma classe final com um construtor private, assim nada pode herdar dele ou fazer qualquer instância dele. As únicas instâncias são as final static criadas na mesma classe: JAN, FEB, MAR, etc. Estes objetos também são usados no array month, o qual deixa você percorrer através de um array de objetos Month2. O método number( ) permite a você selecionar um Month dando seu correspondente número de mês. Em main( ) você pode ver o tipo seguramente; m é um objeto Month então ele pode ser atribuido só para um Month. O exemplo anterior Months.java providenciou somente valores int, então a uma variável int cujo objetivo é representar um mês poderia atualmente ser dado qualquer valor inteiro, o que não era muito seguro. Comentários (em inglês)



Esta abordagem também permite que você use == ou equals( ) intercambiavelmente, como mostrado no fim de main( ). Isto funciona porque pode haver uma instância de cada valor de Month. No Capítulo 11 você aprenderá sobre outra maneira de atribuir classes então os objetos podem ser comparados uns com os outros. Comentários (em inglês)



Há também um campo mês [month] no java.util.Calendar. Comentários (em inglês)



O projeto Apache’s Jakarta Commons contêm ferramentas para criar enumerations similares aos que foram mostrados no exemplo anterior, mas com menos esforço. Veja http://jakarta.apache.org/commons, sob “lang,” na package org.apache.commons.lang.enum. Este projeto também tem muitas outras bibliotecas potencialmente úteis. Comentários (em inglês)

Inicializando campos em interfaces



Campos definidos em interfaces são automaticamente static e final. Estes não podem ser “constantes vazias,” mas eles podem ser inicializados com expressões não-constantes. Por exemplo:



//: c08:RandVals.java
// Inicializando campos de interface com 
// inicializadores não constantes.
import java.util.*;

public interface RandVals {
  Random rand = new Random();
  int randomInt = rand.nextInt(10);
  long randomLong = rand.nextLong() * 10;
  float randomFloat = rand.nextLong() * 10;
  double randomDouble = rand.nextDouble() * 10;
} ///:~




Como os campos são static, eles são inicializados quando a classe é carregada pela primeira vez, o que acontece quando quaisquer dos campos são acessados pela primeira vez. Aqui está um teste simples: Comentários (em inglês)



//: c08:TestRandVals.java
import com.bruceeckel.simpletest.*;

public class TestRandVals {
  private static Test monitor = new Test();
  public static void main(String[] args) {
    System.out.println(RandVals.randomInt);
    System.out.println(RandVals.randomLong);
    System.out.println(RandVals.randomFloat);
    System.out.println(RandVals.randomDouble);
    monitor.expect(new String[] {
      "%% -?\\d+",
      "%% -?\\d+",
      "%% -?\\d\\.\\d+E?-?\\d+",
      "%% -?\\d\\.\\d+E?-?\\d+"
    });
  }
} ///:~




Os campos, naturalmente, não são parte da interface mas ao invés, são armazenados na área de memória estática para aquela interface. Comentários (em inglês)

Interfaces aninhadas



Interfaces podem ser aninhadas dentro de classes e com outras interfaces. [34] Isto revela um número de artifícios muito interessantes:



//: c08:nesting:NestingInterfaces.java
package c08.nesting;

class A {
  interface B {
    void f();
  }
  public class BImp implements B {
    public void f() {}
  }
  private class BImp2 implements B {
    public void f() {}
  }
  public interface C {
    void f();
  }
  class CImp implements C {
    public void f() {}
  }
  private class CImp2 implements C {
    public void f() {}
  }
  private interface D {
    void f();
  }
  private class DImp implements D {
    public void f() {}
  }
  public class DImp2 implements D {
    public void f() {}
  }
  public D getD() { return new DImp2(); }
  private D dRef;
  public void receiveD(D d) {
    dRef = d;
    dRef.f();
  }
}

interface E {
  interface G {
    void f();
  }
  // Redundante "public":
  public interface H {
    void f();
  }
  void g();
  // Não pode ser private com uma interface:
  //! private interface I {}
}

public class NestingInterfaces {
  public class BImp implements A.B {
    public void f() {}
  }
  class CImp implements A.C {
    public void f() {}
  }
  // Não pode implementar uma interface private exceto 
  // com aquela classe definida da interface:
  //! class DImp implements A.D {
  //!  public void f() {}
  //! }
  class EImp implements E {
    public void g() {}
  }
  class EGImp implements E.G {
    public void f() {}
  }
  class EImp2 implements E {
    public void g() {}
    class EG implements E.G {
      public void f() {}
    }
  }
  public static void main(String[] args) {
    A a = new A();
    // Não pode acessar A.D:
    //! A.D ad = a.getD();
    // Não retorna nada mas A.D:
    //! A.DImp2 di2 = a.getD();
    // Não pode acessar um membro da interface:
    //! a.getD().f();
    // Somente outro A pode fazer algo com getD():
    A a2 = new A();
    a2.receiveD(a.getD());
  }
} ///:~




A sintaxe para aninhamento de uma interface com uma classe é razoavelmente óbvia, e exatamente igual a de interfaces não-aninhadas, estas podem ter visibilidade public ou de acesso a package. Você pode também ver que ambas interfaces aninhadas public e de acesso a package podem ser implementadas como classes aninhadas public, acesso a package, e private. Comentários (em inglês)



Como um novo giro, interfaces podem também ser private, como visto em A.D (a mesma sintaxe de qualificação é usada para interfaces aninhadas tanto quanto para classes aninhadas). Oque é bom em uma interface aninhada private? Você pode supor que ela só pode ser implementada como uma classe interna private como em DImp, mas A.DImp2 mostra que ela também pode ser implementada como uma classe public. Contudo, A.DImp2 só pode ser usada como ela mesma. Você não está autorizado a mencionar o fato que ela implementa a interface private, então implementar uma interface private é uma maneira de forçar a definição de métodos naquela interface sem adicionar qualquer tipo de informação (isto é, sem permitir qualquer upcasting). Comentários (em inglês)



O método getD( ) produz um novo dilema concernente a interface private: É um método public que retorna uma referência a interface private. Oque você pode fazer com o valor de retorno deste método? Em main( ), você pode ver diversas tentativas para usar o valor de retorno, as quais todas falham. A única coisa que funciona é se o valor de retorno é passado para um objeto que tem permissão para usá-lo—neste caso, outro A, via método receiveD( ). Comentários (em inglês)



A Interface E mostra que interfaces podem ser aninhadas com outras. Contudo, as regras a respeito de interfaces-em particular, que todos os elementos de interface devem ser public—são estritamente reforçadas aqui, então uma interface aninhada com outra interface é automaticamente public e não pode ser tornada private. Comentários (em inglês)



NestingInterfaces mostra as várias maneiras que interfaces aninhadas podem ser implementadas. Em particular, observe que quando você implementa uma interface, você não é obrigado a implementar quaisquer interfaces aninhadas dentro. Também, interfaces private não podem ser implentadas do lado de fora de suas classes definidoras. Comentários (em inglês)



Inicialmente, estas características podem parecer como se elas estão adicionadas estritamente para consistência semântica, mas eu geralmente acho que uma vez que você conhece o artifício, você descobre com frequência lugares onde ele é útil. Comentários (em inglês)

Classes internas



É possível colocar uma definição de classe com outra definição de classe. Isto é chamado de classe interna . A classe interna é um artifício valioso porque permite que você agrupe classes que fazem parte logicamente juntas para controlar a visibilidade de uma com a outra. Contudo, é importante compreender que classes internas são distintamente diferentes de composição. Comentários (em inglês)



Enquanto você está aprendendo sobre elas, a necessidade de classes internas não é sempre óbvia. No fim desta seção, depois de toda a sintaxe e semântica das classes internas terem sido descritas, você encontrará exemplos que começariam a tornar claros os benefícios das classes internas. Comentários (em inglês)



Você cria uma classe interna da mesma maneira que você esperaria-colocando a definição de classe dentro de uma classe fechada: Comentários (em inglês)



//: c08:Parcel1.java
// Criando classes internas.

public class Parcel1 {
  class Contents {
    private int i = 11;
    public int value() { return i; }
  }
  class Destination {
    private String label;
    Destination(String whereTo) {
      label = whereTo;
    }
    String readLabel() { return label; }
  }
  // Usando classes internas parecendo só como 
  // usando qualquer outra classe, dentro de Parcel1:
  public void ship(String dest) {
    Contents c = new Contents();
    Destination d = new Destination(dest);
    System.out.println(d.readLabel());
  }
  public static void main(String[] args) {
    Parcel1 p = new Parcel1();
    p.ship("Tanzania");
  }
} ///:~




As classes internas, quando usadas dentro de ship( ), parecem apenas com o uso de quaisquer outras classes. Aqui, a única diferença prática é que os nomes estão aninhados dentro de Parcel1. Você verá em tempo que esta não é a única diferença. Comentários (em inglês)



Muito comumente, uma classe externa terá um método que retorna uma referência a classe interna, como esta:



//: c08:Parcel2.java
// Retornando uma referência para uma classe interna.

public class Parcel2 {
  class Contents {
    private int i = 11;
    public int value() { return i; }
  }
  class Destination {
    private String label;
    Destination(String whereTo) {
      label = whereTo;
    }
    String readLabel() { return label; }
  }
  public Destination to(String s) {
    return new Destination(s);
  }
  public Contents cont() {
    return new Contents();
  }
  public void ship(String dest) {
    Contents c = cont();
    Destination d = to(dest);
    System.out.println(d.readLabel());
  }
  public static void main(String[] args) {
    Parcel2 p = new Parcel2();
    p.ship("Tanzania");
    Parcel2 q = new Parcel2();
    // Definindo referências para classes internas:
    Parcel2.Contents c = q.cont();
    Parcel2.Destination d = q.to("Borneo");
  }
} ///:~




Se você quer fazer um objeto de uma classe interna em qualquer lugar exceto de dentro de um método não-static de uma classe externa, você deve especificar o tipo daquele objeto como OuterClassName.InnerClassName, como visto em main( ). Comentários (em inglês)

Classes internas e fazer upcast



Então, classes internas não parecem tão dramáticas. Afinal de contas, se está escondendo que você está depois, Java já tem um mecanismo de ocultação perfeitamente bom—é só dar a classe acesso a package (visível somente dentro de uma package) que é melhor que criá-la como uma classe interna. Comentários (em inglês)



Contudo, classes internas vem dentro de suas própriedades quando você inicia um upcast para uma classe básica, e em particular para uma interface. (O efeito de produzir uma referência a interface de um objeto que a implementa é essencialmente o mesmo que fazer um upcast para uma classe básica.) Isto é porque a classe interna-a implementação da interface—pode então ser completamente invisível e indisponível para todos, o que é conveniente para ocultar a implementação. Tudo que você obtem de volta é uma referência a classe básica ou a interface. Comentários (em inglês)



Primeiro, a interface comum será definida em seus próprios arquivos assim elas podem ser usadas em todos os exemplos:



//: c08:Destination.java
public interface Destination {
  String readLabel();
} ///:~




//: c08:Contents.java
public interface Contents {
  int value();
} ///:~




Agora Contents e Destination representam interfaces disponíveis ao programador cliente. (A interface, lembre, torna automaticamente todos os seus membros public.) Comentários (em inglês)



Quando você quer obter de volta uma referência para a classe básica ou para a interface, é possível que você não possa nunca encontrar o tipo exato, como mostrado aqui:



//: c08:TestParcel.java
// Retornando uma referência para uma classe interna.

class Parcel3 {
  private class PContents implements Contents {
    private int i = 11;
    public int value() { return i; }
  }
  protected class PDestination implements Destination {
    private String label;
    private PDestination(String whereTo) {
      label = whereTo;
    }
    public String readLabel() { return label; }
  }
  public Destination dest(String s) {
    return new PDestination(s);
  }
  public Contents cont() {
    return new PContents();
  }
}

public class TestParcel {
  public static void main(String[] args) {
    Parcel3 p = new Parcel3();
    Contents c = p.cont();
    Destination d = p.dest("Tanzania");
    // Ilegal = Não pode acessar classe private:
    //! Parcel3.PContents pc = p.new PContents();
  }
} ///:~




No exemplo, main( ) deve estar em classe separada em ordem para demonstrar a privacidade da classe interna PContents. Comentários (em inglês)



Em Parcel3, algo novo foi adicionado: A classe interna PContents é private, assim nada além de Parcel3 pode acessá-la. PDestination é protected, assim nada alem de Parcel3, classes na mesma package (desde que protected também dão acesso a package), e os herdeiros de Parcel3 podem acessar PDestination. Isto significa que o programador cliente tem conhecimento e acesso restrito a estes membros. De fato, você não pode nunca fazer um downcast para uma classe interna private (ou uma classe interna protected a menos que você é um herdeiro), porque você não pode acessar o nome, como você pode ver em class TestParcel. Então, a classe interna private provê uma maneira para o projetista da classe prevenir completamente quaisquer dependências tipo-código e para ocultar completamente detalhes sobre a implementação. Adicionalmente, extensão de interface é menos usual da perspectiva de programadores cliente pois o programador cliente não pode acessar quaisquer métodos adicionais que não são parte da interface public. Isto também provê uma oportunida para o compilador Java gerar código mais eficiente. Comentários (em inglês)



Classes normais (não-internas) não podem ser feitas private ou protected; a elas pode ser dado somente public ou acesso a package. Comentários (em inglês)

Classes internas
em métodos e escopos



Oque você tem visto ultimamente inclue o uso típico de classes internas. Em geral, o código que você irá escrever e ler envolvendo classes internas terá classes internas “planas” que são simples e fáceis de compreender. Contudo, o projeto para classes internas é bastante completo, e há um número de outros caminhos, mais obscuros, que você pode usar se você quiser; classes internas podem ser criadas com um método ou até mesmo um escopo arbitrário. Há duas razões para fazer isto: Comentários (em inglês)

  1. Como mostrado anteriormente, você está implementando uma interface de algum tipo então você pode criar e retornar uma referência. Análise
  2. Você está resolvendo um problema complicado e você quer criar uma classe para ajudar em sua solução, mas você não quer disponibilizar sua publicação. Análise


Nos exemplos seguintes, o código anterior será modificado para usar: Comentários (em inglês)

  1. Uma classe definida com um método
  2. Uma classe definida com um escopo dentro de um método
  3. Uma classe anônima implementando uma interface
  4. Uma classe anônima extendendo uma classe que tem um construtor não padrão
  5. Uma classe anônima que executa inicialização de campo
  6. Uma classe anônima que executa construção usando inicialização de instância (classes internas anônimas não podem ter construtores )


Embora ela seja uma classe ordinária com uma implementação, Wrapping também está sendo usada como uma “interface” comum para suas classes derivadas:



//: c08:Wrapping.java
public class Wrapping {
  private int i;
  public Wrapping(int x) { i = x; }
  public int value() { return i; }
} ///:~




Você notará que Wrapping tem um construtor que requer um argumento, para fazer coisas um pouco mais interessantes. Comentários (em inglês)



O primeiro exemplo mostra a criação de uma classe inteira com o escopo de um método (ao invés do escopo de outra classe). Isto é chamado de classe interna local:



//: c08:Parcel4.java
// Aninhando uma classe dentro de um método.

public class Parcel4 {
  public Destination dest(String s) {
    class PDestination implements Destination {
      private String label;
      private PDestination(String whereTo) {
        label = whereTo;
      }
      public String readLabel() { return label; }
    }
    return new PDestination(s);
  }
  public static void main(String[] args) {
    Parcel4 p = new Parcel4();
    Destination d = p.dest("Tanzania");
  }
} ///:~




A classe PDestination é parte de dest( ) mais que é de Parcel4. (Também note que você poderia usar o identificador de classe PDestination para uma classe interna dentro de cada classe no mesmo subdiretório sem haver conflito de nome.) Então, PDestination não pode ser acessado fora de dest( ). Note a conversão [Upcast] que ocorre na declaração return—nada sai de dest( ) exceto uma referência a Destination, a classe básica. Naturalmente, o fato de que o nome da classe PDestination é colocado dentro de dest( ) não significa que PDestination não é um objeto válido, uma vez que dest( ) retorna. Comentários (em inglês)



O próximo exemplo mostra como você pode aninhar uma classe interna com qualquer escopo arbitrário:



//: c08:Parcel5.java
// Aninhando uma classe dentro de um escopo.

public class Parcel5 {
  private void internalTracking(boolean b) {
    if(b) {
      class TrackingSlip {
        private String id;
        TrackingSlip(String s) {
          id = s;
        }
        String getSlip() { return id; }
      }
      TrackingSlip ts = new TrackingSlip("slip");
      String s = ts.getSlip();
    }
    // Não posso usá-la aqui! Fora do escopo:
    //! TrackingSlip ts = new TrackingSlip("x");
  }
  public void track() { internalTracking(true); }
  public static void main(String[] args) {
    Parcel5 p = new Parcel5();
    p.track();
  }
} ///:~




A classe TrackingSlip é aninhada dentro do escopo de uma declaração if. Isto não significa que a classe é criada condicionalmente—ela é compilada mesmo assim no conjunto como um todo. Contudo, não está disponível fora do escopo no qual ela foi definida. Além disso, parece apenas com uma classe ordinária. Comentários (em inglês)

Classes internas anônimas



O proximo exemplo pode parecer um pouco estranho:



//: c08:Parcel6.java
// Um método que retorna uma classe interna anônima.

public class Parcel6 {
  public Contents cont() {
    return new Contents() {
      private int i = 11;
      public int value() { return i; }
    }; // Ponto-e-vírgula exigido neste caso
  }
  public static void main(String[] args) {
    Parcel6 p = new Parcel6();
    Contents c = p.cont();
  }
} ///:~




O método cont( ) combina a criação de um valor de retorno com a definição da classe que representa aquele valor de retorno! Adicionalmente, a classe é anônima; ela não tem nome. Para tornar o assunto um pouco pior, ele parece como se você está começando a criar um objeto Contents: Comentários (em inglês)



return new Contents()




Mas então, antes de você obter um ponto-e-vírgula, você diz, “Mas espere, Eu acho que eu tropeçarei na definição da classe”: Comentários (em inglês)



return new Contents() {
  private int i = 11;
  public int value() { return i; }
};




O que esta sintaxe estranha significa é: “Crie um objeto de uma classe anônima que é herdada de Contents.” A referência retornada pela expressão new é automaticamente convertida para uma referência Contents. A sintaxe da classe interna anônima é uma abreviação de: Comentários (em inglês)



class MyContents implements Contents {
  private int i = 11;
  public int value() { return i; }
}
return new MyContents();




Na classe interna anônima, Contents é criado usando um construtor padrão. O código seguinte mostra o que fazer se sua classe básica precisa de um construtor com um argumento: Comentários (em inglês)



//: c08:Parcel7.java
// Uma classe interna anônima que chama 
// o construtor da classe básica.

public class Parcel7 {
  public Wrapping wrap(int x) {
    // Chama o construtor básico:
    return new Wrapping(x) { // Passa o argumento ao construtor.
      public int value() {
        return super.value() * 47;
      }
    }; // Ponto-e-vírgula exigida
  }
  public static void main(String[] args) {
    Parcel7 p = new Parcel7();
    Wrapping w = p.wrap(10);
  }
} ///:~




Isto é, você simplesmente passa o argumento apropriado para o construtor da classe básica, visto aqui como o x passado em new Wrapping(x).



O ponto e vírgula no final da classe interna anônima não marca o fim do corpo da classe (como acontece em C++). Ao invés, ele marca o fim da expressão que existe para conter a classe anônima. Então, é idêntico ao uso do ponto e vírgula de sempre. Comentários (em inglês)



Você pode também executar a inicialização quando você define campos em uma classe anônima:



//: c08:Parcel8.java
// Uma classe interna anônima que executa 
// inicialização. Uma versão abreviada de Parcel4.java.

public class Parcel8 {
  // Argumento deve ser final para usar dentro de uma 
  // classe interna anônima:
  public Destination dest(final String dest) {
    return new Destination() {
      private String label = dest;
      public String readLabel() { return label; }
    };
  }
  public static void main(String[] args) {
    Parcel8 p = new Parcel8();
    Destination d = p.dest("Tanzania");
  }
} ///:~




Se você está definindo uma classe interna anônima e quer usar um objeto que está definido fora da classe interna anônima, o compilador exige que a referência argumento seja final, como o argumento para dest( ). Se você esquecer, você receberá um erro em tempo de compilação. Comentários (em inglês)



Mais que simplesmente atribuição de um campo, a abordagem neste exemplo é perfeita. Mas e se você precisar executar alguma atividade como construtor? Você não pode ter um construtor nomeado em uma classe anônima (pois não há nome!), mas com inicialização de instância, você pode, com efeito, criar um construtor para uma classe interna anônima, como esta: Comentários (em inglês)



//: c08:AnonymousConstructor.java
// Criando um construtor para uma classe interna anônima.
import com.bruceeckel.simpletest.*;

abstract class Base {
  public Base(int i) {
    System.out.println("Base constructor, i = " + i);
  }
  public abstract void f();
}

public class AnonymousConstructor {
  private static Test monitor = new Test();
  public static Base getBase(int i) {
    return new Base(i) {
      {
        System.out.println("Inside instance initializer");
      }
      public void f() {
        System.out.println("In anonymous f()");
      }
    };
  }
  public static void main(String[] args) {
    Base base = getBase(47);
    base.f();
    monitor.expect(new String[] {
      "Base constructor, i = 47",
      "Inside instance initializer",
      "In anonymous f()"
    });
  }
} ///:~




Neste caso, a variável i não teve de ser final. Enquanto i é passado para o construtor básico da classe anônima, ele nunca é usado diretamente dentro da classe anônima. Comentários (em inglês)



Aqui está o tema “Parcel” com a inicialização de instância. Note que os argumentos para dest( ) devem ser final pois eles são usados com a classe anônima:



//: c08:Parcel9.java
// Usando "inicialização de instância" para executar a 
// construção em uma classe interna anônima.
import com.bruceeckel.simpletest.*;

public class Parcel9 {
  private static Test monitor = new Test();
  public Destination
  dest(final String dest, final float price) {
    return new Destination() {
      private int cost;
      // Inicialização de instância para cada objeto:
      {
        cost = Math.round(price);
        if(cost > 100)
          System.out.println("Over budget!");
      }
      private String label = dest;
      public String readLabel() { return label; }
    };
  }
  public static void main(String[] args) {
    Parcel9 p = new Parcel9();
    Destination d = p.dest("Tanzania", 101.395F);
    monitor.expect(new String[] {
      "Over budget!"
    });
  }
} ///:~




Dentro do inicializador de instância você pode ver código que não poderia ser executado como parte de um inicializador de campo (isto é, a declaração if). Então em efeito, um inicializador de instância é o construtor para uma classe interna anônima. Naturalmente, ele é limitado; você não pode sobrecarregar inicializadores de instância, então você pode ter somente um destes construtores. Comentários (em inglês)

O link para a classe externa



De longe, parece que classes internas são só um nome oculto e esquema de organização de código, o que é útil mas não totalmente convincente. Contudo, há outros motivos. Quando você cria uma classe interna, um objeto daquela classe interna tem um link para o objeto incluso que o fêz, e assim ele pode acessar os membros do objeto incluso—Sem quaisquer qualificações especiais. Em adição, classes internas tem acesso direto a todos os elementos na classe inclusora.[35] O exemplo seguinte demonstra isto: Comentários (em inglês)



//: c08:Sequence.java
// Mantem uma sequência de Objetos.
import com.bruceeckel.simpletest.*;

interface Selector {
  boolean end();
  Object current();
  void next();
}

public class Sequence {
  private static Test monitor = new Test();
  private Object[] objects;
  private int next = 0;
  public Sequence(int size) { objects = new Object[size]; }
  public void add(Object x) {
    if(next < objects.length)
      objects[next++] = x;
  }
  private class SSelector implements Selector {
    private int i = 0;
    public boolean end() { return i == objects.length; }
    public Object current() { return objects[i]; }
    public void next() { if(i < objects.length) i++; }
  }
  public Selector getSelector() { return new SSelector(); }
  public static void main(String[] args) {
    Sequence sequence = new Sequence(10);
    for(int i = 0; i < 10; i++)
      sequence.add(Integer.toString(i));
    Selector selector = sequence.getSelector();
    while(!selector.end()) {
      System.out.println(selector.current());
      selector.next();
    }
    monitor.expect(new String[] {
      "0",
      "1",
      "2",
      "3",
      "4",
      "5",
      "6",
      "7",
      "8",
      "9"
    });
  }
} ///:~




A Sequence é simplesmente um array de tamanho fixo de Object com uma classe embalada em volta dele. Você chama add( ) para adicionar um novo Object ao fim da sequência (se houver espaço). Para trazer cada um dos objetos na Sequence, há uma interface chamada Selector, a qual permite que você veja se você está no end( ), para olhar o current( ) Object, e para mover para o next( ) Object na Sequence. Porque Selector é uma interface, muitas outras classes podem implementar a interface de suas próprias maneiras, e muitos métodos podem tomar a interface como um argumento, em ordem para criar código genérico. Comentários (em inglês)



Aqui, a SSelector é uma classe private que providencia funcionalidades ao Selector. Em main( ), você pode ver a criação de uma Sequence, seguida pela adição de um número de objetos String. Então, um Selector é produzido com uma chamada a getSelector( ), e este é usado para se mover através da Sequence e selecionar cada item. Comentários (em inglês)



Por primeiro, a criação de SSelector parece com apenas outra classe interna. Mas examine detalhadamente. Note que cada um dos métodos—end( ), current( ), e next( ) referem-se a objects, oque é uma referência que não é parte de SSelector, mas ao invés é um campo private na classe inclusora. Contudo, a classe interna pode acessar métodos e campos da classe inclusora como se os possuisse. Isto se mostra muito conveniente, como você pode ver no exemplo anterior. Comentários (em inglês)



Assim uma classe interna tem acesso automático aos membros da classe inclusora. Como isto pode acontecer? A classe interna deve manter uma referência para o objeto particular da classe inclusora que foi responsável por sua criação. Então, quando você se refere a a um membro da classe inclusora, aquela referência (oculta) é usada para selecionar aquele membro. Afortunadamente, o compilador toma conta de todos estes detalhes para você, mas você pode agora também entender que um objeto de uma classe interna pode ser criado somente em associação com um objeto de uma classe inclusora. Construção de objetos de classes internas requer a referência ao objeto da classe inclusora, e o compilador reclamará se ele não puder acessar aquela referência. Muitas vezes isto ocorre sem qualquer intervenção da parte do programador. Comentários (em inglês)

Classes aninhadas



Se você não precisa uma conexão entre o objeto da classe interna e o objeto da classe externa, então você pode fazer a classe interna static. Isto é comumente chamado de classe aninhada.[36] Para compreender o significado de static quando aplicado a classes internas, você deve lembrar que o objeto de uma classe interna ordinária mantem implicitamente uma referência ao objeto da classe que o inclui. Isto não é verdade, contudo, quando você diz que uma classe interna é static. Uma classe aninhada significa: Comentários (em inglês)

  1. Você não precisa de um objeto da classe externa em ordem para criar um objeto da classe aninhada. Análise
  2. Você não pode acessar um objeto da classe externa static de um objeto de uma classe aninhada. Análise


Classes aninhadas são diferentes de classes internas comuns de outra maneira, inclusive. Campos e métodos em classes internas comuns podem somente estar no nível mais externo da classe, então classes internas comuns não podem ter dados static, campos static, ou classes aninhadas. Contudo, classes aninhadas podem ter todos estes: Comentários (em inglês)



//: c08:Parcel10.java
// Classes aninhadas (classes internas static).

public class Parcel10 {
  private static class ParcelContents implements Contents {
    private int i = 11;
    public int value() { return i; }
  }
  protected static class ParcelDestination
  implements Destination {
    private String label;
    private ParcelDestination(String whereTo) {
      label = whereTo;
    }
    public String readLabel() { return label; }
    // Classes aninhadas podem conter outros elementos static:
    public static void f() {}
    static int x = 10;
    static class AnotherLevel {
      public static void f() {}
      static int x = 10;
    }
  }
  public static Destination dest(String s) {
    return new ParcelDestination(s);
  }
  public static Contents cont() {
    return new ParcelContents();
  }
  public static void main(String[] args) {
    Contents c = cont();
    Destination d = dest("Tanzania");
  }
} ///:~




Em main( ), nenhum objeto Parcel10 é necessário; ao invés, você usa a sintaxe normal para selecionar um membro static para chamar os métodos que retornam referências a Contents e Destination. Comentários (em inglês)



Como você verá brevemente, em uma classe interna ordinária (não-static), o link para o objeto da classe externa é obtido com uma referência this especial. Uma classe aninhada não tem esta referência this especial, o que a torna análoga a um método static. Comentários (em inglês)



Normalmente, você não pode colocar qualquer código dentro de uma interface, mas uma classe aninhada pode ser parte de uma interface. Como a classe é static, ela não viola as regras para interfaces—a classe aninhada é somente colocada dentro do nome da interface:



//: c08:IInterface.java
// Classes aninhadas dentro de interfaces.

public interface IInterface {
  static class Inner {
    int i, j, k;
    public Inner() {}
    void f() {}
  }
} ///:~




Recentemente neste livro eu sugeri colocar um main( ) em toda classe para agir como uma cama de teste para aquela classe. Um defeito para isto é o montante de código compilado extra que você deve carregar. Se isto é um problema, você pode usar uma classe aninhada para manter seu código teste: Comentários (em inglês)



//: c08:TestBed.java
// Colocando código de teste em uma classe aninhada.

public class TestBed {
  public TestBed() {}
  public void f() { System.out.println("f()"); }
  public static class Tester {
    public static void main(String[] args) {
      TestBed t = new TestBed();
      t.f();
    }
  }
} ///:~




Isto gera uma classe separada chamada TestBed$Tester (para rodar um programa, você digita java TestBed$Tester). Você pode usar esta classe para teste, mas você não precisa incluí-la em seu pacote de produto; você pode simplesmente deletar TestBed$Tester.class antes de fechar o pacote. Comentários (em inglês)

Referindo-se ao objeto da classe externa



Se você precisa produzir a referência para o objeto da classe externa, você coloca o nome da classe externa seguido de um ponto e this. Por exemplo, na classe Sequence.SSelector, quaisquer destes métodos podem produzir a referência armazenada para a classe externa Sequence dizendo Sequence.this. A referência resultante é automaticamente do tipo correto. (Isto é conhecido e verificado em tempo de compilação, então não há atraso em tempo de execução.) Comentários (em inglês)



Algumas vezes você quer contar com algum outro objeto para criar um objeto de uma de suas classes internas. Para fazer isso você deve providenciar uma referência para o objeto da outra classe externa na nova expressão, assim:



//: c08:Parcel11.java
// Criando instâncias de classes internas.

public class Parcel11 {
  class Contents {
    private int i = 11;
    public int value() { return i; }
  }
  class Destination {
    private String label;
    Destination(String whereTo) { label = whereTo; }
    String readLabel() { return label; }
  }
  public static void main(String[] args) {
    Parcel11 p = new Parcel11();
    // Deve usar instância de classe externa 
    // para criar instâncias de classe interna:
    Parcel11.Contents c = p.new Contents();
    Parcel11.Destination d = p.new Destination("Tanzania");
  }
} ///:~




Par criar um objeto de uma classe interna diretamente, você não segue a mesma forma e se refere ao nome da classe externa Parcel11 como você poderia esperar, mas ao invés você deve usar um objeto da classe externa para fazer um objeto da classe interna:



Parcel11.Contents c = p.new Contents();




Então, não é possível criar um objeto da classe interna a menos que você já tenha um objeto da classe externa. Isto é porque o objeto da classe interna é silenciosamente conectado ao objeto da classe externa de onde ele foi feito. Contudo, se você faz uma classe aninhada (uma classe interna static), então ele não precisa de uma referência ao objeto da classe externa. Comentários (em inglês)

Alcançando o exterior de uma classe multi-aninhada



[37]Não importa o quão profundamente uma classe interna possa ser aninhada—ela pode acessar transparentemente todos os membros de todas as classes em que esteja aninhada dentro, como visto aqui:



//: c08:MultiNestingAccess.java
// Classes aninhadas podem acessar todos os membros de todos os 
// níveis das classes em que eles estão aninhados dentro.

class MNA {
  private void f() {}
  class A {
    private void g() {}
    public class B {
      void h() {
        g();
        f();
      }
    }
  }
}

public class MultiNestingAccess {
  public static void main(String[] args) {
    MNA mna = new MNA();
    MNA.A mnaa = mna.new A();
    MNA.A.B mnaab = mnaa.new B();
    mnaab.h();
  }
} ///:~




Você pode ver que em MNA.A.B, os métodos g( ) e f( ) são chamaveis sem qualquer qualificação (a despeito do fato de que eles são private). Este examplo também demonstra a sintaxe necessária para criar objetos de classes internas multi-aninhadas quando você cria objetos em uma classe diferente. A sintaxe “.new” produz o correto escopo, assim você não tem de qualificar o nome da classe na chamada do construtor. Comentários (em inglês)

Herdando de classes internas



Porque o construtor da classe interna deve ser anexado a uma referência ao objeto da classe inclusora, as coisas são levemente complicadas quando você herda de uma classe interna. O problema é que uma referência “secreta” ao objeto da classe inclusora deve ser inicializada, e na classe derivada ainda não há um objeto padrão para anexar. A resposta é usar uma sintaxe providenciada para fazer a associação explícita: Comentários (em inglês)



//: c08:InheritInner.java
// Herdando uma classe interna.

class WithInner {
  class Inner {}
}

public class InheritInner extends WithInner.Inner {
  //! InheritInner() {} // Não compilará
  InheritInner(WithInner wi) {
    wi.super();
  }
  public static void main(String[] args) {
    WithInner wi = new WithInner();
    InheritInner ii = new InheritInner(wi);
  }
} ///:~




Você pode ver que InheritInner está extendendo somente a classe interna, não a externa. Mas quando vem a hora de criar um construtor, um padrão não é bom, e você não pode só passar uma referência para um objeto inclusor. Adicionalmente, você deve usar a sintaxe Comentários (em inglês)



enclosingClassReference.super();




dentro do construtor. Isto providencia a referência necessária, e o programa então compilará. Comentários (em inglês)

Classes internas podem sofrer override?



Oque acontece quando você cria uma classe interna, então herda de uma classe inclusora e redefine a classe interna? Isto é, é possível fazer override de uma classe interna inteira? Isto parece que seria um conceito poderoso, mas fazer “override” em uma classe interna como se fosse outro método de uma classe externa não faz realmente nada: Comentários (em inglês)



//: c08:BigEgg.java
// Uma classe interna não pode ser sobrescrita como um método.
import com.bruceeckel.simpletest.*;

class Egg {
  private Yolk y;
  protected class Yolk {
    public Yolk() { System.out.println("Egg.Yolk()"); }
  }
  public Egg() {
    System.out.println("New Egg()");
    y = new Yolk();
  }
}

public class BigEgg extends Egg {
  private static Test monitor = new Test();
  public class Yolk {
    public Yolk() { System.out.println("BigEgg.Yolk()"); }
  }
  public static void main(String[] args) {
    new BigEgg();
    monitor.expect(new String[] {
      "New Egg()",
      "Egg.Yolk()"
    });
  }
} ///:~




O construtor padrão é sintetizado automaticamente pelo compilador, e este chama o construtor padrão da classe básica. Você pode pensar que como um BigEgg está sendo criado, a versão que sofreu “override” de Yolk seria usada, mas não é este o caso, como você vê nas saídas. Comentários (em inglês)



Este exemplo mostra que não há qualquer mágica extra na classe interna acontecendo quando você herda de uma classe externa. As duas classes internas são entidades completamente separadas, cada uma com seu próprio nome. Contudo, ainda é possível explicitar herança de classe interna: Comentários (em inglês)



//: c08:BigEgg2.java
// Herança apropriada de uma classe interna.
import com.bruceeckel.simpletest.*;

class Egg2 {
  protected class Yolk {
    public Yolk() { System.out.println("Egg2.Yolk()"); }
    public void f() { System.out.println("Egg2.Yolk.f()");}
  }
  private Yolk y = new Yolk();
  public Egg2() { System.out.println("New Egg2()"); }
  public void insertYolk(Yolk yy) { y = yy; }
  public void g() { y.f(); }
}

public class BigEgg2 extends Egg2 {
  private static Test monitor = new Test();
  public class Yolk extends Egg2.Yolk {
    public Yolk() { System.out.println("BigEgg2.Yolk()"); }
    public void f() {
      System.out.println("BigEgg2.Yolk.f()");
    }
  }
  public BigEgg2() { insertYolk(new Yolk()); }
  public static void main(String[] args) {
    Egg2 e2 = new BigEgg2();
    e2.g();
    monitor.expect(new String[] {
      "Egg2.Yolk()",
      "New Egg2()",
      "Egg2.Yolk()",
      "BigEgg2.Yolk()",
      "BigEgg2.Yolk.f()"
    });
  }
} ///:~




Agora BigEgg2.Yolk explicitamente extends Egg2.Yolk e faz override de seus métodos. O método insertYolk( ) permite BigEgg2 fazer upcast de um de seus próprios objetos Yolk dentro da referência y em Egg2, então quando g( ) chama y.f( ), a versão que sofreu um override de f( ) é usada. A segunda chamada a Egg2.Yolk( ) é a chamada ao construtor da classe básica do construtor de BigEgg2.Yolk. Você pode ver que a versão que sofreu override de f( ) é usada quando g( ) é chamado. Comentários (em inglês)

Classes internas locais



Como notado anteriormente, classes internas também podem ser criadas dentro de blocos de código, tipicamente dentro do corpo de um método. Uma classe interna local não pode ter um especificador de acesso porque ela não faz parte da classe externa, mas ela tem acesso as variáveis final no bloco de código corrente e a todos os membros da classe inclusora. Aqui está um exemplo comparando a criação de uma classe interna local com uma classe interna anônima: Comentários (em inglês)



//: c08:LocalInnerClass.java
// Mantem uma sequência de objetos.
import com.bruceeckel.simpletest.*;

interface Counter {
  int next();
}

public class LocalInnerClass {
  private static Test monitor = new Test();
  private int count = 0;
  Counter getCounter(final String name) {
    // Uma classe interna local:
    class LocalCounter implements Counter {
      public LocalCounter() {
        // Classe interna local pode ter um construtor
        System.out.println("LocalCounter()");
      }
      public int next() {
        System.out.print(name); // Acessa final[constante] local
        return count++;
      }
    }
    return new LocalCounter();
  }
  // A mesma coisa com uma classe interna anônima:
  Counter getCounter2(final String name) {
    return new Counter() {
      // Classe interna anônima não pode ter um construtor com 
      // nome, somente um inicializador de instância:
      {
        System.out.println("Counter()");
      }
      public int next() {
        System.out.print(name); // Acessa final local
        return count++;
      }
    };
  }
  public static void main(String[] args) {
    LocalInnerClass lic = new LocalInnerClass();
    Counter
      c1 = lic.getCounter("Local inner "),
      c2 = lic.getCounter2("Anonymous inner ");
    for(int i = 0; i < 5; i++)
      System.out.println(c1.next());
    for(int i = 0; i < 5; i++)
      System.out.println(c2.next());
    monitor.expect(new String[] {
      "LocalCounter()",
      "Counter()",
      "Local inner 0",
      "Local inner 1",
      "Local inner 2",
      "Local inner 3",
      "Local inner 4",
      "Anonymous inner 5",
      "Anonymous inner 6",
      "Anonymous inner 7",
      "Anonymous inner 8",
      "Anonymous inner 9"
    });
  }
} ///:~




Counter retorna o próximo valor na sequência. Ele é implementado como ambas uma classe local e uma classe interna anônima, ambas das quais tem os mesmos comportamentos e capacidades. Como o nome da classe interna local não é acessível do lado de fora do método, a única justificativa para usar uma classe interna local ao invés de uma classe interna anônima é se você precisa um construtor com nome e/ou um construtor sobrecarregado, pois uma classe interna anônima pode usar somente uma inicialização de instância. Comentários (em inglês)



A única razão para preferir uma classe interna local mais que uma classe interna anônima é se você precisa fazer mais que um objeto daquela classe. Comentários (em inglês)

Identificadores de classe interna



Como toda classe produz um arquivo .class que mantem todas as informações a respeito de como criar objetos daquele tipo (esta informação produz uma “meta-classe” chamada de objeto Class ), você pode supor que classes internas devem também produzir arquivos .class para conter a informação dos objetos daquela Class. Os nomes destes arquivos/classes tem uma fórmula restrita: o nome da classe inclusora, seguido por um ‘$’, seguido pelo nome da classe interna. Por exemplo, os arquivos .class criados por LocalInnerClass.java incluem: Comentários (em inglês)



Counter.class
LocalInnerClass$2.class
LocalInnerClass$1LocalCounter.class
LocalInnerClass.class




Se as classes internas são anônimas, o compilador simplesmente inicia gerando números como identificadores de classe interna. Se classes internas são aninhadas dentro de classes internas, seus nomes são simplesmente acrescentados depois de um ‘$’ e o(s) identificador(es) da classe externa. Comentários (em inglês)



Embora este esquema de geração de nomes internos é simples e direto, é também robusto e manipula muitas situações.[38] Como ele é o esquema padrão de nomeação para Java, os arquivos gerados são automaticamente independentes de plataforma. (Note que o compilador Java está alterando suas classes internas de todos os tipos e de outros modos para os fazer funcionar.) Comentários (em inglês)

Por que classes intenas ?



Neste ponto você viu uma porção de sintaxes de semânticas descrevendo a maneira como classes internas funcionam, mas isto não responde a questão de por que elas existem. Por que a Sun foi ter tanto trabalho assim para adicionar esta característica fundamental de linguagem? Comentários (em inglês)



Tipicamente, a classe interna herda de uma classe ou implementa uma interface, e o código na classe interna manipula o objeto da classe externa em que foi criada dentro. Então você poderia dizer que uma classe interna provê um tipo de janela dentro da classe externa. Comentários (em inglês)



Uma questão que corta o coração das classes internas é esta: Se eu só preciso de uma referência a uma interface, por que eu só não faço a classe externa implementar aquela interface? A resposta é “Se isto é tudo que você precisa, então é o que você deveria fazer.” Então o que é que distingue uma classe interna implementando uma interface de uma classe externa implementando a mesma interface? A resposta é que você não pode sempre ter a conveniência das interfaces—algumas vezes você está trabalhando com implementações. Então a razão que mais compele para classes internas é: Comentários (em inglês)





Sem a habilidade que as classes internas provêm de herdar—em efeito—de mais que uma classe concreta ou abstract , alguns projetos e problemas de programação seriam intratáveis. assim uma maneira de olhar a classe interna é como o restante da solução do problema da herança múltipla. Interfaces resolvem parte do problema, mas classes internas efetivamente permitem “herança de implementação múltipla.” Isto é, classes internas efetivamente permitem que você herde de mais de uma não-interface. Comentários (em inglês)



Para ver isto em mais detalhe, considere uma situação na qual você tem duas interfaces que devem algumas vezes ser implementadas dentro de uma classe. Por causa da flexibilidade das interfaces, você tem duas escolhas: uma classe única ou uma classe interna:



//: c08:MultiInterfaces.java
// Duas maneiras que uma classe pode implementar interfaces múltiplas.

interface A {}
interface B {}

class X implements A, B {}

class Y implements A {
  B makeB() {
    // Classe interna anônima:
    return new B() {};
  }
}

public class MultiInterfaces {
  static void takesA(A a) {}
  static void takesB(B b) {}
  public static void main(String[] args) {
    X x = new X();
    Y y = new Y();
    takesA(x);
    takesA(y);
    takesB(x);
    takesB(y.makeB());
  }
} ///:~




Naturalmente, isto assume que a estrutura de seu código faz sentido desta forma. Contudo, você terá ordinariamente algum tipo de direção por parte da natureza do problema sobre se deve usar um classe única ou uma classe interna. Mas sem quaisquer outras restrições, a abordagem no exemplo anterior não faz realmente muita diferença do ponto de vista da implementação. Ambos funcionam. Comentários (em inglês)



Contudo, se você tem classes abstract ou concretas ao invés de interfaces, você está repentinamente limitado pelo uso de classes internas se sua classe deve algumas vezes implementar ambas das outras:



//: c08:MultiImplementation.java
// Com classes concretas ou abstratas, classes 
// internas são a única maneira de produzir o efeito 
// da "herança de implementação múltipla."
package c08;

class D {}
abstract class E {}

class Z extends D {
  E makeE() { return new E() {}; }
}

public class MultiImplementation {
  static void takesD(D d) {}
  static void takesE(E e) {}
  public static void main(String[] args) {
    Z z = new Z();
    takesD(z);
    takesE(z.makeE());
  }
} ///:~




Se você não precisou resolver o problema da “herança de múltipla implementação”, você poderia codificar de forma concebível ao redor disso tudo sem precisar de classes internas. Mas com classes internas você tem estes artifícios adicionais: Comentários (em inglês)

  1. A classe interna pode ter instâncias múltiplas, cada uma com sua própria informação de estado que é independente da informação no objeto da classe externa. Análise
  2. Em uma classe externa única você pode ter diversas classes internas, cada uma delas implementando a mesma interface ou herdando da mesma classe de uma maneira diferente. Um exemplo disto será mostrado brevemente. Análise
  3. O ponto de criação do objeto de uma classe interna não está ligado a criação do objeto da classe externa.Análise
  4. Não há relacionamento “é-um” potencialmente confuso com a classe interna; é uma entidade separada. Análise


Como um exemplo, se Sequence.java não usou classes internas, você poderia dizer que “uma Sequence é um Selector,” e você só estaria habilitado a ter um Selector em existência para uma particular Sequence. Você pode facilmente ter um segundo método, getRSelector( ), que produz um Selector que se move de volta através da sequência. Este tipo de flexibilidade é disponível somente em classes internas. Comentários (em inglês)

Closures e Callbacks



Um closure é um objeto chamável que retem informação sobre o escopo em que foi criado. Desta definição, você pode ver que uma classe interna é um fechamento orientado a objeto, porque não só contêm cada peça de informação do objeto da classe externa (“o escopo no qual ele foi criado”), mas ele mantem automaticamente uma referência de volta para o objeto inteiro da classe externa, onde ele tem permissão para manipular todos os membros, mesmo os private. Comentários (em inglês)



Um dos argumentos que mais incentivam a inclusão de algum tipo de mecanismo de ponteiro em Java era permitir callbacks. Com um callback, algum outro objeto é determinado como uma peça de informação que permite chamar de volta para dentro do objeto originante em algum ponto mais recente. Isto é um conceito muito poderoso, como você verá mais tarde neste livro. Se um callback é implementado usando um ponteiro, contudo, você deve contar com o programador para se comportar e não usar impropriamente o ponteiro. Como você tem visto até agora, Java tende a ser mais cuidadosa que isto, assim ponteiros não foram incluidos na linguagem. Comentários (em inglês)



O fechamento providenciado pela classe interna é uma solução perfeita—mais flexível e seguro que um ponteiro. Aqui está um exemplo:



//: c08:Callbacks.java
// Usando classes internas para callbacks
import com.bruceeckel.simpletest.*;

interface Incrementable {
  void increment();
}

// Muido simples apenas para implementar a interface:
class Callee1 implements Incrementable {
  private int i = 0;
  public void increment() {
    i++;
    System.out.println(i);
  }
}

class MyIncrement {
  void increment() {
    System.out.println("Other operation");
  }
  static void f(MyIncrement mi) { mi.increment(); }
}

// Se sua classe deve implementar increment() de
// alguma outra maneira, você deve usar uma classe interna:
class Callee2 extends MyIncrement {
  private int i = 0;
  private void incr() {
    i++;
    System.out.println(i);
  }
  private class Closure implements Incrementable {
    public void increment() { incr(); }
  }
  Incrementable getCallbackReference() {
    return new Closure();
  }
}

class Caller {
  private Incrementable callbackReference;
  Caller(Incrementable cbh) { callbackReference = cbh; }
  void go() { callbackReference.increment(); }
}

public class Callbacks {
  private static Test monitor = new Test();
  public static void main(String[] args) {
    Callee1 c1 = new Callee1();
    Callee2 c2 = new Callee2();
    MyIncrement.f(c2);
    Caller caller1 = new Caller(c1);
    Caller caller2 = new Caller(c2.getCallbackReference());
    caller1.go();
    caller1.go();
    caller2.go();
    caller2.go();
    monitor.expect(new String[] {
      "Other operation",
      "1",
      "2",
      "1",
      "2"
    });
  }
} ///:~




Este exemplo também provê uma distinção a mais entre implementar uma interface em uma classe externa e fazer isso emuma classe interna. Callee1 é claramente uma solução simples em termos de código. Callee2 herda de MyIncrement, o qual já tem um método increment( ) diferente que faz algo não relacionado ao esperado pela interface Incrementable. Quando MyIncrement é herdado dentro de Callee2, increment( ) não pode ser override para uso por Incrementable, então você é forçado a providenciar uma implementação separada usando uma classe interna. Note também que quando você cria uma classe interna, você não adiciona nada ou modifica a interface da classe externa. Comentários (em inglês)



Note que tudo exceto getCallbackReference( ) em Callee2 é private. Para permitir qualquer conexão ao mundo externo, a interface Incrementable é essencial. Aqui você pode ver como interfaces permitem uma separação completa da interface para implementação. Comentários (em inglês)



A classe interna Closure implementa Incrementable para prover um gancho de volta para dentro de Callee2—mas um gancho seguro. Quem precisar da referência a Incrementable pode, naturalmente, só chamar increment( ) e ter nenhuma outra habilidade (diferente de um ponteiro, que permitiria a você rodar sem alvo). Comentários (em inglês)



Caller toma uma referência Incrementable em seu construtor (embora a captura da referência do callback poderia acontecer a qualquer tempo) e então, um pouco mais tarde, usa a referência para “chamar de volta” dentro da classe Callee. Comentários (em inglês)



O valor do callback está em sua flexibilidade; você pode decidir dinamicamente que métodos serão chamados em tempo de execução. O benefiício disto torna-se evidente no Capítulo 14, onde callbacks são usados em todo lugar para implementar funcionalidade a um GUI. Comentários (em inglês)

Classes internas e frameworks de controle



Um exemplo mais concreto do uso de classes internas pode ser encontrado em algo que eu referirei aqui como um framework de controle. Comentários (em inglês)



Um application framework é uma classe ou um conjunto de classes que são projetadas para resolver um tipo particular de problema. Para aplicar um application framework, você normalmente herda de uma ou mais classes e faz override de alguns dos métodos. O código que você escreve nos métodos que sofrem override adaptam a solução geral providenciada por aquele application framework em ordem para resolver seu problema específico (Este é um exemplo de um projeto padrão de Template Method; veja Thinking in Patterns (with Java) em www.BruceEckel.com). O control framework é um tipo particular de aplicativo framework dominado pela necessidade de responder a eventos; um sistema que primariamente responde a eventos é chamado em sistema event-driven [dirigido a eventos]. Um dos mais importantes problemas na programação de aplicações é interface gráfica do usuário (GUI), a qual é quase inteiramente dirigida a eventos. Como você poderá ver no Capítulo 14, a biblioteca Java Swing é um control framework que elegantemente resolve o problema GUI e que usa pesadamente classes internas. Comentários (em inglês)



Para ver como classes internas permitem a criação simples e o uso de frameworks de controle, considere um framework de controle cujo trabalho é executar eventos sempre que esses eventos estão “prontos.” Embora “pronto” possa não significar nada, neste caso o padrão será baseado no tempo do relógio. O que segue é um framework de controle que não contêm nenhum informação específica sobre oque ele está controlando. Esta informação é suprida durante a herança, quando o “método modelo” é implementado. Comentários (em inglês)



Primeiro, aqui está a interface que descreve qualquer evento de controle. É uma classe abstract ao invés de uma interface atual porque o comportamento padrão é executar o controle baseado em tempo. Então, algumas das implementações estão incluidas aqui: Comentários (em inglês)



//: c08:controller:Event.java
// Os métodos comuns para qualquer evento de controle.
package c08.controller;

public abstract class Event {
  private long eventTime;
  protected final long delayTime;
  public Event(long delayTime) {
    this.delayTime = delayTime;
    start();
  }
  public void start() { // Permite reinício
    eventTime = System.currentTimeMillis() + delayTime;
  }
  public boolean ready() {
    return System.currentTimeMillis() >= eventTime;
  }
  public abstract void action();
} ///:~




O construtor captura o tempo (do tempo de criação do objeto) quando você quer que o Evento execute e então chame start( ), o qual pega o tempo atual e adiciona o intervalo de tempo para produzir o tempo quando o evento ocorrerá. De preferência quando incluído no construtor, start( ) isto é um método separado porque, desta maneira, te permite reiniciar o contator após a execução do evento, então o Event objeto pode ser reutilizado. Por exemplo, se você quer um evento repetitivo, você pode simplesmente chamar start( ) dentro de seu método action( ) . Comentários (em inglês)



ready( ) avisa a você quando é hora de executar o método action( ). Naturalmente, ready( ) poderia sofrer um override em uma classe derivada para basear o Event em algo diferente que o tempo. Comentários (em inglês)



O seguinte arquivo contêm o framework de controle atual que gerencia e dispara eventos. Os objetos Event são mantidos dentro de um contendor de objetos do tipo ArrayList, sobre o qual você aprenderá mais no Capítulo 11. Por agora, tudo que você precisa saber é que add( ) anexará um Object no fim de ArrayList, size( ) produz o número de entradas no ArrayList, get( ) encontra um elemento de ArrayList em um índice particular, e remove( ) retira um elemento de ArrayList, dado o número do elemento que você quer remover. Comentários (em inglês)



//: c08:controller:Controller.java
// Com Event, o framework genérico para sistemas de controle.
package c08.controller;
import java.util.*;

public class Controller {
  // Um objeto de java.util para manter objetos Event:
  private List eventList = new ArrayList();
  public void addEvent(Event c) { eventList.add(c); }
  public void run() {
    while(eventList.size() > 0) {
      for(int i = 0; i < eventList.size(); i++) {
        Event e = (Event)eventList.get(i);
        if(e.ready()) {
          System.out.println(e);
          e.action();
          eventList.remove(i);
        }
      }
    }
  }
} ///:~




O método run( ) circula através de eventList, caçando um objeto Event que esteja ready( ) para executar. Para cada um que ele encontra ready( ), ele imprime informações usando o método toString( ) do objeto, chama o método action( ), e então remove o Event da lista. Comentários (em inglês)



Note que por enquanto neste projeto você não sabe nada a respeito de oque exatamente um Event faz. E isto é o crucial do projeto—como ele “separa as coisas que alteram das coisas que ficam as mesmas.” Ou, para usar meu termo, o “vetor de alterações” é as ações diferentes dos vários tipos de objetos Event, e você expressa ações diferentes pela criação de diferentes subclasses de Event. Comentários (em inglês)



Isto é onde classes internas entram no jogo. Elas permitem duas coisas:

  1. Criar uma implementação inteira de um control framework em uma classe única, através do encapsulamento de tudo que é único nesta implementação. Classes internas são usadas para expressar os muitos diferentes tipos de action( ) necessarios para resolver o problema. Análise
  2. Classes internas evitam desta implementação de se tornar estranha, pois você está habilitado a acessar facilmente qualquer dos membros na classe externa. Sem esta habilidade o código pode se tornar tão desagradável que você teria que terminar procurando outra alternativa. Análise


Considere uma implementação particular do control framework projetado para controlar funções de jardinagem.[39] Cada ação é inteiramente diferente: ligar luzes, água, e ligar e desligar termostatos, soar campainhas, e reiniciar o sistema. Mas o control framework é projetado para isolar facilmente estes códigos diferentes. Classes internas deixam você ter múltiplas versões derivadas da mesma classe básica, Event, com uma classe única. Para cada tipo de ação, você herda uma nova classe interna Event, e escreve o código de controle na implementação de action( ). Comentários (em inglês)



Como é tipico com um framework de aplicação, a classe GreenhouseControls é herdada de Controller:



//: c08:GreenhouseControls.java
// Isto produz uma aplicação específica do 
// systema de controle, tudo em uma única classe. Classes 
// internas permitem que você encapsule funcionalidades 
// diferentes para tipo de evento.
import com.bruceeckel.simpletest.*;
import c08.controller.*;

public class GreenhouseControls extends Controller {
  private static Test monitor = new Test();
  private boolean light = false;
  public class LightOn extends Event {
    public LightOn(long delayTime) { super(delayTime); }
    public void action() {
      // Put hardware control code here to
      // physically turn on the light.
      light = true;
    }
    public String toString() { return "Light is on"; }
  }
  public class LightOff extends Event {
    public LightOff(long delayTime) { super(delayTime); }
    public void action() {
      // Coloque código de controle de hardware aqui para 
      // ligar fisicamente a luz.
      light = false;
    }
    public String toString() { return "Light is off"; }
  }
  private boolean water = false;
  public class WaterOn extends Event {
    public WaterOn(long delayTime) { super(delayTime); }
    public void action() {
      // Coloque código de controle hardware aqui.
      water = true;
    }
    public String toString() {
      return "Greenhouse water is on";
    }
  }
  public class WaterOff extends Event {
    public WaterOff(long delayTime) { super(delayTime); }
    public void action() {
      // Coloque código de controle de hardaware aqui.
      water = false;
    }
    public String toString() {
      return "Greenhouse water is off";
    }
  }
  private String thermostat = "Day";
  public class ThermostatNight extends Event {
    public ThermostatNight(long delayTime) {
      super(delayTime);
    }
    public void action() {
      // Coloque código de controle de hardaware aqui.
      thermostat = "Night";
    }
    public String toString() {
      return "Thermostat on night setting";
    }
  }
  public class ThermostatDay extends Event {
    public ThermostatDay(long delayTime) {
      super(delayTime);
    }
    public void action() {
      // Coloque código de controle de hardaware aqui.
      thermostat = "Day";
    }
    public String toString() {
      return "Thermostat on day setting";
    }
  }
  // Um exemplo de uma action() que insere uma 
  // nova dela mesmo na lista de eventos:
  public class Bell extends Event {
    public Bell(long delayTime) { super(delayTime); }
    public void action() {
      addEvent(new Bell(delayTime));
    }
    public String toString() { return "Bing!"; }
  }
  public class Restart extends Event {
    private Event[] eventList;
    public Restart(long delayTime, Event[] eventList) {
      super(delayTime);
      this.eventList = eventList;
      for(int i = 0; i < eventList.length; i++)
        addEvent(eventList[i]);
    }
    public void action() {
      for(int i = 0; i < eventList.length; i++) {
        eventList[i].start(); // Roda novamente cada evento.
        addEvent(eventList[i]);
      }
      start(); // Roda novamente este Evento
      addEvent(this);
    }
    public String toString() {
      return "Restarting system";
    }
  }
  public class Terminate extends Event {
    public Terminate(long delayTime) { super(delayTime); }
    public void action() { System.exit(0); }
    public String toString() { return "Terminating";  }
  }
} ///:~




Note que light, water, e thermostat pertencem a classe externa GreenhouseControls, e ainda que as classes internas podem acessar aqueles campos sem qualificação ou permissão especial. Também, que a maioria dos métodos action( ) involvem algum tipo de controle de hardware. Comentários (em inglês)



Muitas das classes Event parecem similares, mas Bell e Restart são especiais. Bell soa e então adiciona um novo objeto Bell a lista de eventos, então ele irá soar novamente mais tarde. Note como classes internas quase parecem como múltipla herança: Bell e Restart tem todos os métodos de Event e também parece ter todos os métodos da classe externa GreenhouseControls. Comentários (em inglês)



Restart dá um array de objetos Event que ele adiciona ao controlador. Como Restart( ) é só mais um objeto Event, você pode também adicionar um objeto Restart com Restart.action( ) então o sistema se reinicia regularmente. Comentários (em inglês)



A seguinte classe configura o sistema pela criação de um objeto GreenhouseControls e adiciona vários tipos de objetos Event . Este é um exemplo de um projeto padrão Command: Comentários (em inglês)



//: c08:GreenhouseController.java
// Configura e executa um sistema de ajardinamento.
// {Args: 5000}
import c08.controller.*;

public class GreenhouseController {
  public static void main(String[] args) {
    GreenhouseControls gc = new GreenhouseControls();
    // Ao invés de ligação de hardware, você poderia passar  aqui 
    // informações de configuração de um arquivo texto:
    gc.addEvent(gc.new Bell(900));
    Event[] eventList = {
      gc.new ThermostatNight(0),
      gc.new LightOn(200),
      gc.new LightOff(400),
      gc.new WaterOn(600),
      gc.new WaterOff(800),
      gc.new ThermostatDay(1400)
    };
    gc.addEvent(gc.new Restart(2000, eventList));
    if(args.length == 1)
      gc.addEvent(
        gc.new Terminate(Integer.parseInt(args[0])));
    gc.run();
  }
} ///:~




Esta classe inicializa o sistema, então ela adiciona todos os eventos apropriados. Naturalmente, uma maneira mais flexível de efetuar isto é evitar codificação pesada de eventos e ao invés disso lê-los de um arquivo. (Um exercício no Capítulo 12 pede a você para modificar este exemplo para fazer só isso.) Se você providencia um argumento em linha de comando, ele usa este para terminar o programa após aqueles tantos milisegundos (isto é usado para teste). Comentários (em inglês)



Este exemplo deveria mover você para uma apreciação do valor das classes internas, especialmente quando usado com um framework de controle. Contudo, no Capítulo 14 você verá como classes internas podem ser elegantemente usadas para descrever as ações de uma interface gráfica de usuário. Por hora termine este capítulo e você deverá estar completamente convencido. Comentários (em inglês)

Resumo



Interfaces e classes internas são os conceitos mais sofisticados que você encontrará em muitas linguagens POO; por exemplo, não há nada como eles em C++. Ao mesmo tempo, eles resolvem o mesmo problema que C++ tenta resolver com sua possiblidade de herança múltipla (MI). Contudo, MI em C++ se mostrou muito mais difícil de usar, do que são as interfaces e classes internas de Java, e por comparação, muito mais acessíveis. Comentários (em inglês)



Embora as características sejam por elas mesmas claramente razoáveis, o uso destas características é um assunto de projeto, muito igual a polimorfismo. Com o passar do tempo, você reconhecerá melhor as situações onde você poderia usar uma interface, ou uma classe interna, ou ambos. Mas neste ponto neste livro, você deveria ao menos estar confortável com a sintaxe e semânticas. Como você vê estas características de linguagem em uso, você eventualmente as internalizará. Comentários (em inglês)

Exercícios



Soluções para os exercícios selecionados podem ser encontradas no documento eletrônico The Thinking in Java Annotated Solution Guide, disponível por uma pequena taxa em www.BruceEckel.com.

  1. Prove que os campos em uma interface são implicitamente static e final. Análise
  2. Crue yna interface contendo três métodos, em sua própria package. Implemente a interface em uma package diferente. Análise
  3. Prove que todos os métodos em uma interface são automaticamente public. Análise
  4. Em c07:Sandwich.java, crie uma interface chamada FastFood (com métodos apropriados) e altere Sandwich para que ele também implemente FastFood. Análise
  5. Crie três interfaces, cada uma com dois métodos. Herde uma nova interface das três, adicionando um novo método. Crie uma classe pela implementação da nova interface e também herdando de uma classe concreta. Agora escreva quatro métodos, cada um tomando uma das quatro interfaces como um argumento. Em main( ), crie um objeto de sua clase e passe-o para cada um dos métodos. Análise
  6. Modifique o Exercício 5 pela criação de uma classe abstract e herdando aquela dentro de uma classe derivada. Análise
  7. Modifique Music5.java pela adição da interface Playable. Mova a declaração play( ) de Instrument para Playable. Adicione Playable a classes derivadas incluindo-o na lista de implements. Altere tune( ) para que ele tome um Playable ao invés de um Instrument. Análise
  8. Altere o Exercício 6 no Capítulo 7 para que Rodent seja uma interface. Análise
  9. Ém Adventure.java, adicione uma interface chamada CanClimb, seguindo a forma das outras interfaces. Análise
  10. Escreva um programa que importe e use Month.java. Análise
  11. Siga o exemplo dado em Month.java, crie um enumeration de dias da semana.Análise
  12. Crie uma interface com ao menos um método, em sua própria package. Crie uma classe em uma package separada. Adicione uma classe interna protected que implemente a interface. Em uma terceira package, herde de sua classe e, dentro de um método, retorne um objeto da classe interna protected, fazendo um upcast para ainterface durante o retorno. Análise
  13. Crie uma interface com ao menos um método, e implemente aquela interface pela definição de uma classe interna com um método, o qual retorna uma referência a sua interface. Análise
  14. Repita o Exercício 13 mas defina a classe interna com um escopo com um método. Análise
  15. Repita o Exercício 13 usando uma classe interna anônima. Análise
  16. Modifique HorrorShow.java para implementar DangerousMonster e Vampire usando classes anônimas.
  17. Crie uma classe interna private que implemente uma interface public . Escreva um método que retorne uma referência a uma instância de uma classe interna private, faça um upcast para a interface. Mostre que a classe interna é completamente oculta tentando fazer um downcast dela. Análise
  18. Crie uma classe com um construtor não padrão (uma com argumentos) e um construtor não padrão (não um construtor “sem-arg”). Crie uma segunda classe que tem um método que retorna uma referência para a primeira classe. Crie o objeto para retornar fazendo uma classe interna anônima que herda da primeira classe. Análise
  19. Crie uma classe com um campo private e um método private. Crie uma classe interna com um método que modifica o campo da classe externa e chama o método da classe externa. Em um segundo método da classe externa, crie um objeto da classe interna e chame seu método, então mostre o efeito no objeto da classe externa . Análise
  20. Repita o Exercício 19 usando uma classe interna anônima. Análise
  21. Crie uma classe contendo uma classe aninhada. Em main( ), crie uma instância da classe interna. Análise
  22. Crie uma interface contendo uma classe aninhada. Implemente esta interface e crie uma instância da classe aninhada. Análise
  23. Crie uma classe contendo uma classe interna e que ela mesma contenha uma classe interna. Repita isto usando classes aninhadas. Note os nomes dos arquivos .class produzidor pelo compilador. Análise
  24. Crie uma classe com uma classe interna. Em uma classe separada, faça uma instância da classe interna. Análise
  25. Crie uma classe com uma classe interna que tem um construtor não padrão (um que toma argumentos).Crie uma segunda classe com um classe interna que herde da primeira classe interna.Análise
  26. Conserte o problema em WindError.java. Análise
  27. Modifique Sequence.java pela adição de um método getRSelector( ) que produza uma implementação diferente de interface Selector que se move de volta através da sequência do fim para o começo. Análise
  28. Crie uma interface U com três métodos. Crie uma classe A com um método que produz uma referência para U através da construção de uma classe interna anônima. Crie uma segunda classe B que contêm um array de U. B deveria ter um método que aceita e armazena referências a um U no array, um segundo método que atribui a uma referência no array (especificada pelo argumetno do método ) o valor null, e um terceiro método que se move através do array e chama os métodos em U. Em main( ), crie um grupo de objetos A e um único B. Preencha o B com referências a U produzidas pelo objeto A. Use o B para chamar de volta dentro de todos os objetos A. Remova algumas das referências U do objeto B. Análise
  29. Em GreenhouseControls.java, adicione classes internas Event que permita ao ventilador ligar e desligar. Configure GreenhouseController.java para usar estes objetos Event novos. Análise
  30. Herde de GreenhouseControls em GreenhouseControls.java para adicionar classes internas Event que liguem e desliguem geradores de mistura de água. Escreva uma nova versão de GreenhouseController.java para usar estes objetos Event novos.
  31. Mostre que uma classe interna tem acesso a elementos private de sua classe externa. Determine se o inverso é verdade. Análise





[33] Esta abordagem foi inspirada por um e-mail de Rich Hoffarth. Item 21 em Effective Java de Joshua Bloch (Addison-Wesley, 2001) cobre o tópico com muito mais detalhe.



[34] Obrigado a Martin Danner por fazer esta pergunta durante um seminário.



[35] Isto é muito diferente do projeto de classes aninhadas em C++, o qual é simplesmente um mecanismo de ocultação de nomes. Não há link para o objeto inclusor e não implica permissões em C++.



[36] Rudemente similar as classes aninhadas em C++, exceto que aquelas classes não podem acessar membros private como elas podem em Java.



[37] Obrigado novamente a Martin Danner.



[38] Por outro lado, ‘$’ é um meta-caracter para o envelope Unix e então você terá algumas vezes problemas quando listando os arquivos .class. Isto é um pouco estranho vindo da Sun, uma empresa baseada em Unix. Meu palpite é que ele não consideraram este assunto, mas ao invés acharam que teriam naturalmente que se focar nos arquivos de código fonte.



[39] Por alguma razão isto sempre tem sido um prazeroso problema para mim resolver; ele veio de meu recente livro C++ Inside & Out, mas Java permite uma solução muito mais elegante.


Anterior Próximo Página Inicial Índice Conteúdo