O main()
método primeiro verifica se um único argumento de linha de comando foi especificado. Se a verificação for bem-sucedida, ele passa esse argumento para Audio.newAudio()
e atribui o retornado Audio
referência do objeto a uma variável local chamada audio
. main()
então prossegue verificando que audio
não é nulo e (neste caso) interrogar o Audio
objeto, emitindo os valores de amostra do clipe de áudio junto com sua taxa de amostragem.
Copie a Listagem 3 para um arquivo chamado UseAudio.java
e coloque este arquivo no mesmo diretório que o ca
diretório que você criou anteriormente. Em seguida, execute o seguinte comando para compilar UseAudio.java
:
javac UseAudio.java
Se tudo correr bem, você deve observar UseAudio.class
no diretório atual.
Execute o seguinte comando para executar UseAudio
contra um arquivo WAV fictício chamado audio.wav
:
java UseAudio audio.wav
Você deve observar a seguinte saída:
Samples
Sample Rate: 0
Suponha que UseAudio.java
não estava localizado no mesmo diretório que ca
. Como você compilaria esse arquivo de origem e executaria o aplicativo resultante? A resposta é usar o classpath.
O classpath Java
O Caminho de classe Java é uma sequência de pacotes que a máquina virtual Java (JVM) procura por tipos de referência. É especificado por meio do -classpath
(ou -cp
) opção usada para iniciar a JVM ou, quando não presente, a CLASSPATH
variável de ambiente.
Suponha (em uma plataforma Windows) que a biblioteca de áudio esteja armazenada em C:audio
e isso UseAudio.java
é armazenado em C:UseAudio
que é atual. Especifique os seguintes comandos para compilar o código-fonte e executar o aplicativo:
javac -cp ../audio UseAudio.java
java -cp ../audio;. UseAudio audio.wav
O caráter do período no java
– a linha de comando prefixada representa o diretório atual. Ele deve ser especificado para que a JVM possa localizar UseAudio.class
.
Tópicos de pacotes adicionais
A linguagem Java inclui um protected
palavra-chave, que é útil em um contexto de pacote. Além disso, os pacotes podem ser distribuídos em arquivos JAR. Além disso, a JVM segue uma ordem de pesquisa específica ao pesquisar pacotes para tipos de referência (independentemente de esses pacotes estarem ou não armazenados em arquivos JAR). Exploraremos esses tópicos a seguir.
Acesso protegido
O protected
a palavra-chave atribui o nível de acesso protegido a um membro da classe, como um campo ou método (por exemplo, protected void clear()
). Declarando um membro de classe protected
torna o membro acessível a todo o código em qualquer classe localizada no mesmo pacote e às subclasses, independentemente de seus pacotes.
Joshua Bloch explica a justificativa para dar aos membros da classe acesso protegido em seu livro, Effective Java Segunda Edição (“Item 17: Projete e documente para herança ou então proíba-a”). Eles são ganchos para o funcionamento interno de uma classe para permitir que os programadores “escrevam subclasses eficientes sem dor indevida”. Confira o livro para mais informações.
Arquivos JAR
Distribuir um pacote especificando instruções para criar a estrutura de diretório necessária junto com os arquivos de classe do pacote (e instruções sobre quais arquivos de classe armazenar em quais diretórios) seria uma tarefa tediosa e propensa a erros. Felizmente, os arquivos JAR oferecem uma alternativa muito melhor.
UM Arquivo JAR (arquivo Java) é um arquivo ZIP com um .jar
extensão (em vez de .zip
extensão). Inclui um especial META-INF
diretório contendo manifest.mf
(um arquivo especial que armazena informações sobre o conteúdo do arquivo JAR) e uma estrutura de diretório hierárquica que organiza os arquivos de classe.
Você usa o JDK jar
ferramenta para criar e manter um arquivo JAR. Você também pode visualizar o índice do arquivo JAR. Para mostrar como é fácil usar esta ferramenta, criaremos um audio.jar
arquivo que armazena o conteúdo do ca.javajeff.audio
pacote. Então acessaremos esse arquivo JAR ao executar UseAudio.class
. Criar audio.jar
do seguinte modo:
Primeiro, certifique-se de que o diretório atual contém o criado anteriormente ca / javajeff / audio
hierarquia de diretórios e que audio
contém audio.class
e WavReader.class
.
Segundo, execute o seguinte comando:
jar cf audio.jar cajavajeffaudio*.class
O c
opção significa “criar novo arquivo” e a f
opção significa “especificar nome do arquivo”.
Agora você deve encontrar um audio.jar
arquivo no diretório atual. Prove a si mesmo que este arquivo contém os dois arquivos de classe executando o seguinte comando, onde o t
opção significa “listar índice”:
jar tf audio.jar
Você pode correr UseAudio.class
adicionando audio.jar
para seu classpath. Por exemplo, supondo que audio.jar
está localizado no mesmo diretório que UseAudio.class
você pode correr UseAudio
no Windows através do seguinte comando:
java -classpath audio.jar;. UseAudio
Para sua conveniência, você pode especificar o mais curto -cp
em vez do mais longo -classpath
.
Procurando pacotes para tipos de referência
Os novatos em pacotes Java frequentemente ficam frustrados com “nenhuma definição de classe encontrada” e outros erros. Essa frustração pode ser parcialmente evitada ao entender como a JVM procura por tipos de referência. Para entender esse processo, você deve perceber que o compilador é um aplicativo Java especial que roda sob o controle da JVM. Além disso, há duas formas de busca: busca em tempo de compilação e busca em tempo de execução.
Pesquisa em tempo de compilação
Quando o compilador encontra uma expressão de tipo (como uma chamada de método) no código-fonte, ele deve localizar a declaração desse tipo para verificar se a expressão é legal. Como exemplo, ele pode verificar se um método existe na classe do tipo, cujos tipos de parâmetro correspondem aos tipos dos argumentos passados na chamada de método.
O compilador primeiro pesquisa os pacotes da plataforma Java (em rt.jar
e outros arquivos JAR), que contêm os tipos de biblioteca de classes padrão do Java (como java.lang
‘s System
classe). Ele então pesquisa pacotes de extensão para tipos de extensão. Se o -sourcepath
opção é especificada ao iniciar javac
o compilador pesquisa os arquivos de origem do caminho indicado.
Caso contrário, o compilador pesquisa o classpath (na ordem da esquerda para a direita) para o primeiro arquivo de classe ou arquivo de origem que contém o tipo. Se nenhum classpath estiver presente, o diretório atual será pesquisado. Se nenhum pacote corresponder ou o tipo ainda não puder ser encontrado, o compilador relata um erro. Caso contrário, ele registra as informações do pacote no arquivo de classe.
Pesquisa em tempo de execução
Quando o compilador ou qualquer outro aplicativo Java é executado, a JVM encontrará tipos e deverá carregar seus arquivos de classe associados por meio de um código especial conhecido como carregador de classe. A JVM usará as informações do pacote armazenadas anteriormente associadas ao tipo encontrado em uma busca pelo arquivo de classe desse tipo.
A JVM pesquisa os pacotes da plataforma Java, seguidos pelos pacotes de extensão, seguidos pelo classpath ou diretório atual (quando não há classpath) para o primeiro arquivo de classe que contém o tipo. Se nenhum pacote corresponder ou o tipo não puder ser encontrado, um erro “nenhuma definição de classe encontrada” será relatado. Caso contrário, o arquivo de classe será carregado na memória.
Importando membros estáticos estaticamente
Em Effective Java Segunda Edição, Item 19Joshua Bloch menciona que os desenvolvedores Java devem usar apenas interfaces para declarar tipos. Não devemos usar interfaces para declarar interfaces constantesque são interfaces que existem apenas para exportar constantes. Listagem 4’s Switchable
A interface constante fornece um exemplo.
Listagem 4. Uma interface constante (Switchable.java)
public interface Switchable
{
boolean OFF = false;
boolean ON = true;
}
Os desenvolvedores recorrem a interfaces constantes para evitar ter que prefixar o nome da constante com o nome do seu tipo de referência (por exemplo, Math.PI
). Por exemplo, considere a Listagem 5 Light
classe, que implementa o Switchable
interface para que o desenvolvedor tenha liberdade para especificar constantes OFF
e ON
sem ter que incluir prefixos de classe (se eles foram declarados em uma classe).
Listagem 5. Light implementa Switchable (Light.java, versão 1)
public class Light implements Switchable
{
private boolean state = OFF;
public void printState()
{
System.out.printf("state = %s%n", (state == OFF) ? "OFF" : "ON");
}
public void toggle()
{
state = (state == OFF) ? ON : OFF;
}
}
Uma interface constante fornece constantes que devem ser usadas na implementação de uma classe. Como um detalhe de implementação, você não deve vazar constantes na API exportada da classe porque elas podem confundir outros que usam sua classe. Além disso, para preservar a compatibilidade binária, você está comprometido em dar suporte a elas, mesmo quando a classe não as estiver mais usando.
Importações estáticas
Para satisfazer a necessidade de interfaces constantes e, ao mesmo tempo, evitar os problemas impostos pela sua utilização, o Java 5 introduziu importações estáticas. Este recurso de linguagem pode ser usado para importar membros estáticos de um tipo de referência. Ele é implementado por meio do import static
declaração cuja sintaxe aparece abaixo:
import static packagespec . typename . ( staticmembername | * );
Colocação static
depois import
distingue esta declaração de uma declaração de importação regular. A sintaxe é semelhante à da declaração de importação regular. import
declaração em termos da lista padrão separada por períodos de nomes de pacotes e subpacotes. Você pode importar um único nome de membro estático ou todos os nomes de membros estáticos (graças ao asterisco). Considere os seguintes exemplos:
import static java.lang.Math.*; // Import all static members from Math.
import static java.lang.Math.PI; // Import the PI static constant only.
import static java.lang.Math.cos; // Import the cos() static method only.
Depois de importá-los, você pode especificar membros estáticos sem precisar prefixá-los com seus nomes de tipo. Por exemplo, depois de especificar a primeira ou terceira importação estática, você pode especificar cos
diretamente, como em (>
double
cosine = cos(angle);
Para corrigir a Listagem 5 para que ela não dependa mais de implements Switchable
podemos inserir uma importação estática, conforme demonstrado na Listagem 6.
Listagem 6. Uma importação estática melhora a implementação do Switchable (Light.java, versão 2)
package foo;
import static foo.Switchable.*;
public class Light
{
private boolean state = OFF;
public void printState()
{
System.out.printf("state = %s%n", (state == OFF) ? "OFF" : "ON");
}
public void toggle()
{
state = (state == OFF) ? ON : OFF;
}
}
A listagem 6 começa com um package foo;
declaração porque você não pode importar membros estáticos de um tipo localizado no pacote sem nome. Este nome de pacote aparece como parte da importação estática subsequente:
import static
foo.Switchable.*;
O que observar ao usar importações estáticas
Há dois cuidados adicionais relacionados a importações estáticas.
Primeiro, quando duas importações estáticas importam o mesmo membro nomeado, o compilador relata um erro. Por exemplo, suponha que o pacote physics
contém um Math
classe que é idêntica a java.lang
‘s Math
classe na medida em que implementa o mesmo PI
constante e métodos trigonométricos. Quando confrontado com o seguinte fragmento de código, o compilador relata erros porque não consegue determinar se java.lang.Math
‘s ou physics.Math
‘s PI
constante está sendo acessado e cos()
método está sendo chamado:
import static java.lang.Math.cos;
import static physics.Math.cos;
double angle = PI;
System.out.println(cos(angle));
Segundo, o uso excessivo de importações estáticas polui o namespace do código com todos os membros estáticos que você importa, o que pode tornar seu código ilegível e impossível de manter. Além disso, qualquer um que leia seu código pode ter dificuldade para descobrir de qual tipo um membro estático vem, especialmente ao importar todos os nomes de membros estáticos de um tipo.
Conclusão
Pacotes ajudam você a criar bibliotecas reutilizáveis de tipos de referência com seus métodos. Se você chamar um método (seja empacotado em uma biblioteca ou não) com um argumento ilegal (como um índice negativo para um array), você provavelmente encontrará uma exceção Java.