Laura Lemay e Charles L. Perkins
Aqui na partida da Semana 3, agarrou provavelmente os fundamentos da língua de Java da Semana 1, e aplicou-os regularmente muitas vezes para criar applets na Semana 2. Pode parar aqui, se você gostar e for ao seu caminho alegre, sabendo bastante Java se vire.
A semana 3 estende o que já sabe. Nesta semana aprenderá mais sobre conceitos de Java promovidos como controle de acesso e pacotes, e aprenderá técnicas para estruturar grandes programas de um modo orientado ao objeto eficiente portanto o seu código pode manter-se mais facilmente e estender-se ou, se você assim selecione, facilmente reutilizado por outras pessoas.
Hoje começaremos com conceitos de língua de Java promovidos para organizar e projetar classes individuais:
As técnicas para programar aprenderá hoje implicam estratégias diferentes e maneiras de pensar sobre como uma classe se organiza. Mas uma coisa que todas estas técnicas têm em comum consiste em que todos eles usam palavras-chave de modificador especiais na língua de Java.
Na Semana 1 aprendeu como definir classes, métodos e variáveis em Java. Os modificadores são palavras-chave que acrescenta àquelas definições para modificar a sua significação. As classes, os métodos e as variáveis com modificadores ainda são classes, métodos e variáveis, mas os modificadores modificam o seu comportamento ou como Java trata aqueles elementos.
Os modificadores são palavras-chave de língua especiais que modificam a definição (e o comportamento) de uma classe, método ou variável.
Observar |
Já aprendeu sobre alguns destes modificadores antes no livro, mas aqui falaremos sobre eles detalhadamente portanto pode adquirir o quadro maior de porque os modificadores trabalham o modo que fazem. |
A língua de Java tem uma grande variedade de modificadores, inclusive
Alguns modificadores, como pode ver, podem aplicar-se só a classes e métodos ou só a métodos e variáveis. Para cada um dos modificadores, contudo, para usá-los põe-nos somente antes da classe, método ou definição variável, como nos seguintes exemplos:
public class MyApplet extends Java.applet.Applet { ... } private boolean engineState; static final double pi = 3.141559265 protected static final int MAXNUMELEMENTS = 128; public static void main(String args[]) { ...}
A ordem de modificadores é inaplicável à sua significação - a sua ordem pode variar e é realmente uma matéria do gosto. Escolha um estilo e logo seja compatível com ele em todas as partes de todas as suas classes. Aqui está a ordem habitual:
<access> static abstract synchronized volatile final native
Nesta definição, <access> pode ser public, protected ou private (mas não mais do que um deles).
Todos os modificadores são essencialmente opcionais; nenhum tem de aparecer em uma declaração. O bom estilo de programação orientado ao objeto, contudo, aconselha acrescentar tanto quanto são necessários para descrever melhor o uso desejado de, e restrições em, a coisa que declara. Em algumas situações especiais (dentro de uma interface, por exemplo, como descrito amanhã), certos modificadores definem-se implicitamente para você, e não tem de datilografá-los - vai se supor que estejam lá.
Os modificadores mais importantes na língua, do ponto de vista de classe e desenho de objeto, são aqueles que lhe permitem controlar a visibilidade de, e acesso a, variáveis e métodos dentro das suas classes.
Porque se preocuparia com o controle de acesso a métodos e variáveis dentro das suas classes? Se se lembra do caminho atrás ao começo deste livro, usei a analogia do PC - como pode comprar componentes de PC diferentes e pôr todos eles em conjunto para que interajam para criar um sistema maior.
Cada componente naquele sistema de PC trabalha de um determinado modo e tem um modo específico de interagir com outros componentes no sistema. Por exemplo, uma placa de vídeo liga a sua placa mãe usando uma tomada padrão e acordo de tomada, como faz o seu monitor às costas do cartão. E logo o seu computador pode falar a língua de software direita pelo cartão para levantar bits na tela.
A própria placa de vídeo tem um lote inteiro de outras características internas e capacidades além deste hardware básico e interface de software. Mas como um usuário ou o consumidor do cartão, não preciso de saber o que cada pedaço faz, nem preciso de tocá-los para conseguir que o cartão trabalhe. Considerando as interfaces padrão, o cartão compreende tudo e faz o que tem de fazer interiormente. E, de fato, o fabricante do cartão mais provavelmente não quer que eu entre e comece a estercar com batata frita individual ou capacidades do cartão, porque provavelmente parafusarei algo. É o melhor se somente apoiar a interface definida e deixar o trabalho interno fique escondido.
As classes e os objetos são o mesmo caminho. Enquanto uma classe pode definir muitos métodos e variáveis, não todos eles são úteis para um consumidor daquela classe, e alguns podem até ser perigosos se não se usarem no modo que se destinaram para usar-se.
O controle de acesso é sobre o controle de visibilidade. Quando um método ou a variável são visíveis a outra classe, os seus métodos podem referir (chamada ou modificar) aquele método ou variável. Proteger aqueles métodos e variáveis de exemplo limita a visibilidade e o uso daqueles métodos e variáveis (e também limita o que tem de documentar!). Como um desenhista de uma classe ou uma hierarquia inteira de classes, por isso, é uma boa ideia de definir o que a aparência externa de uma classe vai ser, que variáveis e os métodos serão acessíveis para outros usuários daquela classe, e quais são para o uso interno só. Isto chama-se encapsulation e é uma característica importante do desenho orientado ao objeto.
Encapsulation é o processo de esconder as partes internas da implementação de um objeto e permitir o acesso àquele objeto só por uma interface definida.
Pode observar que até este ponto não fizemos a maior parte disto em nenhum dos exemplos; de fato, quase cada variável e o método que criamos foram regularmente promíscuos e não tinham controle de acesso em absoluto. A razão aproximei o problema deste modo consiste em que faz para exemplos mais simples. Como torna-se um programador mais sofisticado e cria programas Java com muitas classes relacionadas, encontrará que acrescentar características como encapsulation e proteger acesso ao trabalho interno das suas classes fazem para programas melhor projetados em geral.
A língua de Java fornece quatro níveis da proteção de variáveis de exemplo e métodos: public, private, protected e package (de fato, o último não é uma forma explícita da proteção de Java, mas incluí-o aqui porque é agradavelmente aliterativo). Antes de aplicar níveis de proteção ao seu próprio código, deve saber o que cada forma significa e entenda as relações fundamentais que um método ou a variável dentro de uma classe podem ter a outras classes no sistema.
Observar |
Também pode proteger classes inteiras usando estes modificadores. Mas a proteção de classe aplica-se melhor uma vez que sabe quais os pacotes são, portanto posporemos falar sobre isto até amanhã. |
A primeira forma da proteção sobre a qual falaremos é aquela que tem usado inconscientemente todo este tempo: o que chamou a proteção de pacote. Em C, há noção de esconder um nome para que só as funções dentro de um arquivo original dado possam vê-lo. Java não tem esta espécie do controle; os nomes vão se encontrar felizmente em outros arquivos originais enquanto Java sabe onde encontrá-los. Em vez da proteção de nível do arquivo, Java tem o conceito de pacotes, que, como aprendeu no Dia 2, "A Programação orientada ao Objeto e Java", e aprenderão um lote inteiro mais sobre amanhã, são um grupo de classes relacionadas por objetivo ou função.
Os métodos e as variáveis com a proteção de pacote são visíveis a todas outras classes no mesmo pacote, mas não do lado de fora daquele pacote. Isto é a espécie da proteção que tem usado até este ponto, e não é muita proteção em absoluto. A maior parte do tempo quererá ser mais explícito quando definir a proteção de métodos daquela classe e variáveis.
A proteção de pacote, nível à revelia da proteção, significa que os seus métodos e as variáveis são acessíveis a todas as outras classes no mesmo pacote.
A proteção de pacote não é um modificador explícito que pode acrescentar ao seu método ou definições variáveis; em vez disso, é a proteção à revelia que adquire quando não acrescenta nenhum modificador de proteção àquelas definições.
Observar |
Não pode pensar que tem usado pacotes em absoluto até este ponto, mas de fato, tem. Em Java, se não puser explicitamente uma classe em um pacote, vai se incluir em um pacote à revelia que também inclui todas as outras classes que não estão em um pacote específico. Enquanto não definir uma classe para estar em um pacote trabalha para exemplos simples, é melhor se somente criar pacotes em vez disso. |
Da proteção à revelia vem com a proteção de pacote, pode ficar mais restritivo ou mais solto em como controla a visibilidade e acesso aos seus métodos e variáveis. A forma mais restritiva da proteção é private, que limita a visibilidade de métodos e variáveis de exemplo à classe na qual se definem. Uma variável de exemplo privada, por exemplo, pode usar-se por métodos dentro da mesma classe, mas não pode ver-se ou usar-se por qualquer outra classe ou objeto. Os métodos privados, analogamente, podem chamar-se por outros métodos dentro daquela mesma classe, mas não por qualquer outra classe. Além disso, nem as variáveis privadas nem os métodos privados se herdam por subclasses.
A proteção privada significa que os seus métodos e as variáveis são só acessíveis a outros métodos na mesma classe.
Para criar um método privado ou variável de exemplo, acrescente o modificador de private à sua definição:
class Writer { private boolean writersBlock = true; private String mood; private int income = 0; private void getIdea(Inspiration in) { . . . } Book createBook(int numDays, long numPages) { ... } }
Neste exemplo de código, os dados internos à classe Writer (as variáveis writersBlock, mood, e income e o método getIdea()) é tudo privado. O único método acessível do exterior da classe de Writer é o método de createBook(). createBook() é a única coisa outros objetos (objetos de editor, possivelmente?) pode perguntar o objeto de Writer de fazer; outros bits de dados são detalhes de implementação que podem afetar como o livro se escreve, mas não precisa de ser de outra maneira visível ou acessível de outras fontes.
A regra do polegar da proteção privada consiste em que quaisquer dados ou o comportamento interno à classe que outras classes ou as subclasses não devem ser comovedoras devem ser privados. O uso judicioso de variáveis privadas e métodos consiste em como limita a funcionalidade de uma classe a só aquelas características que quer visível do lado de fora daquela classe como com o exemplo dos componentes de PC. Lembre-se de que o emprego primário de um objeto é encapsular os seus dados - para escondê-lo da vista do mundo e limitar a sua manipulação. Encapsulation separa o desenho da implementação, minimiza o montante da informação que uma classe tem de saber sobre o outro para fazer o seu emprego, e reduz o ponto do código modifica-o tem de fazer se a sua implementação interna se modificar. Também, separando a interface pública da implementação privada, a interface da sua classe fica mais abstrata - isto é, objetivo mais geral e mais facilmente usado com outros objetivos. As subclasses da sua classe podem ignorar o comportamento mais abstrato da sua interface pública com as suas próprias implementações privadas.
Além de escolha e escolha que métodos quererá manter-se privado e que será acessível a outros, uma regra geral do polegar consiste em que todas as variáveis de exemplo em uma classe devem ser privadas, e deve criar métodos não-privados especiais para adquirir ou modificar aquelas variáveis. Aprenderá mais sobre esta regra e porque é importante um pouco depois, na seção "Proteção de Variável de exemplo e Métodos Accessor".
O contrário diametral da proteção privada e a forma menos restritiva da proteção, são public. Um método ou a variável que se declara com o modificador de public são acessíveis à classe na qual se define, todas as subclasses daquela classe, todas as classes no pacote e qualquer outra classe do lado de fora daquele pacote, em qualquer lugar no universo inteiro de classes de Java.
A proteção pública significa que os seus métodos e as variáveis são acessíveis a outros métodos em qualquer lugar dentro e fora da classe atual ou pacote.
Indicar que um método ou a variável são public é não necessariamente uma má coisa. Tão como a ocultação dos dados que são internos à sua classe usando private ajuda a encapsular um objeto, a utilização de métodos públicos define precisamente qual a interface para exemplos da sua classe é. Se esperar que as suas classes se reutilizem por outros programadores em outros programas, os métodos que estarão usando para usar a sua classe devem ser públicos.
De muitos modos, a proteção pública é muito semelhante à proteção de pacote à revelia. Ambos permitem a métodos e variáveis acessar-se por outras classes no mesmo pacote. A diferença ocorre quando cria pacotes de classes. As variáveis e os métodos com a proteção de pacote podem usar-se em classes que existem no mesmo pacote. Mas se alguém importar a sua classe para o seu próprio programa do exterior do seu pacote, aqueles métodos e as variáveis não serão acessíveis a menos que se tenham declarado pelo público. Mais uma vez, aprenderá mais sobre pacotes amanhã.
As declarações públicas trabalham como privados; simplesmente substitua a palavra public por private.
A forma final da proteção disponível em Java concerne a relação entre uma classe e as suas subclasses presentes e futuras declaradas dentro e fora de um pacote. Estas subclasses são muito mais fechadas para uma determinada classe do que a qualquer outra classe "exterior" pelas seguintes razões:
Para apoiar um nível especial da visibilidade reservada para subclasses um tanto menos restritivas do que privado, Java tem um nível intermediário do acesso entre o pacote e privado chamado, apropriadamente, protegido. Os métodos protegidos e as variáveis são acessíveis a qualquer classe dentro do pacote, como seriam se fossem pacote protegido, mas aqueles métodos e as variáveis também estão disponíveis para qualquer subclasse da sua classe que se definiu do lado de fora do seu pacote.
A proteção protegida significa que os seus métodos e as variáveis são acessíveis a todas as classes dentro do pacote, mas só a subclasses do lado de fora do pacote.
Nota técnica |
Em C ++, o modificador de protected significa que só as subclasses podem acessar um método ou variável, período. A significação de Java do protegido é ligeiramente diferente, também permitindo qualquer classe dentro do pacote acessar aqueles métodos e variáveis. |
Porque precisaria de fazer isto? Pode ter métodos na sua classe que são específicos para a sua implementação interna - isto é, não destinado para usar-se pelo grande público - mas seria útil para subclasses das suas próprias implementações internas. Neste caso, podem confiar ao desenvolvedor da subclasse - ser ele você ou alguém mais - para ser capazes de tratar a chamada ou a anulação daquele método.
Por exemplo, digamos tinha uma classe chamada AudioPlayer, que joga um arquivo de áudio digital. AudioPlayer tem um método chamado openSpeaker(), que é um método interno que interage com o hardware para preparar o falante para o jogo. openSpeaker() não é importante para ninguém do lado de fora da classe de AudioPlayer, portanto à primeira vista poderia querer fazê-lo privado. Um fragmento de AudioPlayer poderia olhar algo como isto:
class AudioPlayer { private boolean openSpeaker(Speaker sp_ { // implementation details } }
Isto trabalha perfeito se AudioPlayer não estiver indo subclassificar-se. Mas e se ia criar uma classe chamada StereoAudioPlayer que é uma subclasse de AudioPlayer? Esta classe quereria o acesso ao método de openSpeaker() para que possa ignorá-lo e fornecer a inicialização de falante estérea e específica. Ainda não quer o método geralmente disponível para objetos casuais (e portanto não deve ser público), mas quer que a subclasse tenha o acesso a ele assim protegido é somente a solução.
Nota técnica |
Em versões de Java e o JDK até 1.0.1, pode usar private e protected em conjunto ainda para criar outra forma da proteção que restringiria o acesso a métodos ou variáveis sozinho a subclasses de uma classe dada. Desde 1.0.2, esta capacidade retirou-se da língua. |
As diferenças entre vários tipos de proteção podem ficar muito confusas, em particular em caso de métodos protegidos e variáveis. A tabela 15.1, que resume exatamente o que se permite onde, ajudará a clarificar as diferenças do menos restritivo (público) às formas mais restritivas (privadas) da proteção.
Visibilidade | ||||
Da mesma classe | ||||
De qualquer classe no mesmo pacote | ||||
De qualquer classe do lado de fora do pacote | ||||
De uma subclasse no mesmo pacote | ||||
De uma subclasse do lado de fora do mesmo pacote |
Fundar proteções em novas classes com novos métodos é fácil; toma as suas decisões baseadas no seu desenho e aplica os modificadores direitos. Quando cria subclasses e ignora outros métodos, contudo, tem de considerar a proteção do método original.
A regra geral em Java consiste em que não pode ignorar um método e fazer o novo método mais privado do que o método original (pode, contudo, fazê-lo mais público). Mais especificamente, as seguintes regras de métodos herdados têm o cumprimento exigido por Java:
Uma boa regra do polegar na programação orientada ao objeto consiste em que a menos que uma variável de exemplo seja constante deve ser quase certamente private. Mas, ouço-o dizer, se as variáveis de exemplo forem privadas, como podem modificar-se do exterior da classe? Não podem. Isto é precisamente o ponto. Em vez disso, se cria métodos especiais que indiretamente leem ou modificam o valor daquela variável de exemplo, pode controlar muito melhor a interface das suas classes e como aquelas classes se comportam. Aprenderá sobre como fazer isto depois nesta seção.
Na maioria dos casos, ter alguém mais acessar ou modificar variáveis de exemplo dentro do seu objeto não são uma boa ideia. Tome, por exemplo, uma classe chamada círculo, cuja definição parcial parece a isto:
class Circle { int x, y, radius; Circle(int x, int y, int radius) { ... } void draw() { ... } }
A classe de Circle tem três variáveis de exemplo: para o x e posição y do ponto de centro, e do raio. Um construtor constrói o círculo daqueles três valores, e o método de draw() desenha o círculo na tela. Até aqui tudo bem, certo?
Então digamos manda criar um objeto de Circle e atraído a tela. Então algum outro objeto vai e modifica o valor de radius. Agora que? O seu círculo não sabe que o raio se modificou. Não sabe para desenhar-se novamente para aproveitar-se do novo tamanho do círculo. Modificar o valor de uma variável de exemplo não provoca em si mesmo nenhum método. Tem de confiar no mesmo objeto casual que modificou o raio para chamar também o método de draw(). E isto demais complica a interface da sua classe, fazendo-o mais propenso a erros.
Outro exemplo de porque é melhor não fazer variáveis de exemplo publicamente acessíveis é que não é possível impedir uma variável de exemplo não-constante de modificar-se. Em outras palavras, pode criar uma variável que tinha pretendido ser somente de leitura, e possivelmente o seu programa foi bem amaneirado e não foi sobre a modificação daquela variável à toa - mas porque a variável está lá e disponível alguém mais pode muito modificá-lo bem sem entender a sua metodologia.
Se todas as suas variáveis de exemplo forem private, como lhes dá o acesso ao mundo exterior? A resposta deve escrever que a métodos especiais leiam e modifiquem aquela variável (um para ler o valor da variável, um para modificá-lo) em vez de permitir-lhe ler-se e se modifique diretamente. Estes métodos chamam-se às vezes métodos accessor, mutator métodos (para modificar a variável) ou simplesmente obtentores e cães de caça.
Os métodos de Accessor são métodos especiais que implementa para modificar indiretamente variáveis de exemplo privadas de outra maneira.
Ter um método para modificar uma variável de exemplo dada significa que pode controlar ambos o valor em que a variável se estabelece (para assegurar-se que é dentro dos limites que espera), bem como execute qualquer outra operação que precisaria de fazer-se se aquela variável se modificar, por exemplo, para desenhar novamente o círculo.
Ter dois métodos para ler e modificar a variável também lhe permite fundar proteções diferentes para cada um. O método para ler o valor, por exemplo, pode ser público, ao passo que o método para modificar o valor pode ser privado ou protegido, efetivamente criando uma variável exceto a qual isto é somente de leitura em alguns casos (que é diferente da constante, que é somente de leitura em todos os casos).
Usar métodos para acessar uma variável de exemplo é um dos idiomas o mais frequentemente usados em programas orientados ao objeto. Aplicá-lo liberalmente em todas as partes de todas as suas classes reembolsa-o tempos numerosos com programas mais robustos e reutilizáveis.
Criar accessor métodos das suas variáveis de exemplo simplesmente implica a criação de dois extra métodos de cada variável. Não há nada especial sobre métodos accessor; são como qualquer outro método. Deste modo, por exemplo, aqui está uma classe de Circle modificada que tem três variáveis de exemplo privadas: x, y e radius. O método de getRadius() público usa-se para recuperar o valor da variável de raio, e o método de setRadius() usa-se para estabelecê-lo (e atualizar outras partes da classe que tem de atualizar-se ao mesmo tempo):
class Circle { private int x, y radius; public int getRadius() { return radius; } public int setRadius(int value) { radius = value; draw(); doOtherStuff(); return radius; } .... }
Neste exemplo modificado da classe de Circle os métodos accessor da variável de exemplo radius têm as palavras set e get acrescentado com o nome da variável. Isto é uma convenção de nomeação popular entre muitos programadores de métodos accessor, portanto sempre sabe que os métodos fazem que e a que variável. Para acessar ou modificar o valor da variável de exemplo, por isso, somente chamaria os métodos setRadius() e getRadius(), respectivamente:
theCircle.getRadius(); //get the value theCircle.setRadius(4); //set the value (and redraw, etc)
Outra convenção para denominar métodos accessor é usar o mesmo nome para os métodos quanto à própria variável. Em Java é legal por exemplo variáveis e métodos ter o mesmo nome; Java sabe de como se usam para executar a operação direita. Enquanto isto realmente faz métodos accessor mais curto para datilografar (nenhum extra "jogo" ou "vir" para datilografar no início de cada variável), há dois problemas com a utilização desta convenção:
Que convenção que usa é uma pergunta do gosto pessoal. A coisa mais importante é escolher uma convenção e picar com ela em todas as partes de todas as suas classes para que as suas interfaces sejam consistentes e compreensíveis.
A ideia atrás da declaração de variáveis de exemplo privadas e criação accessor métodos consiste em para que os usuários externos da sua classe se forcem a usar os métodos decide modificar dados da sua classe. Mas o benefício de métodos accessor não é somente para o uso por objetos externos a seu; também estão lá para você. Somente porque tem o acesso à variável de exemplo real dentro da sua própria classe não significa que pode evitar usar métodos accessor.
Considere que uma das boas razões de fazer variáveis de exemplo privadas é esconder detalhes de implementação do exterior do seu objeto. Proteger uma variável com métodos accessor significa que outros objetos não precisam de saber sobre nada outro do que os métodos accessor - pode modificar felizmente a implementação interna da sua classe sem causar estragos a todo o mundo que usou a sua classe. O mesmo é verdade do seu código dentro daquela classe; guardando variáveis separam-se de accessors, se dever modificar algo sobre uma variável de exemplo dada tudo que tem de modificar são os métodos accessor e não cada referência para a própria variável. Quanto a manutenção de código e reutilização, o que é bom para o ganso (os usuários externos da sua classe) é geralmente também bom para o tolo (você, como um usuário da sua própria classe).
Aprendeu sobre variáveis de classe e métodos no início da semana passada, portanto não repetirei uma descrição longa deles aqui. Como usam modificadores, contudo, merecem uma menção superficial.
Para criar uma variável de classe ou método, simplesmente inclua a palavra static em frente do nome de método. O modificador de static tipicamente vem depois de qualquer modificador de proteção, como isto:
public class Circle { public static float pi = 3.14159265F; public float area(float r) { return pi * r * r; } }
Observar |
A palavra static vem de C e C ++. Enquanto static tem uma significação específica para onde um método ou a variável se guardam na memória em tempo de execução de um programa naquelas línguas, static simplesmente significa que se guarda na classe em Java. Sempre que veja a palavra static, se lembre de substituir mentalmente a classe de palavra. |
Tanto as variáveis de classe como os métodos podem acessar-se usando a notação de ponto padrão com o nome de classe ou com um objeto no lado abandonado do ponto. Contudo, a convenção sempre é usar o nome da classe, para clarificar que uma variável de classe se está usando, e ajudar o leitor a saber imediatamente que a variável é global a todos os exemplos. Aqui estão alguns exemplos:
float circumference = 2 * Circle.pi * getRadius(); float randomNumer = Math.random();
Ponta |
As variáveis de classe, pelas mesmas razões que variáveis de exemplo, também podem beneficiar-se de declarado private e tendo accessor métodos adquirem ou estabelecem os seus valores. |
A listagem 15.1 mostra uma classe chamada CountInstances que a classe de usos e as variáveis de exemplo para guardar a pista de quantos exemplos daquela classe se criaram.
A listagem 15.1. A classe de CountInstances, que usa variáveis de exemplo e classe.
1: public class CountInstances { 2: private static int numInstances = 0; 3: 4: protected static int getNumInstances() { 5: return numInstances; 6: } 7: 8: private static void addInstance() { 9: numInstances++; 10: } 11: 12: CountInstances() { 13: CountInstances.addInstance(); 14: } 15: 16: public static void main(String args[]) { 17: System.out.println("Starting with " + 18: CountInstances.getNumInstances() + " instances"); 19: for (int i = 0; i < 10; ++i) 20: new CountInstances(); 21: System.out.println("Created " + 22: CountInstances.getNumInstances() + " instances"); 23: } 24:}
Started with 0 instances Creates 10 instances
Este exemplo tem um número de características, então vamos por ele linha pela linha. Na linha 2 declaramos que uma variável de classe de private se mantenha o número de exemplos (chamou numInstances). Isto é uma variável de classe (declarou static) porque o número de exemplos é relevante para a classe no conjunto, não para qualquer exemplo. E é private para que siga as mesmas regras que variáveis de exemplo accessor métodos.
Observe a inicialização de numInstances a 0 naquela mesma linha. Tão como uma variável de exemplo inicializa-se quando o seu exemplo se cria, uma variável de classe inicializa-se quando a sua classe se cria. Esta inicialização de classe acontece essencialmente antes que algo mais possa acontecer àquela classe ou os seus exemplos, portanto a classe no exemplo trabalhará como planejado.
Em linhas 4 para 6, criamos um método de get daquela variável de exemplo privada para adquirir o seu valor (getNumInstances()). Este método também se declara como um método de classe, como se aplica diretamente à variável de classe. O método de getNumInstances() declara-se por protected, ao contrário de public, porque só esta classe e possivelmente as subclasses se interessarão naquele valor; outras classes casuais, por isso, restringem-se de vê-lo.
Observe que não há método accessor para estabelecer o valor. A razão consiste em que o valor da variável deve incrementar-se só quando um novo exemplo se cria; não deve estabelecer-se em nenhum valor casual. Em vez de criar um método accessor, por isso, criaremos um método privado especial chamado addInstance() em linhas 8 para 10 que incrementa o valor de numInstances por 1.
As linhas 12 para 14 têm o método de construtor desta classe. Lembre-se, chamam construtores quando um novo objeto se cria, que faz isto o lugar mais lógico de chamar addInstance() e incrementar a variável.
E finalmente, o método de main() indica que podemos dirigir isto como uma aplicação de Java e testar todos os outros métodos. No método de main() criamos 10 exemplos da classe de CountInstances, informando depois que nos fazemos o valor da variável de classe de numInstances (que, previsivelmente, imprime 10).
Embora não seja o modificador final que discutirei hoje, o modificador de final usa-se para finalizar classes, métodos e variáveis. Finalizar uma coisa efetivamente "congela" a implementação ou o valor daquela coisa. Mais especificamente, aqui está como final trabalha com classes, variáveis e métodos:
A finalização (usando o modificador de final) congela a implementação de uma classe, método ou variável.
Para finalizar uma classe, acrescente o modificador de final à sua definição. final tipicamente vai depois de qualquer modificador de proteção como private ou public:
public final class AFinalClass { . . . }
Declara uma classe final por só duas razões:
A biblioteca de classe de Java usa classes de final extensivamente. As classes que se finalizaram para prevenir o que subclassificam-se incluem java.lang.System, java.net.InetAddress e java.net.Socket (embora, como aprendesse no Dia 14, "O Windows, a Ligação em rede e Outros Petiscos", o último não será já final desde Java 1.1). Um bom exemplo de uma classe sendo declarada por final por razões de eficiência é java.lang.String. As cadeias são tanto comum em Java, e tão central para ele que Java os trata especialmente.
Na maioria dos casos, será um evento raro para você para criar uma classe de final você mesmo desde que as classes extensíveis são muito mais úteis do que classes finalizadas, e os lucros de eficiência são mínimos. Contudo, terá mais provavelmente abundância da oportunidade a tombar-se em certas classes de sistema que são final (fazendo-o mais difícil de estendê-los).
Uma variável finalizada significa que o seu valor não pode modificar-se. Isto é efetivamente uma constante, que aprendeu sobre início de Semana 1. Para declarar constantes em Java, use variáveis de final com valores iniciais:
public class AnotherFinalClass { public static final int aConstantInt = 123; public final String aConstantString = "Hello world!"; }
As variáveis locais (aqueles blocos interiores do código rodeado de tiras, por exemplo, em while ou laços de for) não podem declarar-se por final.
Os métodos finalizados são métodos que não podem ignorar-se; isto é, as suas implementações congelam-se e não podem redefinir-se em subclasses.
public class ClassWithFinalMethod { public final void noOneGetsToDoThisButMe() { . . . } }
A única razão de declarar um método final é eficiência. Normalmente, as assinaturas de método e as implementações experimentam-se quando o seu programa Java corre, não quando se compila. Lembre-se de que quando chama um método, Java dinamicamente verifica a classe atual e cada superclasse à sua vez da definição daquele método. Embora isto faça métodos muito flexíveis para definir e usar, não é muito rápido.
Se declarar um método final, contudo, o compilador então pode "ligado em série" ele (pique a sua definição) bem no meio de métodos que o chamam porque "sabe" que ninguém mais pode subclassificar alguma vez e ignorar o método para modificar a sua significação. Embora não pudesse usar final imediatamente escrevendo uma classe, como sintoniza o sistema depois, pode descobrir que alguns métodos têm de ser final para fazer a sua classe bastante rápido. Quase todos os seus métodos serão perfeitos, contudo, como são.
Se usar métodos accessor muito (como recomendado), modificar os seus métodos accessor para ser final pode ser um modo rápido de acelerar a sua classe. Como as subclasses quererão raramente modificar as definições daqueles métodos accessor, há pouca razão aqueles métodos não devem ser finais.
A biblioteca de classe de Java declara muitos métodos comumente usados final para que se beneficie da aceleração. Em caso de classes que já são final, isto faz o sentido perfeito e é uma escolha sábia. Os poucos métodos de final declarados em classes non-final o aborrecerão - as suas subclasses não podem ignorá-los já. Quando a eficiência se torna menos de uma questão do ambiente de Java, muitos destes métodos de final podem "descongelar-se" novamente, restituindo esta flexibilidade perdida ao sistema.
Observar |
Os métodos de Private são efetivamente final, como são todos os métodos declarados em uma classe de final. Marcando estes últimos métodos final (como a biblioteca de Java às vezes faz) é legal, mas redundante; o compilador já os trata como final. É possível usar métodos de final por algumas as mesmas razões de segurança usa classes de final, mas é um evento muito mais raro. |
Sempre que arranje classes em uma hierarquia de herança, a presunção consiste em que as classes "mais altas" são mais abstratas e gerais, ao passo que as subclasses "mais baixas" são mais concretas e específicas. Muitas vezes, como projeta hierarquias de classes, você fator desenho fora comum e implementação em uma superclasse compartilhada. Aquela superclasse não terá exemplos; a sua única razão do existente é substituir como um repositório comum, compartilhado sob a informação que as suas subclasses usam. Estas espécies de classes chamam-se classes abstratas, e declara-os usando o modificador de abstract. Por exemplo, a seguinte definição de classe de esqueleto da classe de Fruit declarou que classe ser tanto public como abstract:
public abstract class Fruit { ... }
As classes abstratas nunca podem ser instantiated (adquirirá um erro de compilador se tentar), mas podem conter algo que uma classe normal pode conter, inclusive classe e variáveis de exemplo e métodos com qualquer espécie de modificadores de finalização ou proteção. Além disso, as classes abstratas também podem conter métodos abstratos. Um método abstrato é uma assinatura de método sem implementação; espera-se que as subclasses da classe abstrata forneçam a implementação para aquele método. Os métodos abstratos, deste modo, fornecem o mesmo conceito fundamental que classes abstratas; são um caminho da fatorização comportamento comum em superclasses e logo fornecimento dos usos concretos específicos daqueles comportamentos em subclasses.
As classes abstratas são classes cujo único objetivo é fornecer a informação comum para subclasses. As classes abstratas não podem ter exemplos.
Os métodos abstratos são métodos com assinaturas, mas nenhuma implementação. As subclasses da classe que contém aquele método abstrato devem fornecer a sua implementação real.
Como classes abstratas, os métodos abstratos dão-lhe a capacidade do fator informação comum em uma superclasse geral e logo reutilização aquela classe de maneiras diferentes.
O contrário do resumo é concreto: as classes concretas são classes que podem ser instantiated; os métodos concretos são aqueles que têm implementações reais.
Os métodos abstratos declaram-se com o modificador de abstract, que normalmente vai depois dos modificadores de proteção mas antes static ou antes final. Além disso, não têm corpo. Os métodos abstratos só podem existir dentro de classes abstratas; mesmo se tiver uma classe cheia de métodos concretos, com só um método abstrato, a classe inteira deve ser abstrata. Isto é porque os métodos abstratos não podem chamar-se; não têm implementação, a assim chamada deles produziria um erro. Em vez de importunar com métodos de resumo de caso especial no interior de outra maneira exemplos concretos, é mais fácil somente insistir que métodos abstratos só conter-se dentro de classes abstratas.
A listagem 15.2 mostra duas classes simples. Um, MyFirstAbstractClass apropriadamente chamado, tem uma variável de exemplo e dois métodos. Um daqueles métodos, subclassesImplementMe(), é abstrato. O outro, doSomething(), é concreto e tem uma definição normal.
A segunda classe é AConcreteSubclass, que é uma subclasse de MyFirstAbstractClass. Fornece a implementação de subclassesImplementMe() e herda o comportamento restante de MyFirstAbstractClass.
Observar |
Como ambas estas classes são públicas, devem definir-se em arquivos originais separados. |
A listagem 15.2. Duas classes: um resumo, um concreto.
1:ipublic abstract class MyFirstAbstractClass { 2: int anInstanceVariable; 3:p 4: public abstract int subclassesImplementMe(); // note no definition 5: 6: public void doSomething() { 7: . . . // a normal method 8: } 9:} 10: 11:public class AConcreteSubClass extends MyFirstAbstractClass { 12: public int subclassesImplementMe() { 13: . . . // we *must* implement this method here 14: } 15:}
Aqui estão alguns usos tentados destas classes:
Object a = new MyFirstAbstractClass(); // illegal, is abstract Object c = new AConcreteSubClass(); // OK, a concrete subclass
Usando uma classe abstrata com métodos apenas abstratos - isto é, aquele que fornece apenas um padrão para o comportamento - realiza-se melhor em Java usando uma interface (discutido amanhã). Sempre que um desenho peça uma abstração que inclui o estado de exemplo e/ou uma implementação parcial, contudo, uma classe abstrata é a sua única escolha.
Hoje aprendeu como as variáveis e os métodos podem controlar a sua visibilidade e acesso por outras classes via quatro Ps da proteção: public, package, protected e private. Também aprendeu que embora as variáveis de exemplo se declarem muitas vezes private, declarando accessor métodos lhe permite controlar a leitura e escrita deles separadamente. Os níveis de proteção permitem-lhe, por exemplo, separar limpamente as suas abstrações públicas das suas representações concretas.
Também aprendeu como criar variáveis de classe e métodos, que se associam com a própria classe, e como declarar que variáveis de final, métodos e classes representem constantes e rápido ou métodos seguros e classes.
Finalmente, descobriu como declarar e usar classes de abstract, que não podem ser instantiated e métodos de abstract, que não têm implementação e devem ignorar-se em subclasses. Em conjunto, fornecem um padrão para subclasses para preencher e atuar como uma variante das interfaces potentes de Java que estudará amanhã.
Porque estão lá tantos níveis diferentes da proteção em Java? | |
Cada nível da proteção ou visibilidade, fornece uma visão diferente da sua classe ao mundo exterior. Uma visão talha-se para todo o mundo, um para classes no seu próprio pacote, o outro para a sua classe e as suas subclasses só, uma combinação destes dois últimos e o final para somente dentro da sua classe. Cada um é uma separação logicamente bem definida e útil que Java apoia diretamente na língua (ao contrário de, por exemplo, accessor métodos, que são uma convenção que deve seguir). | |
Não vai usar accessor os métodos em todo lugar diminuem o meu código de Java? | |
Não sempre. Quando os compiladores de Java melhoram-se e podem criar mais otimizações, serão capazes de fazê-los rápido automaticamente, mas se se preocupar com a velocidade, sempre pode declarar que métodos accessor sejam final, e serão tão rápidos como acessos a variável de exemplo diretos. | |
Classe é (static) métodos herdados como métodos de exemplo? | |
Não. static (classe) métodos é agora final à revelia. Como, então, pode declarar alguma vez um método de classe non-final? A resposta é que não pode! A herança de métodos de classe não se permite, quebrando a simetria com métodos de exemplo. | |
Baseado no que aprendi, parece métodos de private abstract e métodos de final abstract ou as classes não fazem sentido. São legais? | |
Não, são compilam erros vezes, como adivinhou. Para ser úteis, os métodos de abstract devem ignorar-se, e as classes de abstract devem subclassificar-se, mas nenhuma daquelas duas operações seria legal se também fossem private ou final. | |
Que tal o modificador de transient? Vi que mencionou na Especificação de Língua de Java. | |
O modificador de transient reserva-se pelos desenhistas de Java do uso em futuras versões da língua de Java (além 1.0.2 e 1.1); vai se usar para criar sistemas de loja de objeto persistentes (a capacidade de salvar o grupo de classes e objetos e restaurar o seu estado mais tarde). Como outros modificadores como byvalue, future, e generic, não se usa atualmente mas é palavras reservadas na língua. | |
Tentei criar uma variável privada dentro de uma definição de método. Não trabalhou. O que fiz mal? | |
Nada. Todos os modificadores neste capítulo, quando pode usá-los com variáveis, só aplicam a classe e variáveis de exemplo. Variáveis locais - aqueles que aparecem dentro do corpo de um método ou laço - não podem usar nenhum destes modificadores. |