1. Pontos de conhecimento sobre controle de objeto e memória
1. O processo de inicialização das variáveis Java, incluindo variáveis locais, variáveis de membros (variáveis de instância e variáveis de classe).
2. Na relação de herança, quando o tipo de tempo de compilação e o tipo de tempo de execução são diferentes do tipo de tempo de compilação da variável de referência de objeto usada, há uma diferença nas propriedades e métodos de acesso ao objeto.
3. Características finais do modificador.
2. O processo de divisão e inicialização das variáveis Java
As variáveis dos programas Java podem ser divididas aproximadamente em variáveis de membros e variáveis locais. As variáveis de membro podem ser divididas em variáveis de instância (variáveis não estáticas) e variáveis de classe (variáveis estáticas). Geralmente, as variáveis locais que encontramos aparecerão nas seguintes situações:
(1) Parâmetro formal: as variáveis locais definidas na assinatura do método são atribuídas pelo chamador e desaparecem quando o método termina.
(2) Variáveis locais dentro do método: as variáveis locais definidas no método devem ser inicializadas (atribua um valor inicial) no método e desaparece quando a inicialização da variável começa e termina.
(3) Variáveis locais no bloco de código: as variáveis locais definidas no bloco de código devem ser inicializadas (atribuir valores iniciais) que devem ser exibidos nos blocos de código. Eles entrarão em vigor à medida que a inicialização é concluída e morre quando o bloco de código termina.
pacote com.zlc.array; classe pública Testfield {{string b; // Se não for inicializado, o compilador relatará que a variável local b pode não ter sido inicializada System.out.println (b); } public static void main (string [] args) {int a; // Se não for inicializado, o compilador relatará a variável local a pode não ter sido inicializada System.out.println (a); }} As variáveis de membros modificadas com estática são variáveis de classe, que pertencem à própria classe. Variáveis de membro que não são modificadas com estática são variáveis de instância. Instâncias pertencentes a esta classe, na mesma JVM, cada classe pode corresponder apenas a um objeto de classe, mas cada classe pode criar vários objetos Java. (Ou seja, uma variável de classe requer apenas uma parte do espaço de memória e, toda vez que a classe cria uma instância, ela precisa alocar uma parte do espaço para a variável de instância)
Processo de inicialização das variáveis de instância: De uma perspectiva de sintaxe, o programa pode executar a inicialização de variáveis de instância em três locais:
(1) Especifique o valor inicial ao definir uma variável de instância.
(2) Especifique o valor inicial, por exemplo, variáveis em blocos não estáticos.
(3) Especifique o valor inicial, por exemplo, variáveis no construtor.
Entre eles, o tempo de inicialização dos dois métodos (1) e (2) é anterior ao (3) no construtor, e as duas ordens de inicialização (1) e (2) são determinadas na ordem em que estão dispostas no código -fonte.
pacote com.zlc.array; classe pública Testfield {public testField (int Age) {System.out.println ("Inicialize este.age no construtor ="+this.age); this.age = idade; } {System.out.println ("Inicialize em blocos não estáticos"); idade = 22; } // inicialize int Age = 15; public static void main (string [] args) {testfield field = new testfield (24); System.out.println ("Final Age ="+Field.age); }} O resultado da execução é: Inicialize isto.age = 15 no construtor de inicialização no bloco não estático
Idade final = 24
Se você souber usar o Javap, pode usar o Javap -c XXXX (FILE Class) para ver como a classe Java é compilada.
Ao definir uma variável de instância, especifique o valor inicial. No bloco de inicialização, o status da instrução especificando o valor inicial para a variável de instância é igual. Depois que o compilador é compilado e processado, todos são mencionados no construtor. A Int Age acima mencionada = 15 será dividida nas duas etapas a seguir para executar:
1) idade int; Ao criar um objeto Java, o sistema aloca memória para o objeto de acordo com a declaração.
2) idade = 15; Esta declaração será extraída para o construtor da classe Java e executada.
Processo de inicialização das variáveis de classe: De uma perspectiva de sintaxe, um programa pode inicializar e atribuir valores às variáveis de classe de dois locais.
(1) Especifique o valor inicial ao definir uma variável de classe.
(2) Especifique o valor inicial para variáveis de classe em um bloco estático.
As duas ordens de execução são as mesmas que seu arranjo no código -fonte. Vamos dar um pequeno exemplo anormal:
pacote com.zlc.array; classe testStatic {// Classe Demo de Demonstração de Membro Teststatic Instância final estática TestStatic Demo = new TestStatic (15); // Membro da classe estática Int Age = 20; // Variável de instância Curage int Curage; public testStatic (int anos) {// TODO Construtor de auto -generado Curage = Age - Anos; }} public class Test {public static void main (string [] args) {System.out.println (testStatic.Demo.curage); TestStatic staticDemo = new Teststatic (15); System.out.println (staticdemo.curage); }} O resultado da saída é impresso em duas linhas. Uma é imprimir a variável de instância da demonstração do atributo de classe Teststatic, e a segunda é emitir o atributo de instância do testStatic através do staticDemo do objeto Java. De acordo com o processo de inicialização da variável de instância e variáveis de classe que analisamos acima, podemos inferir:
1) Na primeira etapa da inicialização, ao carregar a classe, aloque o espaço de memória para a demonstração e a idade das variáveis de classe. No momento, os valores padrão de demonstração e idade são nulos e 0, respectivamente.
2) Na segunda etapa da inicialização, o programa atribui valores iniciais à demonstração e envelhecer em sequência. Teststatic (15) precisa chamar o construtor do Teststatic. Neste momento, idade = 0, então o resultado da impressão é -15. Quando o StaticDemo é inicializado, a idade foi atribuída a 20, portanto o resultado da saída é 5.
3. A diferença entre herdar variáveis de membros e herdar métodos de membros em relacionamentos de herança
Ao criar qualquer objeto Java, o programa sempre chamará o bloco não estático e o construtor da classe pai da classe pai primeiro e, finalmente, chamará o bloco e o construtor não estáticos desta classe. Chamar o construtor da classe pai através do construtor da subclasse é geralmente dividido em duas situações: uma é uma chamada implícita e a outra é uma super tela para chamar o construtor da classe pai.
O método da classe infantil pode chamar a variável de instância da classe pai. Isso ocorre porque a classe infantil herda a classe pai e obterá as variáveis e métodos do membro da classe pai. No entanto, o método da classe pai não pode acessar a variável de instância da classe infantil, porque a classe pai não sabe qual classe herdará e que tipo de variáveis de membro sua subclasse adicionará. Obviamente, em alguns exemplos extremos, a classe pai ainda pode ligar para a variável da classe infantil. Por exemplo: a classe infantil reescreve o método da classe pai e geralmente imprime o valor padrão, porque a variável de instância da classe criança não foi inicializada no momento.
pacote com.zlc.Array; Classe Pai {int Age = 50; public Pai () {// TODO Construtor Geralated Stub System.out.println (this.getClass ()); //this.sonMethod (); Não é possível chamar info (); } public void info () {System.out.println (idade); }} public classe filho estende o pai {int Age = 24; filho público (INT AGE) {// TODO Construtor de auto-generado tobo this.age = Age; } @Override public void info () {// TODO Method Auto-Gerated Stub System.err.println (idade); } public static void main (string [] args) {new Son (28); } // Método específico da subclasse public void SonMethod () {System.out.println ("Son Method"); }} De acordo com nossa inferência normal, o construtor da classe pai é implicitamente chamado através da subclasse, e o método info () é chamado no construtor da classe pai (Nota: eu não disse que a classe pai é chamada). Em teoria, produz a variável da instância da idade da classe pai. O resultado da impressão deve ser de 50, mas o resultado real da saída é 0. Análise do motivo:
1) A alocação de memória dos objetos Java não está concluída no construtor. O construtor conclui apenas o processo de atribuição de inicialização. Ou seja, antes de chamar o construtor da classe pai, a JVM classificou o espaço de memória para o objeto Son. Esse espaço armazena dois atributos etários, um é a idade da subclasse e a outra é a idade da classe pai.
2) Ao chamar o novo filho (28), o atual esse objeto representa um objeto que é um filho subclasse. Podemos imprimir o object.getClass () e obter o resultado da classe com.zlc.array.son. No entanto, o processo de inicialização atual é realizado no construtor da classe pai e não pode ser chamado por meio disso.sonMethod (), porque esse tipo de compilação é pai.
3) Quando o tipo de tempo de compilação da variável é diferente do tipo de tempo de execução, ao acessar a variável de instância de seu objeto de referência através da variável, o valor da variável de instância é determinado pelo tipo da variável declarada. No entanto, quando o método da instância do objeto que ele faz referência através da variável, o comportamento do método é determinado pelo objeto que ele realmente faz referência. Portanto, o método de informação da subclasse é chamado aqui; portanto, a idade da subclasse é impressa. Como a idade ainda não foi inicializada, o valor padrão é 0.
Nos termos do leigo, quando o tipo declarado é inconsistente com o novo tipo real, o atributo usado é a classe pai e o método chamado é a classe infantil.
Através do Javap -C, podemos entender mais diretamente por que há uma grande diferença entre herdar atributos e métodos. Se removermos o método de reescrita de informações da subclasse Son no exemplo acima, o método de informações da classe pai será chamado neste momento, porque, ao compilar, o método de informação da classe pai será transferido para a subclasse, e a variável de membro da reputação será deixada na classe pai e não transferida. Dessa forma, a subclasse e a classe pai têm variáveis de instância com o mesmo nome. Se a subclasse reescrever o método da classe pai com o mesmo nome, o método da subclasse substituirá completamente o método da classe pai (como por que o Java foi projetado assim, não estou muito claro). Variáveis com o mesmo nome podem existir e não substituem ao mesmo tempo. As subclasses de métodos com o mesmo nome substituirão completamente o método de mesmo nome da classe pai.
Em geral, para uma variável de referência, ao acessar uma variável de instância do objeto que ela faz referência através da variável, o valor da variável de instância depende do tipo quando a variável é declarada e, ao acessar o método do objeto que ele referencia através da variável, o comportamento do método depende do tipo de objeto que ele realmente referencia.
Finalmente, vou revisá -lo com um pequeno caso:
pacote com.zlc.array; classe animal {int Age; public animal () {} public animal (Int Age) {// TODO Construtor GOERATADO AUTOGERATO ESTE.AGE = AGE; } void run () {System.out.println ("Animal Run"+Age); }} classe cão estende Animal {int Age; Nome da string; Public Dog (Int Age, String Name) {// TODO Construtor GOERATIDO Auto-GONERATO TUBS.AGE = AGE; this.name = nome; } @Override void run () {System.out.println ("Dog Run"+Age); }} classe pública testextends {public static void main (string [] args) {animal animal = novo animal (5); System.out.println (animal.age); animal.run (); Cachorro cachorro = novo cachorro (1, "xiaobai"); System.out.println (Dog.age); cachorro.run (); Animal animal2 = novo cachorro (11, "wangcai"); System.out.println (animal2.age); animal2.run (); Animal animal3; animal3 = cachorro; System.out.println (animal3.age); animal3.run (); }} Se você deseja ligar para o método da classe pai: você pode chamá -lo através do Super, mas a super palavra -chave não se refere a nenhum objeto e não pode ser usado como uma variável de referência real. Amigos interessados podem estudá -lo sozinho.
Os acima são variáveis e métodos de exemplo. As variáveis de classe e os métodos de classe são muito mais simples, portanto, usando os nomes de classe diretamente. Os métodos são muito mais convenientes e você não encontrará tantos problemas.
4. Uso dos modificadores finais (especialmente a substituição de macro)
(1) Inal pode modificar variáveis. Depois que a variável modificada pelo final recebe um valor inicial, ela não pode ser atribuída novamente.
(2) Inal pode modificar o método e o método modificado final não pode ser reescrito.
(3) Inal pode modificar classes e as classes modificadas por final não podem derivar subclasses.
O valor inicial especificado que a variável modificada por final deve ser exibida:
Por exemplo, variáveis que são modificadas finais, o valor inicial só pode ser atribuído nas três posições especificadas a seguir.
(1) Especifique o valor inicial ao definir a variável de instância final.
(2) Especifique o valor inicial para a variável de instância final em um bloco não estático.
(3) Especifique o valor inicial para a variável de instância final no construtor.
Eles serão mencionados no construtor para inicialização.
Para variáveis de classe especificadas com o final: os valores iniciais só podem ser atribuídos em dois locais especificados.
(1) Especifique o valor inicial ao definir a variável de classe final.
(2) Especifique o valor inicial para a variável de classe final em um bloco estático.
Também processado pelo compilador, diferentemente das variáveis de instância, as variáveis de classe são mencionadas para atribuir valores iniciais em blocos estáticos, enquanto as variáveis de instância são mencionadas aos construtores.
Há outro recurso das variáveis de classe modificadas por final, que é "substituição de macro". Quando a variável de classe modificada satisfaz o valor inicial ao definir a variável, o valor inicial pode ser determinado durante a compilação (por exemplo: 18, "AAAA", 16,78 e outras quantidades diretas), a variável de classe modificada por final não é uma variável e o sistema o tratará como uma "macro variável" (que é frequentemente chamada de constante). Se o valor inicial puder ser determinado durante a compilação, ele não será mencionado no bloco estático para inicialização e o valor inicial será diretamente substituído pela variável final na definição da classe. Vamos dar um exemplo de idade menos ano:
pacote com.zlc.array; classe testStatic {// Classe Demo de Demonstração de Membro Teststatic Instância final estática TestStatic Demo = new TestStatic (15); // Membro da classe Age estática final Int Age = 20; // Variável de instância Curage int Curage; public testStatic (int anos) {// TODO Construtor de auto -generado Curage = Age - Anos; }} public class Test {public static void main (string [] args) {System.out.println (testStatic.Demo.curage); TestStatic static1 = new Teststatic (15); System.out.println (static1.curage); }} No momento, a idade é modificada por final; portanto, ao compilar, todas as idades da classe pai se tornam 20, não uma variável, para que o resultado da saída possa atender às nossas expectativas.
Especialmente ao comparar strings, pode ser exibido mais
pacote com.zlc.array; classe pública testString {static string static_name1 = "java"; static string static_name2 = "me"; Static string static statci_name3 = static_name1+static_name2; Final Static String final_static_name1 = "java"; Final Static String final_static_name2 = "me"; // Adicionar final ou não, ele pode ser substituído por uma macro na frente. Final String final_statci_name3 = final_static_name1+final_static_name2; public static void main (string [] args) {string name1 = "java"; String name2 = "me"; Nome da string3 = nome1+nome2; // (1) System.out.println (nome3 == "Javame"); // (2) system.out.println (testString.statci_name3 == "javame"); // (3) System.out.println (testString.Final_Statci_Name3 == "Javame"); }} Não há nada a dizer sobre o uso de métodos e classes finais de modificação, apenas que um não pode ser reescrito por subclasses (como privado), e o outro não pode derivar subclasses.
Ao modificar as variáveis locais com o final, o Java exige que as variáveis locais acessadas por classes internas sejam modificadas com o final. Há uma razão. Para variáveis locais comuns, seu escopo permanece dentro do método. Quando o método termina, a variável local desaparece, mas a classe interna pode gerar um "fechamento" implícito, o que faz com que a variável local permaneça separada do método onde está localizado.
Às vezes, um thread será novo em um método e, em seguida, a variável local do método é chamada. Neste momento, a variável de alteração precisa ser declarada como modificada final.
5. Método de cálculo da memória de ocupação de objetos
Use os métodos Freemory (), TotalMemory () e MaxMemory () nas classes java.lang.runtime para medir o tamanho de um objeto Java. Esse método é geralmente usado quando muitos recursos precisam ser determinados com precisão. Este método é quase inútil para implementar o cache do sistema de produção. A vantagem desse método é que o tipo de dados é independente do tamanho e diferentes sistemas operacionais podem obter a memória ocupada.
Ele usa a API de reflexão para atravessar a hierarquia das variáveis de membros de um objeto e calcular o tamanho de todas as variáveis originais. Essa abordagem não requer muitos recursos e pode ser usada para implementações em cache. A desvantagem é que o tamanho do tipo original é diferente e diferentes implementações de JVM têm métodos de cálculo diferentes.
Após o JDK5.0, a API de instrumentação fornece o método GetObjectSize para calcular o tamanho da memória ocupado pelo objeto.
Por padrão, o tamanho do objeto referenciado não é calculado. Para calcular o objeto referenciado, você pode usar a reflexão para obtê -lo. O método a seguir é uma implementação fornecida no artigo acima que calcula o tamanho do objeto de referência:
classe pública sizeofagent {instrumentação estática inst; / ** Inicializa o agente*/ public static void premain (string agenteargs, instrumentação instp) {Inst = Instp; } /*** Retorna o tamanho do objeto sem subobjetos do membro. * @param o Objeto para obter o tamanho de * @return Tamanho do objeto */public estático tamanho longo } retornar Inst.getObjectSize (O); } /** * Calcula o tamanho completo do objeto iterando o seu gráfico de hierarquia. * @param objeto para calcular o tamanho de * @return Tamanho do objeto */ public static long FullSizeof (object obj) {map <objeto, objeto> visitado = novo identityhashmap <object, object> (); Stack <ject> pilha = new Stack <ject> (); Longo resultado = Internalsizeof (obj, pilha, visitado); while (! Stack.isEmpty ()) {resultado += Internalsizeof (Stack.pop (), Stack, visitado); } visitado.clear (); resultado de retorno; } private estático booleano skipobject (object obj, map <objeto, objeto> visitado) {if (obj instanceof string) {// skip string internada if (obj == ((string) obj) .intern ()) {return true; }} return (obj == null) // Skip Visited Object || visitou.containsKey (OBJ); } private estático Longo InternalsizeOf (objeto obj, Stack <ject> pilha, mapa <objeto, objeto> visitado) {if (skipobject (obj, visitado)) {return 0; } visitou.put (obj, nulo); resultado longo = 0; // obtém tamanho de objeto + variáveis primitivas + ponto de membro resultado + = sizeofagent.sizeof (obj); // processa todos os elementos da matriz classe clazz = obj.getclass (); if (clazz.isArray ()) {if (clazz.getName (). length ()! = 2) {// pula o tipo de tipo primitivo Array int length = array.getLength (obj); for (int i = 0; i <comprimento; i ++) {staCK.add (array.get (obj, i)); }} Retornar resultado; } // Processar todos os campos do objeto while (clazz! = null) {field [] campos = clazz.getDeclaredFields (); para (int i = 0; i <fields.Length; i ++) {if (! modifier.isstatic (campos [i] .getModifiers ())) {if (campos [i] .getType (). isPrimition ())) {continuação; // pula os campos primitivos} else {campos [i] .setAccessible (true); tente {// Objetos a serem estimados são colocados para empilhar objetos objectToadd = campos [i] .get (obj); if (objectToadd! = null) {staCK.add (objectToadd); }} Catch (ilegalAccessException ex) {Assert false; }}}}} clazz = clazz.getsuperclass (); } resultado de retorno; }}