1. Puntos de conocimiento sobre el control de objetos y memoria
1. El proceso de inicialización de las variables Java, incluidas las variables locales, las variables miembros (variables de instancia y las variables de clase).
2. En la relación de herencia, cuando el tipo de compilación y el tipo de tiempo de ejecución son diferentes del tipo de tiempo de compilación de la variable de referencia del objeto utilizada, hay una diferencia en las propiedades y métodos para acceder al objeto.
3. Características del modificador final.
2. El proceso de división e inicialización de las variables Java
Las variables de los programas Java se pueden dividir aproximadamente en variables miembros y variables locales. Las variables de los miembros se pueden dividir en variables de instancia (variables no estáticas) y variables de clase (variables estáticas). En general, las variables locales que encontramos aparecerán en las siguientes situaciones:
(1) Parámetro formal: las variables locales definidas en la firma del método son asignadas por la persona que llama y desaparece a medida que finaliza el método.
(2) Variables locales dentro del método: Las variables locales definidas en el método deben inicializarse (asignar un valor inicial) en el método y desaparece cuando comienza la inicialización de la variable y termina.
(3) Variables locales en el bloque de código: las variables locales definidas en el bloque de código deben inicializarse (asignar valores iniciales) que deben mostrarse en bloques de código. Entran en vigencia a medida que se complete la inicialización y morirán a medida que finalice el bloque de código.
paquete com.zlc.array; Public Class Testfield {{String B; // Si no se inicializa, el compilador informará que la variable local B puede no haber sido inicializada System.out.println (b); } public static void main (string [] args) {int a; // Si no se inicializa, el compilador informará la variable local A no puede haberse inicializado System.out.println (a); }} Las variables miembros modificadas con estáticas son variables de clase, que pertenecen a la clase misma. Las variables miembros que no se modifican con Static son variables de instancia. Las instancias que pertenecen a esta clase, en la misma JVM, cada clase solo puede corresponder a una clase de objeto, pero cada clase puede crear múltiples objetos Java. (Es decir, una variable de clase solo requiere una pieza de espacio de memoria, y cada vez que la clase crea una instancia, necesita asignar un espacio a la variable de instancia)
Proceso de inicialización de variables de instancia: desde una perspectiva de sintaxis, el programa puede realizar la inicialización de las variables de instancia en tres lugares:
(1) Especifique el valor inicial al definir una variable de instancia.
(2) Especifique el valor inicial, por ejemplo, las variables en bloques no estáticos.
(3) Especifique el valor inicial, por ejemplo, las variables en el constructor.
Entre ellos, el tiempo de inicialización de los dos métodos (1) y (2) es anterior al de (3) en el constructor, y las dos órdenes de inicialización (1) y (2) se determinan en el orden en que se organizan en el código fuente.
paquete com.zlc.array; public class testfield {public testfield (int a age) {System.out.println ("Inicializar esto.age en el constructor ="+this.age); this.age = edad; } {System.out.println ("Inicializar en bloques no estáticos"); edad = 22; } // inicializar int age = 15; public static void main (string [] args) {testField Field = New Testfield (24); System.out.println ("Final Age ="+Field.age); }} El resultado de la ejecución es: inicializar esto.age = 15 en el constructor de inicialización en el bloque no estático
Edad final = 24
Si sabe cómo usar Javap, puede usar Javap -C xxxx (archivo de clase) para ver cómo se compila la clase Java.
Al definir una variable de instancia, especifique el valor inicial. En el bloque de inicialización, el estado de la declaración que especifica el valor inicial para la variable de instancia es igual. Después de compilar y procesar el compilador, todos se mencionan en el constructor. La AGE int Age = 15 mencionada anteriormente se dividirá en los siguientes dos pasos para ejecutar:
1) int Age; Al crear un objeto Java, el sistema asigna la memoria al objeto de acuerdo con la declaración.
2) edad = 15; Esta declaración se extraerá en el constructor de la clase Java y se ejecutará.
Proceso de inicialización de variables de clase: desde una perspectiva de sintaxis, un programa puede inicializar y asignar valores a las variables de clase desde dos lugares.
(1) Especifique el valor inicial al definir una variable de clase.
(2) Especifique el valor inicial para las variables de clase en un bloque estático.
Las dos órdenes de ejecución son las mismas que su disposición en el código fuente. Demos un pequeño ejemplo anormal:
paquete com.zlc.array; clase TestStatic {// Class Demo Instance Teststatic Final Static TestStatic Demo = New TestStatic (15); // miembro de clase static int age = 20; // Curaje de curado variable de instancia int Curaje; Public testStatic (int años) {// TODO Auto Generated Constructor Stub Curge = Age - años; }} prueba de clase pública {public static void main (String [] args) {System.out.println (teststatic.demo.curage); Teststatic staticDemo = nuevo teststatic (15); System.out.println (staticDemo.curage); }} El resultado de salida se imprime en dos líneas. Una es imprimir la variable de instancia de la demostración de atributo de clase TestStatic, y la segunda es generar el atributo de instancia de TestStatic a través de StaticDemo del objeto Java. De acuerdo con el proceso de inicialización de la variable de instancia y las variables de clase que analizamos anteriormente, podemos inferirlo:
1) En la primera etapa de inicialización, al cargar la clase, asigne el espacio de memoria para la demostración y la edad de las variables de clase. En este momento, los valores predeterminados de demostración y edad son nulos y 0 respectivamente.
2) En la segunda etapa de inicialización, el programa asigna valores iniciales a demostración y edad en secuencia. Teststatic (15) necesita llamar al constructor de Teststatic. En este momento, edad = 0, por lo que el resultado de la impresión es -15. Cuando StaticDemo se inicializa, la edad se ha asignado a 20, por lo que el resultado de la salida es 5.
3. La diferencia entre heredar las variables de los miembros y los métodos de los miembros heredados en las relaciones de herencia
Al crear cualquier objeto Java, el programa siempre llamará al constructor de clase no estático y a la clase principal de la clase principal, y finalmente llamará al bloque y el constructor no static de esta clase. Llamar al constructor de la clase principal a través del constructor de la subclase generalmente se divide en dos situaciones: una es una llamada implícita, y la otra es una súper pantalla para llamar al constructor de la clase principal.
El método de clase infantil puede llamar a la variable de instancia de la clase principal. Esto se debe a que la clase infantil hereda la clase principal y obtendrá las variables y métodos miembros de la clase principal. Sin embargo, el método de la clase principal no puede acceder a la variable de instancia de la clase infantil porque la clase principal no sabe qué clase heredará y qué tipo de variables de miembro agregará su subclase. Por supuesto, en algunos ejemplos extremos, la clase principal aún puede llamar a la variable de clase infantil. Por ejemplo: la clase infantil reescribe el método de la clase principal y generalmente imprime el valor predeterminado, porque la variable de instancia de la clase infantil no se ha inicializado en este momento.
paquete com.zlc.array; class padre {int age = 50; Public Padre () {// TODO Auto Generated Constructor Stub System.out.println (this.getClass ()); //this.sonmethod (); no puedo llamar a la información (); } public void info () {System.out.println (edad); }} El hijo de clase pública extiende el padre {int Age = 24; Public Son (int Age) {// TODO Constructor generado automático Stub this.age = Age; } @Override public void info () {// TODO Auto Generado Método Stub System.err.println (Age); } public static void main (string [] args) {new son (28); } // método específico de subclase public void sonmethod () {system.out.println ("método de hijo"); }} De acuerdo con nuestra inferencia normal, el constructor de la clase principal se llama implícitamente a través de la subclase, y el método info () se llama en el constructor de la clase principal (nota: no dije que la clase principal se llama). En teoría, genera la variable de instancia de edad de la clase principal. Se espera que el resultado de la impresión sea 50, pero el resultado de salida real es 0. Análisis de la razón:
1) La asignación de memoria de los objetos Java no se completa en el constructor. El constructor solo completa el proceso de asignación de inicialización. Es decir, antes de llamar al constructor de la clase principal, JVM ha clasificado el espacio de memoria para el objeto Son. Este espacio almacena dos atributos de edad, uno es la edad de la subclase y el otro es la edad de la clase principal.
2) Al llamar al nuevo hijo (28), la corriente de este objeto representa un objeto que es un hijo subclase. Podemos imprimir el objeto.getClass () y obtener el resultado de la clase com.zlc.array.son. Sin embargo, el proceso de inicialización actual se lleva a cabo en el constructor de la clase principal, y no se puede llamar a través de este.sonmethod (), porque este tipo de compilación es padre.
3) Cuando el tipo de tiempo de compilación de la variable es diferente del tipo de tiempo de ejecución, al acceder a la variable de instancia de su objeto de referencia a través de la variable, el valor de la variable de instancia se determina por el tipo de la variable declarada. Sin embargo, cuando el método de instancia del objeto se hace referencia a través de la variable, el comportamiento del método está determinado por el objeto que realmente hace referencia. Por lo tanto, el método de información de la subclase se llama aquí, por lo que se imprime la edad de la subclase. Dado que la edad aún no se ha inicializado con urgencia, el valor predeterminado es 0.
En términos de Layman, cuando el tipo declarado es inconsistente con el nuevo tipo real, el atributo utilizado es la clase principal y el método llamado es la clase infantil.
A través de Javap -C, podemos comprender más directamente por qué hay una gran diferencia entre heredar atributos y métodos. Si eliminamos el método de reescritura de información del hijo de subclase en el ejemplo anterior, el método de información de la clase principal se llamará en este momento, porque al compilar, el método de información de la clase principal se transferirá a la subclase, y la variable de miembro de la reputación se dejará en la clase principal y no transferida. De esta manera, la subclase y la clase principal tienen variables de instancia con el mismo nombre. Si la subclase reescribe el método de la clase principal con el mismo nombre, el método de subclase sobrescribirá completamente el método de la clase principal (ya que Java está diseñada así, no estoy muy claro). Las variables con el mismo nombre pueden existir y no sobrescribir al mismo tiempo. Las subclases de métodos con el mismo nombre sobrescribirán completamente el mismo método de nombre de la clase principal.
En general, para una variable de referencia, al acceder a una variable de instancia del objeto que hace referencia a través de la variable, el valor de la variable de instancia depende del tipo cuando se declara la variable, y al acceder al método del objeto que hace referencia a través de la variable, el comportamiento del método depende del tipo del objeto que realmente hace referencia.
Finalmente, lo revisaré con un pequeño caso:
paquete com.zlc.array; class animal {int Age; Public Animal () {} public animal (int a age) {// TODO Constructor generado automático Stub this.age = Age; } void run () {system.out.println ("animal run"+edad); }} El perro de clase extiende animal {int Age; Nombre de cadena; Public Dog (int Age, String Name) {// TODO Auto Generated Constructor Stub this.age = Age; this.name = name; } @Override void run () {System.out.println ("Dog Run"+Age); }} public class testExtendes {public static void main (string [] args) {animal animal = new animal (5); System.out.println (animal.age); animal.run (); Perro perro = nuevo perro (1, "xiaobai"); System.out.println (dog.age); dog.run (); Animal animal2 = nuevo perro (11, "wangcai"); System.out.println (animal2.age); animal2.run (); Animal animal3; animal3 = perro; System.out.println (animal3.age); animal3.run (); }} Si desea llamar al método de clase principal: puede llamarlo a través de Super, pero la palabra clave súper no se refiere a ningún objeto, y no puede usarse como una variable de referencia real. Los amigos interesados pueden estudiarlo usted mismo.
Los anteriores son variables y métodos de ejemplo. Las variables de clase y los métodos de clase son mucho más simples, por lo que el uso de nombres de clase directamente. Los métodos son mucho más convenientes y no encontrará tantos problemas.
4. Uso de modificadores finales (especialmente reemplazo macro)
(1) Inal puede modificar variables. Después de que la variable modificada por final se le asigna un valor inicial, no se puede asignar nuevamente.
(2) Inal puede modificar el método, y el método modificado final no puede reescribirse.
(3) Inal puede modificar las clases, y las clases modificadas por final no pueden derivar subclases.
El valor inicial especificado que se debe mostrar la variable modificada por final:
Por ejemplo, variables que están finales modificadas, el valor inicial solo se puede asignar en las siguientes tres posiciones especificadas.
(1) Especifique el valor inicial al definir la variable de instancia final.
(2) Especifique el valor inicial para la variable de instancia final en un bloque no estático.
(3) Especifique el valor inicial para la variable de instancia final en el constructor.
Eventualmente serán mencionados en el constructor para la inicialización.
Para variables de clase especificadas con finales: los valores iniciales solo se pueden asignar en dos lugares especificados.
(1) Especifique el valor inicial al definir la variable de clase final.
(2) Especifique el valor inicial para la variable de clase final en un bloque estático.
También procesado por el compilador, a diferencia de las variables de instancia, las variables de clase se mencionan para asignar valores iniciales en bloques estáticos, mientras que las variables de instancia se mencionan a los constructores.
Hay otra característica de las variables de clase modificadas por Final, que es "Reemplazo macro". Cuando la variable de clase modificada satisface el valor inicial al definir la variable, el valor inicial se puede determinar durante la compilación (por ejemplo: 18, "AAAA", 16.78 y otras cantidades directas), entonces la variable de clase modificada por final no es una variable, y el sistema lo tratará como una "variable macro" (que es lo que a menudo llamamos una constante). Si el valor inicial se puede determinar durante la compilación, no se mencionará en el bloque estático para la inicialización, y el valor inicial será reemplazado directamente por la variable final en la definición de clase. Demos un ejemplo de edad menos año:
paquete com.zlc.array; clase TestStatic {// Class Demo Instance Teststatic Final Static TestStatic Demo = New TestStatic (15); // Edad miembro de la clase Final Static int Age = 20; // Curaje de curado variable de instancia int Curaje; Public testStatic (int años) {// TODO Auto Generated Constructor Stub Curge = Age - años; }} prueba de clase pública {public static void main (String [] args) {System.out.println (teststatic.demo.curage); Teststatic static1 = nuevo teststatic (15); System.out.println (static1.curage); }} En este momento, la edad se modifica por final, por lo que al compilar, todas las edades en la clase principal se convierten en 20, no una variable, para que el resultado de la salida pueda cumplir con nuestras expectativas.
Especialmente al comparar cadenas, se puede mostrar más
paquete com.zlc.array; clase pública testString {static string static_name1 = "java"; cadena estática static_name2 = "me"; cadena estática static statci_name3 = static_name1+static_name2; cadena estática final final_static_name1 = "java"; cadena estática final final_static_name2 = "me"; // Agregar final o no, puede ser reemplazado por una macro en el frente. Cadena final final_statci_name3 = final_static_name1+final_static_name2; public static void main (string [] args) {string name1 = "java"; String name2 = "me"; String name3 = name1+name2; // (1) System.out.println (name3 == "Javame"); // (2) System.out.println (testString.statci_name3 == "Javame"); // (3) System.out.println (testString.final_statci_name3 == "Javame"); }} No hay nada que decir sobre el uso de métodos y clases de modificación final, solo que uno no puede ser reescribido por subclases (como privadas), y el otro no puede derivar subclases.
Al modificar las variables locales con Final, Java requiere que las variables locales a las que se accede por clases internas se modifica con Final. Hay una razón. Para las variables locales ordinarias, su alcance permanece dentro del método. Cuando termina el método, la variable local desaparece, pero la clase interna puede generar un "cierre" implícito, lo que hace que la variable local permanezca separada del método donde se encuentra.
A veces, un hilo será nuevo en un método y luego se llama a la variable local del método. En este momento, la variable de cambio debe declararse como modificada final.
5. Método de cálculo de la memoria de ocupación de objetos
Use los métodos freeMemory (), TotalMemory () y MaxMemory () en las clases Java.Lang.Runtime para medir el tamaño de un objeto Java. Este método generalmente se usa cuando muchos recursos deben determinarse con precisión. Este método es casi inútil para implementar la caché del sistema de producción. La ventaja de este método es que el tipo de datos es independiente del tamaño, y diferentes sistemas operativos pueden obtener la memoria ocupada.
Utiliza la API de reflexión para atravesar la jerarquía de las variables miembros de un objeto y calcular el tamaño de todas las variables originales. Este enfoque no requiere tantos recursos y puede usarse para implementaciones en caché. La desventaja es que el tamaño de tipo original es diferente y las implementaciones de JVM diferentes tienen diferentes métodos de cálculo.
Después de JDK5.0, la API de instrumentación proporciona el método GetObjectSize para calcular el tamaño de la memoria ocupado por el objeto.
Por defecto, no se calcula el tamaño del objeto referenciado. Para calcular el objeto referenciado, puede usar la reflexión para obtenerlo. El siguiente método es una implementación proporcionada en el artículo anterior que calcula el tamaño del objeto de referencia:
clase pública sizeofagent {Instrumentación estática Inst; / ** Inicializa el agente*/ public static void premain (String agenteRargs, instrumentación instp) {inst = instp; } /*** Devuelve el tamaño del objeto sin subbjetos del miembro. * @param o objeto para obtener el tamaño del tamaño del objeto * @return */public static sizeof (objeto o) {if (inst == null) {throw new IlegalStateException ("no puede acceder al entorno de instrumentación ./n" + ", verifique si el archivo jar contiene clases sizeOfagent IS/n" + "especificado en el argumento de la línea de comando java/"-javaagent/"."); ");"); ");"); } return inst.getObjectSize (o); } /** * Calcula el tamaño completo de objetos que iterando sobre * su gráfico de jerarquía. * @param Objeto para calcular el tamaño del tamaño del objeto * @return */ public static long Long FullSizeOf (object obj) {map <object, object> visited = new IdentityHashMap <Object, Object> (); Pila <ject> stack = new Stack <Sect> (); resultado largo = internalSizeOf (obj, pila, visitado); while (! stack.isEmpty ()) {resultado += internalSizeOf (stack.pop (), pila, visitado); } visited.clear (); resultado de retorno; } private static boolean skipObject (object obj, map <object, object> visited) {if (obj instancef string) {// omita la cadena internada if (obj == ((string) obj) .intern ()) {return true; }} return (obj == null) // omita el objeto visitado || visitado.containskey (obj); } private static long internalSizeOf (object obj, pila <pila> pila, mapa <objeto, objeto> visitado) {if (skipobject (obj, visitado)) {return 0; } visited.put (obj, nulo); resultado largo = 0; // Obtener tamaño de objeto + variables primitivas + puntos de puntos de miembro + = sizeOfagent.sizeOf (obj); // procesar todos los elementos de matriz class clazz = obj.getClass (); if (clazz.isArray ()) {if (clazz.getName (). longitud ()! = 2) {// omitir el tipo primitivo array int long = array.getLength (obj); para (int i = 0; i <longitud; i ++) {stack.add (array.get (obj, i)); }} Resultado de retorno; } // procesa todos los campos del objeto mientras (Clazz! = NULL) {Field [] Fields = Clazz.GetDeclaredFields (); for (int i = 0; i <fiields.length; i ++) {if (! Modifier.IsStatic (Fields [i] .getModifiers ())) {if (Fields [i] .GetType (). isPrimitive ())) {continuar; // omita los campos primitivos} else {campos [i] .setAccessible (true); intente {// Los objetos a estimar se ponen en el objeto de apila ObjectToAdd = Fields [i] .get (obj); if (objectToadd! = null) {stack.add (objectToAdd); }} catch (ilegalAccessException ex) {afirmar falso; }}}}} clazz = clazz.getSuperClass (); } resultado de retorno; }}