una puede poo modificadores modificador metodos metodo instanciar finales clases clase acceso java final

java - poo - se puede instanciar una clase final



¿El uso de la palabra clave final en Java mejora el rendimiento? (12)

Definitivamente sí, si la variable se convierte en constante,

como sabemos, el compilador de java convierte tales variables finales como constantes que son posibles

a medida que el concepto de compilador java constante reemplaza directamente el valor con su referencia en tiempo de compilación

en java las variables son constantes si se trata de una cadena o un tipo primitivo que se proporciona sin ningún proceso de tiempo de ejecución

de lo contrario es solo variable final (no cambiable),

El uso constante es siempre más rápido que la referencia.

así que si es posible, use constantes en cualquier lenguaje de programación para un mejor rendimiento

En Java vemos muchos lugares donde se puede usar la palabra clave final pero su uso es poco común.

Por ejemplo:

String str = "abc"; System.out.println(str);

En el caso anterior, str puede ser final pero esto generalmente se deja de lado.

Cuando un método nunca se anulará, podemos usar la palabra clave final. Del mismo modo en el caso de una clase que no va a ser heredada.

¿El uso de la palabra clave final en alguno o todos estos casos realmente mejora el rendimiento? Si es así, entonces ¿cómo? Por favor explique. Si el uso correcto de final realmente importa para el rendimiento, ¿qué hábitos debe desarrollar un programador de Java para hacer un mejor uso de la palabra clave?


En realidad, al probar algunos códigos relacionados con OpenGL, descubrí que el uso del modificador final en un campo privado puede degradar el rendimiento. Aquí está el comienzo de la clase que probé:

public class ShaderInput { private /* final */ float[] input; private /* final */ int[] strides; public ShaderInput() { this.input = new float[10]; this.strides = new int[] { 0, 4, 8 }; } public ShaderInput x(int stride, float val) { input[strides[stride] + 0] = val; return this; } // more stuff ...

Y este es el método que utilicé para probar el rendimiento de varias alternativas, entre las que se encuentra la clase ShaderInput:

public static void test4() { int arraySize = 10; float[] fb = new float[arraySize]; for (int i = 0; i < arraySize; i++) { fb[i] = random.nextFloat(); } int times = 1000000000; for (int i = 0; i < 10; ++i) { floatVectorTest(times, fb); arrayCopyTest(times, fb); shaderInputTest(times, fb); directFloatArrayTest(times, fb); System.out.println(); System.gc(); } }

Después de la tercera iteración, con la VM calentada, obtuve estas cifras de manera consistente sin la palabra clave final:

Simple array copy took : 02.64 System.arrayCopy took : 03.20 ShaderInput took : 00.77 Unsafe float array took : 05.47

Con la palabra clave final:

Simple array copy took : 02.66 System.arrayCopy took : 03.20 ShaderInput took : 02.59 Unsafe float array took : 06.24

Tenga en cuenta las cifras de la prueba ShaderInput.

No importaba si los campos eran públicos o privados.

Por cierto, hay algunas cosas más desconcertantes. La clase ShaderInput supera todas las demás variantes, incluso con la palabra clave final. Esto es notable porque básicamente es una clase que envuelve una matriz flotante, mientras que las otras pruebas manipulan directamente la matriz. Tengo que resolver esto. Puede tener algo que ver con la interfaz fluida de ShaderInput.

Además, al parecer, System.arrayCopy en realidad es algo más lento para arreglos pequeños que simplemente copiar elementos de un arreglo a otro en un bucle for. Y el uso de sun.misc.Unsafe (así como también de java.nio.FloatBuffer directo, que no se muestra aquí) se realiza de manera abismal.


Me sorprende que nadie haya publicado realmente algún código real que se haya descompilado para demostrar que existe al menos una pequeña diferencia.

Para la referencia esto ha sido probado contra javac versión 8 , 9 y 10 .

Supongamos que este método:

public static int test() { /* final */ Object left = new Object(); Object right = new Object(); return left.hashCode() + right.hashCode(); }

Al compilar este código tal como es, produce exactamente el mismo código de byte que cuando final hubiera estado presente ( final Object left = new Object(); ).

Pero este:

public static int test() { /* final */ int left = 11; int right = 12; return left + right; }

Produce:

0: bipush 11 2: istore_0 3: bipush 12 5: istore_1 6: iload_0 7: iload_1 8: iadd 9: ireturn

Dejar final para estar presente produce:

0: bipush 12 2: istore_1 3: bipush 11 5: iload_1 6: iadd 7: ireturn

El código es bastante autoexplicativo, en caso de que haya una constante de tiempo de compilación, se cargará directamente en la pila de operandos (no se almacenará en la matriz de variables locales como lo hace el ejemplo anterior a través de bipush 12; istore_0; iload_0 ) - Lo que tiene sentido ya que nadie puede cambiarlo.

Por otro lado, ¿por qué en el segundo caso el compilador no produce istore_0 ... iload_0 está fuera de mi istore_0 ... iload_0 , no es como si la ranura 0 se usara de alguna manera (podría reducir la matriz de variables de esta manera, pero podría faltar algo) detalles internos, no puedo decir con seguridad)

Me sorprendió ver tal optimización, considerando lo pequeños que hace javac . ¿En cuanto a siempre debemos usar final ? Ni siquiera voy a escribir una prueba JMH (lo que inicialmente quería), estoy seguro de que la diferencia está en el orden de ns (si es posible que se capture). El único lugar en el que podría ser un problema es cuando un método no puede estar en línea debido a su tamaño (y la declaración final reduciría ese tamaño en unos pocos bytes).

Hay dos final más que deben abordarse. Primero, cuando un método es final (desde una perspectiva JIT ), dicho método es monomórfico , y estos son los más queridos por la JVM .

Luego están las variables de instancia final (que se deben establecer en cada constructor); Estos son importantes ya que garantizarán una referencia correctamente publicada, tal como se menciona un poco aquí y también se especifica exactamente por el JLS .


No soy un experto, pero supongo que debería agregar final palabra clave final a la clase o el método si no se va a sobrescribir y dejar solo las variables. Si hay alguna forma de optimizar tales cosas, el compilador lo hará por usted.


Realmente estás preguntando sobre dos (al menos) casos diferentes:

  1. final para variables locales
  2. final para metodos / clases

Jon Skeet ya ha respondido 2). Alrededor de 1):

No creo que haga una diferencia; para las variables locales, el compilador puede deducir si la variable es final o no (simplemente verificando si está asignada más de una vez). Entonces, si el compilador deseaba optimizar las variables que solo se asignan una vez, puede hacerlo sin importar si la variable se declara final o no.

final podría hacer una diferencia para los campos de clases protegidas / públicas; allí es muy difícil para el compilador averiguar si el campo se está configurando más de una vez, ya que podría suceder desde una clase diferente (que puede que ni siquiera se haya cargado). Pero incluso entonces la JVM podría usar la técnica que describe Jon (optimizar de manera optimista, revertir si se carga una clase que cambia el campo).

En resumen, no veo ninguna razón por la que debería ayudar al rendimiento. Por lo tanto, es poco probable que este tipo de microoptimización ayude. Puedes intentar hacer una evaluación comparativa para asegurarte, pero dudo que haga una diferencia.

Editar:

En realidad, según la respuesta de Timo Westkämper, la versión final puede mejorar el rendimiento de los campos de clase en algunos casos. Me quedo corregido.


Respuesta corta: no te preocupes por eso!

Respuesta larga:

Cuando se habla de las variables locales finales, tenga en cuenta que el uso de la palabra clave final ayudará al compilador a optimizar el código de forma estática , lo que puede resultar en un código más rápido. Por ejemplo, las cadenas finales a + b en el siguiente ejemplo están concatenadas estáticamente (en tiempo de compilación).

public class FinalTest { public static final int N_ITERATIONS = 1000000; public static String testFinal() { final String a = "a"; final String b = "b"; return a + b; } public static String testNonFinal() { String a = "a"; String b = "b"; return a + b; } public static void main(String[] args) { long tStart, tElapsed; tStart = System.currentTimeMillis(); for (int i = 0; i < N_ITERATIONS; i++) testFinal(); tElapsed = System.currentTimeMillis() - tStart; System.out.println("Method with finals took " + tElapsed + " ms"); tStart = System.currentTimeMillis(); for (int i = 0; i < N_ITERATIONS; i++) testNonFinal(); tElapsed = System.currentTimeMillis() - tStart; System.out.println("Method without finals took " + tElapsed + " ms"); } }

¿El resultado?

Method with finals took 5 ms Method without finals took 273 ms

Probado en Java Hotspot VM 1.7.0_45-b18.

Entonces, ¿cuánto es la mejora del rendimiento real? No me atrevo a decir. En la mayoría de los casos, probablemente sea marginal (~ 270 nanosegundos en esta prueba sintética porque la concatenación de cadenas se evita por completo, un caso raro), pero en un código de utilidad altamente optimizado podría ser un factor. En cualquier caso, la respuesta a la pregunta original es sí, podría mejorar el rendimiento, pero marginalmente en el mejor de los casos .

Aparte de los beneficios de tiempo de compilación, no pude encontrar ninguna evidencia de que el uso de la palabra clave final tenga un efecto medible en el rendimiento.



Usualmente no. Para los métodos virtuales, HotSpot realiza un seguimiento de si el método realmente se ha anulado, y es capaz de realizar optimizaciones, como la inclusión en el supuesto de que un método no se ha anulado, hasta que carga una clase que anula el método, en qué momento puede deshacer (o deshacer parcialmente) esas optimizaciones.

(Por supuesto, esto es asumiendo que estás usando HotSpot, pero es la JVM más común, así que ...)

En mi opinión, debería usar final basado en un diseño claro y legibilidad en lugar de por razones de rendimiento. Si desea cambiar cualquier cosa por razones de rendimiento, debe realizar las mediciones adecuadas antes de doblar el código más claro fuera de forma; de esa manera, puede decidir si cualquier rendimiento adicional alcanzado vale la pena por una menor legibilidad / diseño. (En mi experiencia, casi nunca vale la pena; YMMV.)

EDITAR: Como se mencionaron los campos finales, vale la pena mencionar que a menudo son una buena idea, en términos de diseño claro. También cambian el comportamiento garantizado en términos de visibilidad de subprocesos: después de que un constructor haya completado, se garantiza que todos los campos finales serán visibles en otros subprocesos inmediatamente. Este es probablemente el uso más común de final en mi experiencia, aunque como partidario de la regla de oro del "diseño para heredar o prohibirlo" de Josh Bloch, probablemente debería usar final más a menudo para las clases ...


final palabra clave final se puede utilizar de cinco maneras en Java.

  1. Una clase es final
  2. Una variable de referencia es final
  3. Una variable local es final
  4. Un metodo es definitivo

Una clase es final: una clase es final significa que no podemos ampliarnos o herencia significa que la herencia no es posible.

De manera similar, un objeto es final: en algún momento no modificamos el estado interno del objeto, por lo que, en ese caso, podemos especificar que el objeto es final object.object final significa que no es variable sino también final.

Una vez que la variable de referencia se convierte en final, no se puede reasignar a otro objeto. Pero puede cambiar el contenido del objeto siempre que sus campos no sean finales


Los miembros declarados con final estarán disponibles en todo el programa porque a diferencia de los no finales, si estos miembros no se han utilizado en el programa, Garbage Collector no los cuidará, por lo que puede causar problemas de rendimiento debido a una mala gestión de la memoria.


Nota: No es un experto en java

Si recuerdo mi Java correctamente, habría muy poca manera de mejorar el rendimiento con la palabra clave final. Siempre he sabido que existe por "buen código": diseño y legibilidad.


Sí puede. Aquí hay una instancia donde la final puede aumentar el rendimiento:

La compilación condicional es una técnica en la que las líneas de código no se compilan en el archivo de clase según una condición particular. Esto se puede usar para eliminar toneladas de código de depuración en una compilación de producción.

considera lo siguiente:

public class ConditionalCompile { private final static boolean doSomething= false; if (doSomething) { // do first part. } if (doSomething) { // do second part. } if (doSomething) { // do third part. } if (doSomething) { // do finalization part. } }

Al convertir el atributo doSomething en un atributo final, le ha dicho al compilador que cada vez que vea doSomething, debería reemplazarlo por false según las reglas de sustitución en tiempo de compilación. La primera pasada del compilador cambia el código a algo como esto:

public class ConditionalCompile { private final static boolean doSomething= false; if (false){ // do first part. } if (false){ // do second part. } if (false){ // do third part. } if (false){ // do finalization part. } }

Una vez hecho esto, el compilador lo examina nuevamente y ve que hay declaraciones inalcanzables en el código. Ya que está trabajando con un compilador de alta calidad, no le gustan todos esos códigos de bytes inalcanzables. Así que los elimina, y terminas con esto:

public class ConditionalCompile { private final static boolean doSomething= false; public static void someMethodBetter( ) { // do first part. // do second part. // do third part. // do finalization part. } }

reduciendo así cualquier código excesivo, o cualquier comprobación condicional innecesaria.

Edición: Como ejemplo, tomemos el siguiente código:

public class Test { public static final void main(String[] args) { boolean x = false; if (x) { System.out.println("x"); } final boolean y = false; if (y) { System.out.println("y"); } if (false) { System.out.println("z"); } } }

Al compilar este código con Java 8 y descompilar con javap -c Test.class obtenemos:

public class Test { public Test(); Code: 0: aload_0 1: invokespecial #8 // Method java/lang/Object."<init>":()V 4: return public static final void main(java.lang.String[]); Code: 0: iconst_0 1: istore_1 2: iload_1 3: ifeq 14 6: getstatic #16 // Field java/lang/System.out:Ljava/io/PrintStream; 9: ldc #22 // String x 11: invokevirtual #24 // Method java/io/PrintStream.println:(Ljava/lang/String;)V 14: iconst_0 15: istore_2 16: return }

Podemos observar que el código compilado incluye solo la variable no final x . Esto demuestra que las variables finales tienen impacto en los rendimientos, al menos para este caso simple.