Acesso Público:
O acesso protegido era um conceito difícil. Felizmente, o último tipo de modificador de acesso é fácil: public significa que qualquer pessoa pode acessar o membro de qualquer lugar.
package pond.duck;
public class DuckTeacher {
public String name = "helpful"; // public access
public void swim() { // public access
System.out.println("swim");
} }
O DuckTeacher permite o acesso a qualquer classe que desejar. Agora podemos experimentar:
package pond.goose;
import pond.duck.DuckTeacher;
public class LostDuckling {
public void swim() {
DuckTeacher teacher = new DuckTeacher();
teacher.swim(); // allowed
System.out.println("Thanks" + teacher.name); // allowed
} }
LostDuckling pode se referir a swim() e nome no DuckTeacher porque eles são public. A história tem um final feliz. LostDuckling aprendeu a nadar e pode encontrar seus parentes - tudo porque o DuckTeacher os tornou públicos. Para revisar modificadores de acesso, verifique por que tudo na Tabela 4.2 é verdadeiro. Lembre-se de que um member é um método ou campo.
Exceto pelo método main () , estivemos analisando métodos de instância. Métodos estáticos não requerem uma instância da classe. Eles são compartilhados entre todos os usuários da classe. Você pode pensar na estática como um membro do objeto de classe única que existe independentemente de quaisquer instâncias dessa classe.
Cada classe possui sua própria cópia do código?
Cada classe possui uma cópia das variáveis da instância. Existe apenas uma cópia do código para o métodos de instância. Cada instância da classe pode chamá-lo quantas vezes quiser. No entanto, cada chamada de um método de instância (ou qualquer método) obtém espaço na pilha para parâmetros de método e variáveis locais.
O mesmo acontece com métodos estáticos. Há uma cópia do código. Parâmetros e variáveis locais vão para a pilha.
Lembre-se de que apenas os dados obtêm sua “própria cópia”. Não há necessidade de duplicar cópias do próprio código.
Vimos um método estático desde o Capítulo 1. O método main () é um método estático. Isso significa que você pode chamá-lo pelo nome da classe.
public class Koala {
public static int count = 0; // static variable
public static void main(String[] args) { // static method
System.out.println(count);
}
}
Dissemos que a JVM basicamente chama Koala.main() para iniciar o programa. Você pode fazer isso também. Podemos ter um KoalaTester que não faz nada além de chamar o método main() .
public class KoalaTester {
public static void main(String[] args) {
Koala.main(new String[0]); // call static method
}
}
Uma maneira bastante complicada de imprimir 0, não é? Quando executamos o KoalaTester , ele faz uma chamada para o método main () do Koala , que imprime o valor da contagem . O objetivo de todos esses exemplos é mostrar que main () pode ser chamado como qualquer outro método estático.
Além dos métodos main () , os métodos estáticos têm dois objetivos principais:
■ Para métodos utilitários ou auxiliares que não exigem nenhum estado de objeto. Como não há necessidade acessar variáveis de instância, ter métodos estáticos elimina a necessidade do chamador instanciar o objeto apenas para chamar o método
■ Para estado compartilhado por todas as instâncias de uma classe, como um contador. Todas as instâncias devem compartilhar o mesmo estado. Os métodos que apenas usam esse estado devem ser estáticos também.
Vejamos alguns exemplos que abrangem outros conceitos estáticos.
Geralmente, é fácil acessar um membro estático. Você acabou de colocar o nome da classe antes do método ou variável e pronto. Por exemplo:
System.out.println(Koala.count);
Koala.main(new String[0]);
Ambos são agradáveis e fáceis. Há uma regra que é mais complicada. Você pode usar uma instância do objeto para chamar um método estático. O compilador verifica o tipo da referência e usa isso em vez do objeto - que é sorrateiro de Java. Este código é perfeitamente legal:
5: Koala k = new Koala();
6: System.out.println(k.count); // k is a Koala
7: k = null;
8: System.out.println(k.count); // k is still a Koala
Acredite ou não, esse código gera 0 duas vezes. A linha 6 vê que k é um Koala e a contagem é um variável estática, então ela lê essa variável estática. A linha 8 faz a mesma coisa. Java não se importa esse k é nulo . Como estamos procurando uma estática, isso não importa.
Lembre-se de olhar para o tipo de referência para uma variável quando vir um método estático ou uma variável. Os criadores do exame tentarão induzi-lo a pensar: Uma NullPointerException é lançada porque a variável é nulo . Não se deixe enganar!
Mais uma vez, porque isso é realmente importante: o que a seguinte saída gera?
Koala.count = 4;
Koala koala1 = new Koala();
Koala koala2 = new Koala();
koala1.count = 6;
koala2.count = 5;
System.out.println(Koala.count);
Felizmente, você respondeu 5. Existe apenas uma variável de contagem , pois é estática. Está definido como 4, depois 6, e finalmente chega a 5. Todas as variáveis de Koala são apenas distrações.
Estático vs. Instância
Há outra maneira dos criadores do exame tentarem enganá-lo em relação à estática e à instância membros. (Lembre-se de que "membro" significa campo ou método.) Um membro estático não pode chamar um membro da instância. Isso não deve ser uma surpresa, já que a estática não exige nenhuma instância da classe para estar por perto.
A seguir, é um erro comum que os programadores novatos cometem:
public class Static {
private String name = "Static class";
public static void first() { }
public static void second() { }
public void third() { System.out.println(name); }
public static void main(String args[]) {
first();
second();
third(); // DOES NOT COMPILE
} }
O compilador fornecerá um erro ao fazer uma referência estática para um elemento método não estático . Se corrigirmos isso adicionando static ao third () , criaremos um novo problema. Você pode descobrir o que é?
Tudo isso faz é mover o problema. Agora, o third() está se referindo ao name não estático . Adicionando estático para name também resolveria o problema. Outra solução teria sido chamar third como método de instância - por exemplo, novo Static (). third (); .
Os criadores do exame gostam deste tópico. Um método estático ou método de instância pode chamar um método estático método porque métodos estáticos não requerem um objeto para usar. Somente um método de instância pode chamar outro método de instância na mesma classe sem usar uma variável de referência, porque métodos de instância exigem um objeto. Lógica semelhante se aplica à instância e variáveis estáticas capazes. Certifique-se de entender a Tabela 4.3 antes de continuar.
Vamos tentar mais um exemplo para ter mais prática em reconhecer esse cenário. Faz você entende por que as seguintes linhas não são compiladas?
1: public class Gorilla {
2: public static int count;
3: public static void addGorilla() { count++; }
4: public void babyGorilla() { count++; }
5: public void announceBabies() {
6: addGorilla();
7: babyGorilla();
8: }
9: public static void announceBabiesToEveryone() {
10: addGorilla();
11: babyGorilla(); // DOES NOT COMPILE
12: }
13: public int total;
14: public static average = total / count; // DOES NOT COMPILE
15: }
As linhas 3 e 4 estão corretas porque os métodos estático e de instância podem se referir a uma variável estática. As linhas 5 a 8 são boas porque um método de instância pode chamar um método estático. Linha 11 não compila porque um método estático não pode chamar um método de instância. Da mesma forma, a linha 14 não compila porque uma variável estática está tentando usar uma variável de instância.
Um uso comum para variáveis estáticas é contar o número de instâncias:
public class Counter {
private static int count;
public Counter() { count++; }
public static void main(String[] args) {
Counter c1 = new Counter();
Counter c2 = new Counter();
Counter c3 = new Counter();
System.out.println(count); // 3
}
}
Cada vez que o construtor é chamado, ele incrementa a contagem em 1. Este exemplo depende do fato de que variáveis estáticas (e instância) são inicializadas automaticamente com o valor padrão para esse tipo, que é 0 para int . Consulte o Capítulo 1 para revisar os valores padrão.
Observe também que não escrevemos Counter.count . Nós poderíamos ter. Não é necessário porque já estamos nessa classe para que o compilador possa inferir isso.
Algumas variáveis estáticas devem mudar à medida que o programa é executado. Os contadores são comuns exemplos disso. Queremos que a contagem aumente com o tempo. Assim como nas variáveis de instância, você pode inicializar uma variável estática na linha em que é declarada:
public class Initializers {
private static int counter = 0; // initialization
}
Outras variáveis estáticas devem nunca mudar durante o programa. Esse tipo de variável capaz é conhecido como uma constante . Ele usa o modificador final para garantir que a variável nunca mude. constantes final estáticas usam uma convenção de nomenclatura diferente de outras variáveis. Eles usam todas as letras maiúsculas com sublinhado entre "palavras". Por exemplo:
public class Initializers {
private static final int NUM_BUCKETS = 45;
public static void main(String[] args) {
NUM_BUCKETS = 5; // DOES NOT COMPILE
} }
O compilador garantirá que você não tente acidentalmente atualizar uma variável final. Isso pode ficar interessante. Você acha que o seguinte compila?
private static final ArrayList<String> values = new ArrayList<>();
public static void main(String[] args) {
values.add("changed");
}
Na verdade, compila. values é uma variável de referência. Temos permissão para chamar métodos em variáveis de referência. Tudo o que o compilador pode fazer é verificar se não tentamos reatribuir o valores finais para apontar para um objeto diferente.
No Capítulo 1, abordamos os inicializadores de instância que pareciam métodos sem nome. Apenas codifique dentro de chaves. Inicializadores estáticos parecem semelhantes. Eles adicionam a palavra-chave estática para especificar deve ser executado quando a classe for usada pela primeira vez. Por exemplo:
private static final int NUM_SECONDS_PER_HOUR;
static {
int numSecondsPerMinute = 60;
int numMinutesPerHour = 60;
NUM_SECONDS_PER_HOUR = numSecondsPerMinute * numMinutesPerHour;
}
O inicializador estático é executado quando a classe é usada pela primeira vez. As instruções nele executadas e atribua quaisquer variáveis estáticas conforme necessário. Há algo interessante sobre este exemplo. Acabamos de dizer que as variáveis finais não podem ser reatribuído. A chave aqui é que o inicializador estático é a primeira atribuição. E, como ocorre na frente, está tudo bem. Vamos tentar outro exemplo para garantir que você entenda a distinção:
14: private static int one;
15: private static final int two;
16: private static final int three = 3;
17: private static final int four; // DOES NOT COMPILE
18: static {
19: one = 1;
20: two = 2;
21: three = 3; // DOES NOT COMPILE
22: two = 4; // DOES NOT COMPILE
23: }
A linha 14 declara uma variável estática que não é final. Pode ser atribuído quantas vezes preferir. A linha 15 declara uma variável final sem inicializá-la. Isso significa que podemos inicializá-lo exatamente uma vez em um bloco estático. A linha 22 não compila porque esta é a segunda tentativa. Linha 16 declara uma variável final e a inicializa ao mesmo tempo. Não temos permissão para atribuí-lo novamente, para que a linha 21 não seja compilada. A linha 17 declara uma variável final que nunca é inicializada. O compilador gera um erro do compilador porque sabe que os blocos estáticos são o único local a variável pode ser inicializada. Desde que o programador esqueceu, isso é claramente um erro.
Tente evitar inicializadores estáticos e de instância
O uso de inicializadores estáticos e de instância pode tornar seu código muito mais difícil de ler. Tudo o que poderia ser feito em um inicializador de instância poderia ser feito de uma maneira estruturada. A abordagem do construtor é mais fácil de ler.
Existe um caso comum para usar um inicializador estático: quando você precisa inicializar um campo estático e o código para isso requer mais de uma linha. Isso geralmente ocorre quando você deseja inicializar uma coleção como um ArrayList. Quando você precisar usar um inicializador estático, coloque toda a inicialização estática no mesmo bloco. Dessa maneira, a ordem é óbvia
De volta ao Capítulo 1, você viu que poderíamos importar uma classe específica ou todas as classes em um pacote:
import java.util.ArrayList;
import java.util.*;
Poderíamos usar esta técnica para importar:
import java.util.List;
import java.util.Arrays;
public class Imports {
public static void main(String[] args) {
List<String> list = Arrays.asList("one", "two");
}
}
As importações são convenientes porque você não precisa especificar de onde vem cada classe a cada vez que você o usa. Há outro tipo de importação chamado importação estática. Regular as importações são para importar classes. As importações estáticas destinam-se à importação de membros estáticos de classes. Assim como as importações regulares, você pode usar um curinga ou importar um membro específico. A idéia é que você não precise especificar de onde vem cada método ou variável estática cada vez que você o usa. Um exemplo de quando as interfaces estáticas brilham é quando você está se referindo para muitas constantes em outra classe.
Em um programa grande, as importações estáticas podem ser usadas em excesso. Ao importar em muitos lugares, pode ser difícil lembrar onde cada membro estático vem.
O método anterior possui uma chamada de método estático: Arrays.asList . Reescrevendo o código para usar uma importação estática gera o seguinte:
import java.util.List;
import static java.util.Arrays.asList; // static import
public class StaticImports {
public static void main(String[] args) {
List<String> list = asList("one", "two"); // no Arrays.
} }
Neste exemplo, estamos importando especificamente o método asList . Isso significa que quando nos referimos a asList na classe, ele chamará Arrays.asList () . Um caso interessante é o que aconteceria se criassemos um método asList em nossa Classe StaticImports. Java daria preferência ao importado e ao método nós codificamos seria usado.
O exame tentará enganá-lo com o uso indevido de importações estáticas. Este exemplo mostra quase tudo o que você pode fazer de errado. Você consegue descobrir o que há de errado com cada um?
1: import static java.util.Arrays; // DOES NOT COMPILE
2: import static java.util.Arrays.asList;
3: static import java.util.Arrays.*; // DOES NOT COMPILE
4: public class BadStaticImports {
5: public static void main(String[] args) {
6: Arrays.asList("one"); // DOES NOT COMPILE
7: } }
A linha 1 tenta usar uma importação estática para importar uma classe. Lembre-se de que importações estáticas são somente para importar membros estáticos. As importações regulares são para importar uma classe. Linha 3 tenta para ver se você está prestando atenção à ordem das palavras-chave. A sintaxe é import static e não vice-versa. A linha 6 é sorrateira. Importamos o método asList na linha 2. No entanto, não importamos a classe Arrays em nenhum lugar. Isso faz com que seja bom escrever como List ("one"); mas não Arrays.asList ("one"); . Há apenas mais um cenário com importações estáticas. No capítulo 1, você aprendeu que importar duas classes com o mesmo nome gera um erro do compilador. Isso é verdade para importações estáticas também. O compilador reclamará se você tentar fazer explicitamente uma importação estática de dois métodos com o mesmo nome ou duas variáveis estáticas com o mesmo nome. Por exemplo:
import static statics.A.TYPE;
import static statics.B.TYPE; // DOES NOT COMPILE
Felizmente, quando isso acontece, podemos apenas nos referir aos membros estáticos através do nome da classe em o código em vez de tentar usar uma importação estática.
Java é uma linguagem de "passagem por valor". Isso significa que é feita uma cópia da variável e o método recebe essa cópia. As atribuições feitas no método não afetam o chamador. Vamos ver um exemplo:
2: public static void main(String[] args) {
3: int num = 4;
4: newNumber(5);
5: System.out.println(num); // 4
6: }
7: public static void newNumber(int num) {
8: num = 8;
9: }
Na linha 3, num é atribuído o valor de 4. Na linha 4, chamamos um método. Na linha 8, o parâmetro num é definido no método como 8. Embora esse parâmetro tenha o mesmo nome que o variável na linha 3, isso é uma coincidência. O nome pode ser qualquer coisa. O exame geralmente use o mesmo nome para tentar confundi-lo. A variável na linha 3 nunca muda porque não há atribuições feitas a ele.
Agora que você viu primitivas, vamos tentar um exemplo com um tipo de referência. O que você faz acha que é produzido pelo seguinte código?
public static void main(String[] args) {
String name = "Webby";
speak(name);
System.out.println(name);
}
public static void speak(String name) {
name = "Sparky";
}
A resposta correta é Webby . Assim como no exemplo primitivo, a atribuição de variável é apenas para o parâmetro method e não afeta o chamador.
Observe como continuamos falando sobre atribuições de variáveis. Isso ocorre porque podemos chamar métodos nos parâmetros. Como exemplo, temos um código que chama um método no StringBuilder transmitido para o método:
public static void main(String[] args) {
StringBuilder name = new StringBuilder();
speak(name);
System.out.println(name); // Webby
}
public static void speak(StringBuilder s) {
s.append("Webby");
}
Nesse caso, a saída é Webby porque o método simplesmente chama um método no parâmetro. Não reatribui o nome a um objeto diferente. Na Figura 4.4, você pode ver como a passagem por valor ainda é usada. s é uma cópia do nome da variável . Ambos apontam para o mesmo StringBuilder , o que significa que as alterações feitas no StringBuilder estão disponíveis para ambas as referências.
Idiomas diferentes tratam parâmetros de maneiras diferentes. A passagem por valor é usada por muitos idiomas, incluindo Java. Neste exemplo, o método de troca não altera a origem valores finais. É apenas altera um e b dentro do método.
public static void main(String[] args) {
int original1 = 1;
int original2 = 2;
swap(original1, original2);
System.out.println(original1); // 1
System.out.println(original2); // 2
}
public static void swap(int a, int b) {
int temp = a;
a = b;
b = temp;
}
A outra abordagem é a passagem por referência. É usado por padrão em alguns idiomas, como Perl. Nós não vamos mostrar o código Perl aqui porque você está estudando para o Java exame e não queremos confundi-lo. O exemplo a seguir está em um idioma inventado que mostra passagem por referência:
original1 = 1;
original2 = 2;
swapByReference(original1, original2);
print(original1); // 2 (not in Java)
print(original2); // 1 (not in Java)
swapByReference(a, b) {
temp = a;
a = b;
b = temp;
}
Veja a diferença? Em nossa linguagem inventada, o chamador é afetado pela atribuição de variáveis procedimentos realizados no método.