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

5: Hiding the Implementation



Uma consideração preliminar em projeto orientado a objeto é “separar as coisas que mudam das coisas que permanecem as mesmas.”



Isto é particularmente importante para bibliotecas. Os usuários (client programmers) desta biblioteca devem ser capazes de confiar na parte que estão usando, e saber que eles não precisarão ter que reescrever código se uma nova versão da biblioteca aparecer. Por outro lado, O criador da biblioteca deve ter a liberdade para fazer modificações e melhorias com a certeza de que o código do cliente não será afetado por estas alterações.Comentários (em inglês)



Isto pode ser conseguido por meio de convenção. Por exemplo, o programador da biblioteca deve concordar em não remover os métodos existentes quando modificar uma classe na biblioteca, uma vez que quebraria o código do client programmer. De qualquer modo, a situação oposta é mais espinhosa. No caso de um campo, como o criador da biblioteca sabe quais campos foram acessados por client programmers? Isto também é verdadeiro com métodos que são apenas parte da implementação de uma classe, e não foram utilizados diretamente pelo client programmer. Mas e se o criador da biblioteca quiser retirar uma implementação antiga e colocar uma nova? Modificar qualquer um destes membros poderia quebrar o código de um client programmer, por isso o criador da biblioteca possui limitações e não pode modificar qualquer coisa.Comentários (em inglês)



Para resolver esse problema, Java provê especificadores de acesso para permitir ao criador da biblioteca dizer o que é disponibilizado ao programador cliente e o que não é. Os níveis de controle acesso de "maior grau de acesso" à "menor grau de acesso" são public, protected, package access (o qual não tem palavra-reservada), e private. Vindo do próximo parágrafo você pode pensar que, como um projetista de bibliotecas, você irá querer manter tudo tão "private" quanto possível, e expor somente os métodos que você quer que o programador cliente use. Isso é perfeitamente correto, apesar de frequentemente não ser intuitivo para pessoas que programam em outras linguagens (especialmente C) e que são acostumadas à acessar tudo sem restrição. Por volta do final deste capítulo você deve estar convencido da importância do controle de acesso em Java.Comentários (em inglês)



No entanto, o conceito de uma biblioteca de componentes e o controle de quem pode acessar os componentes da mesma não está completo. Ainda existe a questão de como os componentes são agrupados em uma unidade de biblioteca, de forma coesa. Isso é controlado com a palavra-chave package em Java, e os especificadores de acesso são afetados se uma classe está no mesmo package ou em um package separado. Então para começar este capítulo, voce aprenderá como os componentes de uma biblioteca são coclocados em packages. Então você será capaz de entender o significado dos especificadores de acesso.Comentários (em inglês)

package: a unidade de biblioteca



Um package é o que se torna disponível quando você usa a palavra-chave import para trazer uma biblioteca inteira, como



import java.util.*;




Isso traz uma biblioteca inteira de utilidades, que é parte da distribuição Java padrão. Por exemplo, existe uma classe chamada ArrayList em java.util, então você agora pode especificar o seu nome completo java.util.ArrayList (o que você pode fazer sem a instrução import), ou você pode simplesmente dizer ArrayList (devido ao import). Comentários (em inglês)



Se você deseja trazer uma única classe, você pode determinar essa classe pelo seu nome na instrução import.



import java.util.ArrayList;




Agora você pode utilizar ArrayList sem qualificação alguma. Entretanto, nenhuma das outras classes em java.util estará disponível. Comentários (em inglês)



A razão para todas estas importações é providenciar um mecanismo para gerenciar name spaces. Os nomes de todos os membros de suas classes são separados uns dos outros. Um método f( ) dentro de uma classe A não confrontará com um f( ) que tem a mesma assinatura (lista de argumentos) na classe B. Mas e quanto aos nomes de classes? Suponha que você criou uma classe Stack que é instalada em uma máquina que já tem uma classe Stack que foi escrita por outro alguém? Este confronto potencial de nomes é o motivo da importância de ter um controle completo sobre os name spaces em Java, e estar apto a criar um nome completamente único sem levar em consideração as limitações da internet. Comentários (em inglês)



Muitos dos exemplos mais a frente neste livro existem em um único arquivo e foram projetados para uso local, então eles não perdem tempo com nomes de package. (Neste caso o nome da classe é colocado na “package padrão.”) Esta é certamente, uma opção, e por motivo de simplicidade esta proposta será usada sempre que possível durante o restante deste livro. Contudo, se você planeja criar bibliotecas ou programas que sejam compatíveis com outros programas Java na mesma máquina, você deve pensar como prevenir confrontos de nomes de classes. Comentários (em inglês)



Quando você cria um arquivo de código fonte para Java, ele é comumente chamado como unidade de compilação (algumas vezes como unidade de translação). Cada unidade de compilação deve ter um nome terminando em .java, e dentro da unidade de compilação pode haver uma classe public que deve ter o mesmo nome do arquivo (incluindo maiúsculas, mas excluindo a extensão do nome do arquivo .java ). Pode haver somente uma classe public em cada unidade de compilação, caso contrário o compilador reclamará. Se houver classes adicionais naquela unidade de compilação, elas serão escondidas do mundo exterior daquela package porque elas não são public, e elas formam classes de “suporte” para a classe public principal. Comentários (em inglês)



Quando você compila um arquivo .java , você recebe um arquivo de saída para cada classes do arquivo .java . Cada arquivo de saída tem o nome da classe no arquivo .java , mas com uma extensão .class. Assim você pode terminar com alguns poucos arquivos .class a mais de um número menor de arquivos .java . Se você já programou com uma linguagem compilada, você deve ter usado as saídas do compilador em uma forma intermediária (geralmente arquivos “obj”) que são então empacotadas juntas com outras deste tipo usando um linkador (para criar um arquivo executável) ou um bibliotecário (para criar uma biblioteca). Não é assim que Java funciona. Um programa funcional é um conjunto de arquivos .class , os quais podem ser empacotados e comprimidos em um ARquivo Java(JAR) (usando o arquivador Java jar ). O interpretador Java é responsável por encontrar, carregar, e interpretar [26] estes arquivos. Comentários (em inglês)



Uma biblioteca é um grupo destes arquivos de classes. Cada arquivo tem uma classe que é public (você não é obrigado a ter uma classe public , mas é o normal), assim há um componente para cada arquivo. Se você quizer dizer que todos estes componentes (cada um em seu próprio e separado arquivo .java e .class ) devem permanecer juntos, então é onde a palavra chave package deve aparecer. Comentários (em inglês)



Quando você diz:



package minhapackage;




no começo de um arquivo (se você usa uma declaração package , ela deve aparecer como a primeira linha não comentada do arquivo), você está declarando que esta unidade de compilação é parte de uma biblioteca chamada minhapackage. Ou, colocando de outra maneira, você está dizendo que o nome da classe public dentro desta unidade de compilação está sob a proteção do nome minhapackage, e ninguém que quizer usar o nome pode a menos que especifique o nome completo ou use a palavra chave import em combinação com minhapackage (usando as opções dadas anteriormente). Note que a convenção para nomes de packages Java é usar todas as letras minúsculas, mesmo para palavras intermediárias. Comentários (em inglês)



Por exemplo, suponha que o nome do arquivo é MinhaClasse.java. Isto significa que deve haver uma única classe public naquele arquivo, e que o nome daquela classes deve ser MinhaClasse (respeitando as letras maiúsculas):



package minhapackage;
public class MinhaClasse {
  // . . .




Agora, se alguém quiser usar MinhaClasse ou, pelo mesmo interesse, quaisquer das outras classes public na minhapackage, ele terá de usar a palavra chave import para tornar o nome ou nomes em minhapackage disponíveis. A alternativa é dar um nome completamente qualificado:



minhapackage.MinhaClasse m = new minhapackage.MinhaClasse();




A palavra chave import pode fazer isto muito mais limpo:



import minhapackage.*;
// . . . 
MinhaClasse m = new MinhaClasse();




Vale a pena manter em mente que oque as palavras chaves package e import permitem você fazer, como um designer de biblioteca, é separar nomes únicos globais assim você não terá conflitos de nomes, não importando quantas pessoas disponibilizem na Internet as classes que começam a escrever em Java. Comentários (em inglês)

Criando nomes únicos de package



Você pode observar que, como uma package não é realmente “empacotada” dentro de um arquivo único, uma package poderia ser feita de muitos arquivos .class , e as coisas poderiam ficar um pouco desordenadas. Para prevenir disso, uma coisa lógica a fazer é colocar todos os arquivos .class de uma package particular em um único diretório; Isto é, usar a estrutura hierarquica de arquivos do sistema operacional em vantagem própria. Esta é uma maneira de Java resolver o problema da desordem; você verá outra maneira mais tarde quando o utilitário jar for apresentado. Comentários (em inglês)



Colecionar os arquivos da package em um único subdiretório resolve dois outros problemas: criação de nomes únicos para a package, e encontrar aquelas classes que podem estar enterradas em algum lugar da estrutura de diretórios. Isto já está resolvido, como foi apresentado no Capítulo 2, pela codificação da localização do caminho dos arquivos .class dentro do nome da package. Por convenção, a primeira parte do nome da package é o inverso do nome do domínio na Internet do criador da classe. Como na Internet os nomes dos domínios estão assegurados para que sejam únicos, se você segue esta convenção, o nome de sua package será único e você nunca terá um conflito de nome. (Isto é, até que você perca o nome de domínio para alguém que comece a escrever códigos Java com o mesmo nome de caminho que você usou.) Naturalmente, se você não tem seu próprio nome de domínio, então você deve fabricar uma combinação diferente (algo como com seu primeiro e último nome) para criar nomes únicos de package. Se você já decidiu começar publicando código Java, é importante fazer um pequeno esforço relativo para ter um nome de domínio. Comentários (em inglês)



A segunda parte deste problema é resolver o nome da package dentro de um diretório na sua máquina, assim quando o programa Java roda e precisar carregar o arquivo .class (o que faz dinamicamente, no ponto do programa onde precisa criar um objeto daquela classe em particular, ou da primeira vez que você acessar um membro static da classe), ele pode localizar o diretório onde o arquivo .class reside. Comentários (em inglês)



O interpretador Java procede como a seguir. Primeiro, ele procura a variável de ambiente CLASSPATH[27] (atribuida via sistema operacional, e algumas vezes através do programa de instalação que instala o Java ou uma ferramenta baseada em Java em sua máquina). CLASSPATH contêm um ou mais diretórios que são usados como rotas na pesquisa por arquivos .class . Iniciando naquela rota, o interpretador tomará o nome da package e substituirá cada ponto com uma barra para gerar o nome de caminho da rota do CLASSPATH (assim package foo.bar.baz torna-se foo\\bar\\baz ou foo/bar/baz ou possivelmente algo diferente, dependendo de seu sistema operacional). Este é então concatenado às várias entradas do CLASSPATH. Isto é onde ele procura pelos arquivos .class com o nome correspondente a classe que você está tentando criar. (Ele também pesquisa alguns diretórios padrão relativos a onde o interpretador Java reside). Comentários (em inglês)



Para entender isto, considere o meu nome de domínio, o qual é bruceeckel.com. O inverso dele, com.bruceeckel, estabelece meu nome global único para minhas classes.(As extensões com, edu, org, etc., foram formalizadas em maiúsculas nas packages Java, mas isto foi alterado no Java 2 assim o nome inteiro da package está em minúsculo.) Eu posso mais adiante subdividir isto por decidir que eu quero criar uma biblioteca chamada simple, assim eu terminaria com um nome de package:



package com.bruceeckel.simple;




Agora este nome de package pode ser usado como um nome de proteção para os dois arquivos seguintes: Comentários (em inglês)



//: com:bruceeckel:simple:Vector.java
// Criando uma package.
package com.bruceeckel.simple;

public class Vector {
  public Vector() {
    System.out.println("com.bruceeckel.simple.Vector");
  }
} ///:~




Quando você criar suas próprias packages, você descobrirá que a declaração package deve ser o primeiro código não comentado no arquivo. O segundo arquivo se parece muito com o primeiro: Comentários (em inglês)



//: com:bruceeckel:simple:List.java
// Criando uma package
package com.bruceeckel.simple;

public class List {
  public List() {
    System.out.println("com.bruceeckel.simple.List");
  }
} ///:~




Ambos os arquivos estão colocados no subdiretório em meu sistema: Comentários (em inglês)



C:\DOC\JavaT\com\bruceeckel\simple




Se você voltar através dele, você pode ver o nome da package com.bruceeckel.simple, mas e sobre a primeira parte do caminho? Isto tem de ser levado com cuidado na variável de ambiente CLASSPATH, a qual, em minha máquina, é: Comentários (em inglês)



CLASSPATH=.;D:\JAVA\LIB;C:\DOC\JavaT




Você pode ver que a CLASSPATH pode conter uma quantidade de caminhos alternativos de pesquisa. Comentários (em inglês)



Contudo, há variações quando usando arquivos JAR. Você deve colocar o nome do arquivo JAR na classpath, não só o caminho onde está localizado. Assim, para um JAR chamado grape.jar sua classpath incluiria:



CLASSPATH=.;D:\JAVA\LIB;C:\flavors\grape.jar




Uma vez que a classpath está atribuida apropriadamente, o arquivo seguinte pode ser colocado em qualquer diretório:



//: c05:LibTest.java
// Usar a biblioteca.
import com.bruceeckel.simpletest.*;
import com.bruceeckel.simple.*;

public class LibTest {
  static Test monitor = new Test();
  public static void main(String[] args) {
    Vector v = new Vector();
    List l = new List();
    monitor.expect(new String[] {
      "com.bruceeckel.simple.Vector",
      "com.bruceeckel.simple.List"
    });
  }
} ///:~




Quando o compilador encontra a declaração import para a biblioteca simple , ele começa pesquisando nos diretórios especificados na CLASSPATH, procurando pelo subdiretório com\\bruceeckel\\simple, então pesquisa os arquivos compilados de nomes apropriados (Vector.class para Vector, e List.class para List). Note que ambas as classes e métodos desejados em Vector e List devem ser public. Comentários (em inglês)



Atribuir a CLASSPATH tem sido tão importante para usuários Java iniciantes (foi para mim, quando eu iniciei) que a Sun fez o JDK do Java 2 um pouco mais esperto. Você verá que quando o instala, mesmo se você não atribuir a CLASSPATH, estará habilitado a compilar e rodar programas Java básicos. No entanto, para compilar e rodar a package do código fonte deste livro (disponível em www.BruceEckel.com), você precisará adicionar o diretório básico da árvore de código do livro na sua CLASSPATH. Comentários (em inglês)

Colisões



O que acontece se duas bibliotecas são importadas via ‘*’ e elas incluem os mesmos nomes? Por exemplo, suponha um programa que faça isto:



import com.bruceeckel.simple.*;
import java.util.*;




Como java.util.* também contêm uma classe Vector , isto causa uma colisão potencial. Contanto que você não escreva o código que atualmente causa a colisão, tudo está OK—Isto é bom, porque caso contrário você pode terminar digitando uma porção para prevenir colisões que na verdade nunca aconteceriam. Comentários (em inglês)



A colisão ocorre se você tentar agora criar um Vector:



Vector v = new Vector();




A qual classe Vector isto se refere? O compilador não pode saber, e o leitor pode também não saber. Assim o compilador reclama e força você a ser mais explícito. Se eu quero o Vector Java padrão, por exemplo, eu devo dizer:



java.util.Vector v = new java.util.Vector();




Por causa desta (junto com a CLASSPATH) completa especificação da localização deste Vector, não há necessidade da declaração import java.util.* a menos que eu esteja usando algo da java.util. Comentários (em inglês)

Uma biblioteca de ferramentas customizáveis



Com este conhecimento, você agora pode criar suas próprias bibliotecas de ferramentas para reduzir ou eliminar a duplicação de código. Considere, por exemplo, criar uma alternativa para System.out.println( ) para reduzir a datilografia. Isto poderia ser parte de uma package chamada tools:



//: com:bruceeckel:tools:P.java
// As estenógrafas P.rint e P.rintln.
package com.bruceeckel.tools;

public class P {
  public static void rint(String s) {
    System.out.print(s);
  }
  public static void rintln(String s) {
    System.out.println(s);
  }
} ///:~




Você pode usar estas estenógrafas para imprimir uma String tanto com um retorno de linha (P.rintln( )) quanto sem um retorno de linha (P.rint( )). Comentários (em inglês)



Você pode supor que a localização deste arquivo deve ser em um diretório que parte de uma das localizações da CLASSPATH, então continua com/bruceeckel/tools. Após compilado, o arquivo P.class pode ser usado em qualquer lugar de seu sistema com a declaração import :



//: c05:ToolTest.java
// Usar a biblioteca de ferramentas.
import com.bruceeckel.tools.*;
import com.bruceeckel.simpletest.*;

public class ToolTest {
  static Test monitor = new Test();
  public static void main(String[] args) {
    P.rintln("Available from now on!");
    P.rintln("" + 100); // Força-o a ser uma String
    P.rintln("" + 100L);
    P.rintln("" + 3.14159);
    monitor.expect(new String[] {
      "Available from now on!",
      "100",
      "100",
      "3.14159"
    });
  }
} ///:~




Observe que todos os objetos podem ser facilmente forçados a uma representação String pela colocação deles em uma expressão String ; no exemplo anterior, mesmo iniciando a expressão com uma String vazia se faz o truque. Mas isto levanta uma observação interessante. Se você chamar System.out.println(100), funciona sem convertê-lo para uma String. Com alguma sobrecarga extra, você pode conseguir que a classe P faça isso melhor (Este é um exercício do fim deste capítulo). Comentários (em inglês)



Então de agora em diante, sempre que você propor uma nova utilidade, você pode adicioná-la a seu próprio diretório tools ou util . Comentários (em inglês)

Usando imports para alterar comportamento



Um artifício que foi perdido no Java é a compilação condicional do C, a qual permite que você altere um comutador e obtenha comportamento diferente sem alterar qualquer código. A razão para que este artifício seja deixado fora do Java é provavelmente porque ele é usado frequentemente em C para resolver problemas de portabilidade: Porções diferentes do código são compiladas dependendo da plataforma para a qual o código está sendo compilado. Como Java é planejada para ser automaticamente portável, então este artifício não seria necessário. Comentários (em inglês)



Entretanto, há outros usos avaliáveis para a compilação condicional. Um uso muito comum é para a depuração do código. Os artefatos de depuração são habilitados durante o desenvolvimento e desabilitados na distribuição do produto. Você pode conseguir isto pela alteração da package que é importada trocando o código usado em seu programa de uma versão de depuração para uma versão de distribuição. Esta técnica pode ser usada para qualquer tipo de código condicional. Comentários (em inglês)

Advertência sobre Packages



É importante lembrar que sempre que você cria uma package, você especifica implicitamente uma estrutura de diretório quando dá um nome a package. A package deve estar no diretório indicado por seu nome, o qual deve ser um diretório que seja pesquisável a partir da CLASSPATH. Experiências com a palavra chave package podem ser um pouco frustrantes na primeira vez, porque a menos que você acrescente o nome da package a regra do caminho do diretório, você receberá uma porção de misteriosas mensagens em tempo de execução sobre não estar habilitado a encontrar uma classe em particular, mesmo se esta classe estiver situada no mesmo diretório. Se você receber uma mensagem como essa, tente comentar a declaração da package , e se rodar, você saberá onde os problemas aconteceram. Comentários (em inglês)

Especificadores de acesso Java



Quando usados, os especificadores de acesso Java public, protected, e private são colocados em frente de cada definição de cada membro de sua classe, se é um campo ou um método. Cada especificador de acesso controla o acesso para somente aquela definição em particular. Isto é um contraste distinto do C++, no qual o especificador de acesso especificado controla todas definições seguintes até que outro especificador de acesso apareça. Comentários (em inglês)



De uma maneira ou de outra, tudo tem algum tipo de especificador de acesso para ele. Nas seções seguintes, você aprenderá tudo a respeito dos vários tipos de acesso, iniciando com o acesso padrão. Comentários (em inglês)

Acesso a package



O que acontece se nenhum especificador de acesso for dado a tudo, como em todos os exemplos anteriorer deste capítulo? O acesso padrão não tem palavra chave, mas é comumente referenciado como Acesso a package(e as vezes “amigavelmente”). Significa que todas as outras classes na package corrente tem acesso aquele membro, mas para todas as classes fora desta package, o membro aparece como sendo private. Como uma unidade de compilação—um arquivo—pode pertencer somente a uma única package, todas as classes dentro de uma única unidade de compilação são automaticamente disponíveis a cada outra via acesso a package. Comentários (em inglês)



Acesso a package permite a você agrupar classes relacionadas juntas em uma package assim elas podem interagir facilmente umas com as outras. Quando você coloca classes juntas em uma package, concedendo assim acesso mútuo aos membros do acesso-package, você se “adona” do código naquela package. Faz sentido que somente códigos de sua propriedade tenham acesso de package a outros códigos de sua propriedade. Você poderia dizer que acesso a package dá um significado ou uma razão para o agrupamento de classes juntas em uma package. Em muitas linguagens a maneira de você organizar suas definições em arquivos pode ser arbitrária, mas em Java você é compelido a organizá-las de uma forma sensível. Em adição, você provavelmente irá querer excluir classes que não deveriam ter acesso as classes e que estão definidas na package corrente. Comentários (em inglês)



A classe controla que código tem acesso a seus membros. Não há caminho mágico para “arrombar.” Código de outra package não pode se mostrar e dizer, “Olá, Eu sou um amigo de Bob!” e esperar para ver os membros protected, acesso-package, e private do Bob. A única maneira de conceder acesso a um membro é: Comentários (em inglês)

  1. Tornar o membro public. Então todos , em qualquer lugar, podem acessá-lo. Feedback
  2. Dar o acesso de membro da package deixando de especificar qualquer acesso, e colocando as outras classes na mesma package. Então as outras classes naquela package podem acessar os membros. Feedback
  3. Como você verá no capítulo 6, quando herança for apresentada, uma classe herdada pode acessar um membro protected tanto quanto um membro public (mas não membros private ). Pode acessar membros do acesso-package somente se as duas classes estiverem na mesma package. Mas não se aborreça com isto por enquanto. Feedback
  4. Provê métodos “acessar/mudar” (também conhecidos como métodos “get/set”) que lêem e alteram valores. Esta é uma proposta muito civilizada em termos de POO, e é fundamental para JavaBeans, como você verá no Capítulo 14. Feedback

public: acesso a interface



Quando você usa a palavra chave public , significa que a declaração de membro que se segue imediatamente a public está disponível para todos, em particular para o programador cliente que usa a biblioteca. Suponha que você definiu uma package dessert contendo as seguintes unidades de compilação: Comentários (em inglês)



//: c05:dessert:Cookie.java
// Criar uma biblioteca.
package c05.dessert;

public class Cookie {
  public Cookie() {
   System.out.println("Construtor de bolo");
  }
  void bite() { System.out.println("Pedaço"); }
} ///:~




Lembre, o arquivo de classe produzido por Cookie.java deve residir em um subdiretório chamado dessert, em um diretório sob c05 (indicando o Capítulo 5 deste livro) que deve estar sob um dos diretórios da CLASSPATH. Não cometa o erro de pensar que Java irá sempre procurar no diretório corrente como um dos pontos de partida para pesquisa. Se você não tem um ‘.’ como um dos caminhos em sua CLASSPATH, Java não irá procurar lá.Comentários (em inglês)



Agora se você criar um programa que usa Cookie:



//: c05:Dinner.java
// Usando a biblioteca.
import com.bruceeckel.simpletest.*;
import c05.dessert.*;

public class Dinner {
  static Test monitor = new Test();
  public Dinner() {
   System.out.println("Construtor de jantar");
  }
  public static void main(String[] args) {
    Cookie x = new Cookie();
    //! x.bite(); // Não pode acessar
    monitor.expect(new String[] {
      "Construtor de bolo"
    });
  }
} ///:~




você pode criar um objeto Cookie , pois seu construtor é public e a classe é public. (Vamos ver mais do conceito de uma classe public mais tarde.) Contudo, o membro bite( ) está inacessível dentro de Dinner.java pois bite( ) provê acesso somente na package dessert, assim o compilador previne você de usá-lo. Comentários (em inglês)

A package padrão



Você pode se surpreender ao descobrir que o código seguinte compila, apesar de parecer que ele quebra as regras:



//: c05:Cake.java
// Acessar uma classe em uma unidade de compilação separada.
import com.bruceeckel.simpletest.*;

class Cake {
  static Test monitor = new Test();
  public static void main(String[] args) {
    Pie x = new Pie();
    x.f();
    monitor.expect(new String[] {
      "Pie.f()"
    });
  }
} ///:~




Em um segundo arquivo no mesmo diretório:



//: c05:Pie.java
// A outra classe.

class Pie {
  void f() { System.out.println("Pie.f()"); }
} ///:~




Você pode inicialmente vê-los como arquivos completamente estranhos, e ainda Cake está habilitado a criar um objeto Pie e chamar seu método f( ) ! (Note que você deve ter um ‘.’ em sua CLASSPATH para que os arquivos compilem.) Você normalmente pensou que Pie e f( ) tem acesso de package e então não está disponível para Cake. Eles tem acesso a package—esta parte está certa. A razão por que eles estão disponíveis em Cake.java é porque eles estão no mesmo diretório e não tem um nome de package declarado. Java trata arquivos como estes como uma parte implícita da “package padrão” daquele diretório, e então eles provêm acesso a package para todos os outros arquivos no mesmo diretório. Comentários (em inglês)

private: você não pode tocar estes!



A palavra chave private significa que nada pode acessar este membro exceto a classe que contêm este membro, de dentro do métodos daquela classe. Outras classes na mesma package não podem acessar membros private , assim é como se você estivesse ilhando a classe contra você mesmo. Por outro lado, não é improvável que uma package possa ser criada por várias pessoas que juntas colaboram, assim private permite que você altere livremente aquele membro sem se preocupar que vá afetar outra classe na mesma package. Comentários (em inglês)



O acesso a package padrão frequentemente provê uma quantidade adequada de encobrimento; lembre, um membro de acesso-package é inacessível para o programador cliente que está usando a classe. Isto é ótmo, desde que o acesso padrão é o único que você normalmente usa (e o único que você conseguirá se você esquecer de adicionar qualquer controle de acesso). Então, você pensará normalmente que sobre acessos a membros você vai querer explicitamente tornar todos public para o programador cliente, e como resultado, você pode inicialmente achar que você não usará a palavra chave private com frequência pois é tolerável seguir em frente sem ela. (Este é um constraste distinto com C++.) No entanto, mostra que o uso consistente de private é muito importante, especialmente quando está relacionado a multithreading. (Como você verá no Capítulo 13.) Comentários (em inglês)



Aqui há um exemplo do uso de private:



//: c05:IceCream.java
// Demonstra a palavra chave "private".

class Sundae {
  private Sundae() {}
  static Sundae makeASundae() {
    return new Sundae();
  }
}

public class IceCream {
  public static void main(String[] args) {
    //! Sundae x = new Sundae();
    Sundae x = Sundae.makeASundae();
  }
} ///:~




Isto mostra um exemplo no qual private torna-se cômodo: você pode querer controlar como um objeto é criado e prevenir alguém de acesso direto a um construtor em particular (ou todos eles). No exemplo anterior, você não pode criar um objeto Sundae via seu construtor; ao invés, você deve chamar o método makeASundae( ) para fazê-lo por você.[28] Comentários (em inglês)



Qualquer método que você esteja certo que é somente um método “auxiliar” daquela classe pode ser tornado private, para assegurar que você não o usará acidentalmente em outro lugar na package e então proibe a você mesmo de trocar ou remover o método. Tornar um método private garante que você mantenha esta opção. Comentários (em inglês)



O mesmo é verdadeiro para um campo private dentro da classe. A menos que você deva expor a implementação fundamental (o que é menos comum que você pode pensar), você deveria tornar todos os campos private. No entanto, só porque uma referência a um objeto é private dentro de uma classe não significa que algum outro objeto não possa ter uma referência public para o mesmo objeto. (Veja o Apêndice A para tópicos sobre aliasing.) Comentários (em inglês)

protected: acesso por herança



Entender o especificador de acesso protected requer um salto adiante. Primeiro, você deve estar ciente que não necessita entender herança (descrita no Capítulo 6) nesta sessão antes de continuar através deste livro. Mas para entendimento, aqui está uma descrição resumida e exemplo de uso do protected. Comentários (em inglês)



A palavra chave protected negocia com um conceito chamado herança, o qual toma uma classe existente—a qual nós nos referiremos como a classe base—e adiciona novos membros àquela classe sem tocar na classe existente. Você pode também alterar o comportamento de um membro existente da classe. Para herdar de uma classe existente, você diz que sua nova classe extends uma classe existente, assim:



class Foo extends Bar {




O restante das definições da classe parecem as mesmas. Comentários (em inglês)



Se você criar uma nova package e herdar de uma classe em outra package, os únicos membros que você terá acesso são os membros public da package original. (Naturalmente, se você fizer a herança na mesma package, você pode manipular todos os membros que tem acesso a package) Algumas vezes o criador de uma classe base gostaria de tomar um membro em particular e conceder acesso a classes derivadas mas não o mundo em geral. Isto é o que protected faz. protected também dá acesso a package—isto é, outras classes na mesma package podem acessar elementos protected .



Se você voltar a se referir ao arquivo Cookie.java, a classe seguinte não pode chamar o membro de acesso-package bite( ):



//: c05:ChocolateChip.java
// Não pode usar um membro de acesso-package de outra package.
import com.bruceeckel.simpletest.*;
import c05.dessert.*;

public class ChocolateChip extends Cookie {
  private static Test monitor = new Test();
  public ChocolateChip() {
   System.out.println("ChocolateChip constructor");
  }
  public static void main(String[] args) {
    ChocolateChip x = new ChocolateChip();
    //! x.bite(); // Não pode acessar bite
    monitor.expect(new String[] {
      "Cookie constructor",
      "ChocolateChip constructor"
    });
  }
} ///:~




Uma das coisas interessantes sobre herança é que se o método bite( ) existe na classe Cookie, então ele também existe em qualquer classe herdada de Cookie. Mas como bite( ) tem acesso a package e está em uma package estrangeira, está indisponível para nós nesta. Naturalmente, você poderia torná-lo public, mas então todos teriam acesso, e poder ser que isto não seja o que você queira. Se nós alterarmos a classe Cookie como a seguir:



public class Cookie {
  public Cookie() { 
    System.out.println("Construtor do Bolo");
  }
  protected void bite() {
    System.out.println("pedaço"); 
  }
}




então bite( ) ainda tem o equivalente do acesso a package com a package dessert, mas ele também está acessível a qualquer um herdado de Cookie. Entretanto, ele não é public. Comentários (em inglês)

Interface e implementação



Controle de acesso é frequentemente referido como implementação oculta. Esconder dados e métodos dentro de classes em combinação com a implementação oculta é chamado com frequência de encapsulação.[29] O resultado é um tipo de dado com características e comportamentos. Comentários (em inglês)



Controle de acesso coloca limites nos tipos de dado por duas razões importantes. A primeira é estabelecer oque o programador cliente pode e não pode usar. Você pode construir seus mecanismos internos dentro da estrutura sem se preocupar que o programador cliente acidentalmente tratará os internos como parte de uma interface que eles deveriam estar usando. Comentários (em inglês)



Isto sustenta diretamente a segunda razão, a qual é para separar a interface da implementação. Se a estrutura é usado em um conjunto de programas, mas os programadores cliente não podem fazer nada além de enviar mensagens para a interface public , então você está livre para alterar qualquer coisa que não é public (i.e., acesso-package, protected, ou private) sem quebrar o código cliente. Comentários (em inglês)



Nós estamos agora no mundo da programação orientada a objetos, onde uma classe é atualmente descrita como “uma classe de objetos,” como você descreveria uma classe de peixes ou uma classe de pássaros. Qualquer objeto pertencente a esta classe compartilhará aquelas características e comportamentos. A classe é uma descrição da forma como todos os objetos deste tipo parecerão e agirão. Comentários (em inglês)



Na linguagem POO original, Simula-67, a palavra chave class era usada para descrever um novo tipo de dado. A mesma palavra chave tem sido usada por muitas linguagens orientadas a objeto. Este é o ponto focal de todas as linguagens: a criação de novos tipos de dados que mais que só caixas contenham dados e métodos. Comentários (em inglês)



A classe é o conceito POO fundamental em Java. É uma das palavras chave que não será grifada em negrito neste livro—torná-lo-ia aborrecido com uma palavra repetida tão frequentemente como “class.” Comentários (em inglês)



Para clareza, você pode preferir um estilo de criação de classes que coloca os membros public no começo, seguidos pelos protected, acesso a package, e membros private . A vantagem é que o usuário da classe pode então ler de cima para baixo e ver primeiro oque é importante para ele (os membros public , porque eles podem ser acessados de fora do arquivo), e parar de ler quando ele encontrar os membros não-public , os quais são parte da implementação interna:



public class X {
  public void pub1() { /* . . . */ }
  public void pub2() { /* . . . */ }
  public void pub3() { /* . . . */ }
  private void priv1() { /* . . . */ }
  private void priv2() { /* . . . */ }
  private void priv3() { /* . . . */ }
  private int i;
  // . . .
}




Isto a tornará só parcialmente mais fácil de ler, porque a interface e a implementação ainda estão mescladas. Isto é, você ainda vê o código fonte—a implementação—porque ele está ali na classe. Em adição, a documentação de comentários suportada pelo javadoc (descrito no Capítulo 2) diminue a importância da legibilidade do código pelo programador cliente. Exibindo a interface para o consumidor de uma classe que é o trabalho real de um browser de classes , uma ferramenta que tem a função de enxergar todas as classes disponíveis e mostrar a você oque você pode fazer com elas (i.e., quais membros estão disponíveis) de um modo útil. Browsers de classes tem se tornado uma parte esperada de qualquer boa ferramenta de desenvolvimento Java. Comentários (em inglês)

Acesso de classe



Em Java, os especificadores de acesso podem também ser usados para determinar quais classes dentro de uma biblioteca estarão disponíveis para os usuários daquela biblioteca. Se você quer uma classe que seja disponível para um programador cliente, você usa a palavra chave public na definição da classe inteira. Isto controla se o programador cliente pode mesmo criar um objeto da classe. Comentários (em inglês)



Para controlar o acesso de uma classe, o especificador deve aparecer antes da palavra chave class. Então você pode dizer:



public class Widget {




Agora se o nome de sua biblioteca é mylib, qualquer programador cliente pode acessar Widget dizendo



import mylib.Widget;




ou



import mylib.*;




Contudo, há um conjunto extra de limitações: Comentários (em inglês)

  1. Pode haver apenas uma classe public por unidade de compilação (arquivo). A idéia é que cada unidade de compilação tem uma interface pública única representada por aquela classe public . Pode haver tantas classes de suporte acesso-package quantas você quiser. Se você tem mais que uma classe public dentro da unidade de compilação , o compilador dará a você uma mensagem de erro. Feedback
  2. O nome de uma classe public deve ser exatamente igual ao nome do arquivo que contem a unidade de compilação, inclusive maiúsculas. Assim para Widget, o nome do arquivo deve ser Widget.java, não widget.java ou WIDGET.java. Novamente, você receberá um erro em tempo de compilação se eles não estiverem de acordo. Feedback
  3. É possível, apesar de incomum, ter uma unidade compilação com uma classe não public no todo. Neste caso, você pode nomear o arquivo do jeito que quiser. Feedback


Oque acontece se você tem uma classe dentro de mylib que você está usando apenas para completar tarefas executadas por Widget ou alguma outra classe public em mylib? Você não quer ter o incômodo da criação de documentação para o programador cliente, e você acha que mais tarde você pode querer alterar completamente as coisas e jogar fora sua classe toda, substituindo por uma diferente. Para lhe dar esta flexibilidade, você precisa se assegurar que nenhum programador cliente se torne dependente dos detalhes ocultos de sua implementação particular dentro de mylib. Para completar isto, você só deixa a palavra chave public desligada na classe, em qualquer caso ela terá o acesso a package. (Aquela classe pode ser usado somente naquela package.) Comentários (em inglês)



Quando você criar uma classe com acesso-package, ainda faz sentido tornar os campos da classe private—você poderia sempre tornar tantos campos private quanto possível—mas é geralmente razoável dar aos métodos o mesmo acesso que a classe (acesso package). Como uma classe acesso-package é normalmente usada somente dentro da package, você só preciso tornar public os métodos da tal classe, se você for obrigado, e nestes casos o compilador dirá isso a você. Comentários (em inglês)



Note que uma classe não pode ser private (isto a tornaria acessível a nada exceto a ela mesma) ou protected.[30] Assim você tem apenas duas escolhas para acesso de classe: acesso package ou public. Se você não quer que ninguém mesmo tenha acesso àquela classe, você pode tornar todos os construtores private, através disso prevenindo qualquer um exceto você, de dentro de um membro static da classe, de criar um objeto daquela classe. Aqui está um exemplo: Comentários (em inglês)



//: c05:Lunch.java
// Demonstra os especificadores de classe. Tornando uma classe 
// efetivamente private com construtores private:

class Soup {
  private Soup() {}
  // (1) Permite criar via método static:
  public static Soup makeSoup() {
    return new Soup();
  }
  // (2) Cria um objeto static e retorna uma referência
  // após pedido.(O "Acontecimento" modelo):
  private static Soup ps1 = new Soup();
  public static Soup access() {
    return ps1;
  }
  public void f() {}
}

class Sandwich { // Usa Lunch
  void f() { new Lunch(); }
}

// Somente uma classe public por arquivo:
public class Lunch {
  void test() {
    // Não pode fazer isso! O construtor é Private.
    //! Soup priv1 = new Soup();
    Soup priv2 = Soup.makeSoup();
    Sandwich f1 = new Sandwich();
    Soup.access().f();
  }
} ///:~




Até agora, muitos dos métodos tem retornado ou void ou um tipo primitivo, assim a definição:



  public static Soup access() {
    return ps1;
  }




pode parecer um pouco confusa a primeira vista. A palavra depois do nome do método (access) conta oque o método retorna. Até agora, isto tem sido com muita frequência void, o que significa que ele retorna nada. Mas você pode também retornar uma referência a um objeto, que é o que acontece aqui. Este método retorna uma referência a um objeto da classe Soup. Comentários (em inglês)



A class Soup mostra como prevenir criação direta de uma classe tornando todos os construtores private. Lembre que se você não criar explicitamente ao menos um construtor, o construtor padrão (um construtor sem argumentos) será criado para você. Escrevendo um construtor padrão, ele não será criado automaticamente. Tornando-o private, nada pode criar um objeto desta classe. Mas agora, como fazer para que ninguém use esta classe? O exemplo anterior mostra duas opções. Primeiro, um método static é criado que cria um novo Soup e retorna uma referência a ele. Isto poderia ser útil se você quiser fazer algumas operações extras em Soup antes de retorná-lo, ou se você quiser manter a contagem de quantos objetos Soup criar (talvez para restringir sua população). Comentários (em inglês)



A segunda opção usa oque é chamado de design pattern, o qual é discutido em Thinking in Patterns (with Java) em www.BruceEckel.com. Este padrão particular é chamado de “singular” porque ele permite que somente um objeto único seja criado. O objeto da classe Soup é criado como um membro static private de Soup, assim há um e somente um, e você não pode obtê-lo exceto através do método public access( ). Comentários (em inglês)



Como mencionado anteriormente, se você não puser um especificador de acesso para o acesso da classe, o padrão é o acesso a package. Isto significa que um objeto daquela classe pode ser criado por qualquer outra classe na package, mas não de fora da package. (Lembre , todos os arquivos dentro do mesmo diretório que não tem declarações explícitas de package são implicitamente parte da package padrão daquele diretório.) Contudo, se um membro static daquela classe é public, o programador cliente pode ainda acessar aquele membro static mesmo que ele não possa criar um objeto daquela classe. Comentários (em inglês)

Sumário



Em qualquer relacionamento é importante ter limites que são respeitados por todas as partes envolvidas. Quando você cria uma biblioteca, você estabelece um relacionamento com o usuário daquela biblioteca—o programador cliente—que não é outro programador qualquer, mas um que está colocando junta uma aplicação ou usando sua biblioteca para construir uma grande biblioteca. Comentários (em inglês)



Sem regras, programadores clientes podem fazer qualquer coisa que quiserem com todos os membros das classes, mesmo que você prefira que eles não manipulem diretamente alguns dos membros. Tudo está descoberto para o mundo. Comentários (em inglês)



Este capítulo mostrou como classes são construidas para formar bibliotecas: primeiro, o modo como um grupo de classes é empacotado dentro de uma biblioteca, e segundo, a maneira como a classe controla o acesso a seus membros. Comentários (em inglês)



É estimado que um projeto programado em C começa a desmoronar em algum lugar entre 50K e 100K linhas de código porque C tem um “nome” singular: nomes começam a colidir, causando uma sobrecarga extra de gerenciamento. Em Java, a palavra chave package , o esquema de nomeação de packages, e a palavra chave import dão a você controle completo sobre os nomes, assim o caso da colisão de nomes é facilmente evitado. Comentários (em inglês)



Há duas razões para controlar acesso a membros. a primeira é manter as mãos do usuário longe de ferramentas que ele não deveria tocar: ferramentas que são necessárias para operações internas do tipo de dado, mas não parte da interface que o usuário precisa para resolver seus problemas particulares. Assim tornar métodos e campos private é um serviço para os usuários, porque eles podem facilmente ver oque é importante para eles e oque eles podem ignorar. Simplifica a compreensão deles sobre a classe. Comentários (em inglês)



A segunda e mais importante razão para controle de acesso é permitir que o projetista da biblioteca altere funções internas da classe sem se preocupar como isto irá afetar o programador cliente. Você pode construir uma classe de uma forma na primeira vez, e então descobrir que reestruturando seu código conseguirá velocidade muito maior. Se a interface e a implementação estiverem claramente separadas e protegidas, você pode completar isto sem forçar usuários a reescrever os códigos deles. Comentários (em inglês)



Especificadores de acesso em Java fornecem controle valioso para o criador da classe. Os usuários da classe podem ver claramente exatamente oque eles podem usar e oque ignorar. Mais importante, de qualquer forma, é a habilidade de assegurar que nenhum usuário se torne dependente de qualquer parte da implementação fundamental da classe. Se você entende isto como criador da classe, você pode alterar a implementação fundamental a vontade, porque você sabe que os programadores cliente não serão afetados pelas alterações; Eles não podem acessar aquela parte da classe. Comentários (em inglês)



Quando você tem a habilidade de alterar a implementação fundamental, você pode testar livremente seu projeto. Você também tem a liberdade para cometer enganos. Não importa quão cuidadosamente você planeja e projeta, você cometerá enganos. Sabendo que é relativamente seguro fazer estes erros significa que você terá mais experiência, você aprenderá mais rápido, e você terminará seu projeto mais cedo. Comentários (em inglês)



A interface publica da classe é oque o usuário pode ver, assim é a parte mais importante da classe para fazer “certo” durante análises e projeto. Mesmo que permita a você alguma reserva para alterações. Você não conseguirá a interface certa na primeira vez, você pode adicionar mais métodos , contanto que não remova algum que programadores cliente já tenham usado em seus códigos. Comentários (em inglês)

Exercícios



Respostas 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. Escreva um programa que cria um objeto ArrayList sem explicitar a importação da java.util.*. Feedback
  2. Na seção nomeada “package: a unidade de biblioteca,” transforme os fragmentos de códigos relativos a minhapackage em um conjunto compilado e rodando de arquivos Java. Feedback
  3. Na seção nomeada “Colisões,” tome os fragmentos de código e transforme-os em um programa e verifique se aquelas colisões de fato ocorrem. Feedback
  4. Generalize a classe P definida neste capítulo adicionando todas as versões sobrecarregadas de rint( ) e rintln( ) necessárias para manusear todos os tipos básicos diferentes de Java.Feedback
  5. Criar uma classe com campos e membros de métodos public, private, protected, e acesso-package. Criar um objeto da classe e ver que tipo de mensagem de compilador você obtem quando você tenta acessar todos os membros da classe. Esteja certo de que classes no mesmo diretório são parte da package “padrão”. Feedback
  6. Crie uma classe com dados protected . Crie uma segunda classe no mesmo arquivo com um método que manipula o dado protected na primeira classe. Feedback
  7. Altere a classe Cookie como especificado na seção nomeada “protected: acesso por herança.” Verifique que bite( ) não é public. Feedback
  8. Na seção entitulada “Acesso de classe” você encontrará fragmentos de código descrevendo mylib e Widget. Crie esta biblioteca, então crie um Widget na classe que não parte da package mylib . Feedback
  9. Criar um novo diretório e editar sua CLASSPATH para incluir este novo diretório. Copie o arquivo P.class (produzido pela compilação de com.bruceeckel.tools.P.java) para seu novo diretório e então altere os nomes do arquivo, a classe P dentro, e os nomes dos métodos. (Você pode também adicionar saídas adicionais para ver como funciona.) Criar outro programa em um diretório diferente que usa sua nova classe. Feedback
  10. Seguindo a forma do exemplo Lunch.java, criar uma classe chamada ConnectionManager que gerencia um array fixo de objetos Connection . O programador cliente não pode ser habilitado a criar explicitamente objetos Connection , mas pode os ter via método static em ConnectionManager. Quando o ConnectionManager roda fora dos objetos, ele retorna uma referência null . Teste as classes no método main( ). Feedback
  11. Criar o arquivo seguinte no diretório c05/local (presumidamente em sua CLASSPATH):


// c05:local:PackagedClass.java
package c05.local;
class PackagedClass {
  public PackagedClass() {
    System.out.println("Criando uma classe empacotada");
  }
}




Então crie o seguinte arquivo em um outro diretório diferente de c05:



// c05:foreign:Foreign.java
package c05.foreign;
import c05.local.*;
public class Foreign {
   public static void main (String[] args) {
      PackagedClass pc = new PackagedClass();
   }
}




Explique por que o compilador gera um erro. Poderia tornar a classe Foreign parte da package c05.local trocando algo? Comentários (em inglês)




[26] Não há nada em Java que force o uso de um interpretador. Existem compiladores de código nativo Java que geram um arquivo executável único.



[27] Quando se referindo a variável de ambiente, letras maiúsculas serão usadas (CLASSPATH).



[28] Há um outro efeito neste caso: Como o construtor padrão é o único definido, e é private, ele irá prevenir a herança desta classe. (Um assunto que será apresentado no Capítulo 6.)



[29] Contudo, pessoas frequentemente ser referem a implementação oculta isolada como encapsulação.



[30] Atualmente, uma inner class pode ser private ou protected, mas isto é um caso especial. Isto será apresentado no Capítulo 7.


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