how - java parameter reference or value
¿Por qué debería usar la palabra clave "final" en un parámetro de método en Java? (12)
No puedo entender dónde está realmente útil la palabra clave final
cuando se utiliza en los parámetros del método.
Si excluimos el uso de clases anónimas, la legibilidad y la declaración de intenciones, entonces me parece casi inútil.
Hacer que algunos datos permanezcan constantes no es tan fuerte como parece.
Si el parámetro es primitivo, no tendrá ningún efecto, ya que el parámetro se pasa al método como un valor y cambiarlo no tendrá ningún efecto fuera del alcance.
Si estamos pasando un parámetro por referencia, entonces la referencia en sí misma es una variable local y si la referencia se cambia desde dentro del método, eso no tendría ningún efecto desde fuera del alcance del método.
Considere el ejemplo de prueba simple a continuación. Esta prueba pasa aunque el método cambió el valor de la referencia que se le dio, no tiene ningún efecto.
public void testNullify() {
Collection<Integer> c = new ArrayList<Integer>();
nullify(c);
assertNotNull(c);
final Collection<Integer> c1 = c;
assertTrue(c1.equals(c));
change(c);
assertTrue(c1.equals(c));
}
private void change(Collection<Integer> c) {
c = new ArrayList<Integer>();
}
public void nullify(Collection<?> t) {
t = null;
}
Detener la reasignación de una variable
Si bien estas respuestas son intelectualmente interesantes, no he leído la respuesta corta y simple:
Use la palabra clave final cuando desee que el compilador impida que una variable se reasigne a un objeto diferente.
Si la variable es una variable estática, variable miembro, variable local o variable de argumento / parámetro, el efecto es totalmente el mismo.
Ejemplo
Veamos el efecto en acción.
Considere este método simple, donde las dos variables ( arg y x ) pueden ser reasignadas a diferentes objetos.
// Example use of this method:
// this.doSomething( "tiger" );
void doSomething( String arg ) {
String x = arg; // Both variables now point to the same String object.
x = "elephant"; // This variable now points to a different String object.
arg = "giraffe"; // Ditto. Now neither variable points to the original passed String.
}
Marque la variable local como final . Esto resulta en un error de compilación.
void doSomething( String arg ) {
final String x = arg; // Mark variable as ''final''.
x = "elephant"; // Compiler error: The final local variable x cannot be assigned.
arg = "giraffe";
}
En su lugar, vamos a marcar la variable de parámetro como final . Esto también resulta en un error del compilador.
void doSomething( final String arg ) { // Mark argument as ''final''.
String x = arg;
x = "elephant";
arg = "giraffe"; // Compiler error: The passed argument variable arg cannot be re-assigned to another object.
}
Moraleja de la historia:
Si desea asegurarse de que una variable siempre apunta al mismo objeto, marque la variable final .
Nunca reasignar argumentos
Como buena práctica de programación (en cualquier lenguaje), nunca debe volver a asignar una variable de parámetro / argumento a un objeto que no sea el objeto pasado por el método de llamada. En los ejemplos anteriores, uno nunca debe escribir la línea arg =
. Como los humanos cometen errores y los programadores son humanos, pidamos al compilador que nos ayude. Marque cada variable de parámetro / argumento como ''final'' para que el compilador pueda encontrar y marcar cualquiera de estas reasignaciones.
En retrospectiva
Como se señaló en otras respuestas ... Dado el objetivo de diseño original de Java de ayudar a los programadores a evitar errores tontos, como leer más allá del final de una matriz, Java debería haber sido diseñado para imponer automáticamente todas las variables de parámetro / argumento como "final". En otras palabras, los argumentos no deben ser variables . Pero la visión retrospectiva es una visión 20/20, y los diseñadores de Java estaban muy ocupados en ese momento.
Otro caso agregado para la integridad.
public class MyClass {
private int x;
//getters and setters
}
void doSomething( final MyClass arg ) { // Mark argument as ''final''.
arg = new MyClass(); // Compiler error: The passed argument variable arg cannot be re-assigned to another object.
arg.setX(20); // allowed
// We can re-assign properties of argument which is marked as final
}
A veces es bueno ser explícito (para facilitar la lectura) que la variable no cambia. Aquí hay un ejemplo simple donde usar final puede ahorrar algunos posibles dolores de cabeza
public void setTest(String test) {
test = test;
}
si olvida la palabra clave ''this'' en un setter, la variable que desea configurar no se establece. Sin embargo, si utilizó la palabra clave final en el parámetro, el error se detectaría en el momento de la compilación.
Déjame explicarte un poco sobre el único caso en el que tienes que usar final, que Jon ya mencionó:
Si creas una clase interna anónima en tu método y usas una variable local (como un parámetro de método) dentro de esa clase, el compilador te obliga a hacer que el parámetro sea final:
public Iterator<Integer> createIntegerIterator(final int from, final int to)
{
return new Iterator<Integer>(){
int index = from;
public Integer next()
{
return index++;
}
public boolean hasNext()
{
return index <= to;
}
// remove method omitted
};
}
Aquí los parámetros from
y to
deben ser finales para que puedan usarse dentro de la clase anónima.
El motivo de este requisito es el siguiente: las variables locales viven en la pila, por lo tanto, solo existen mientras se ejecuta el método. Sin embargo, la instancia de clase anónima se devuelve desde el método, por lo que puede durar mucho más tiempo. No puede conservar la pila, porque es necesaria para las llamadas de método posteriores.
Entonces, lo que hace Java en su lugar es poner copias de esas variables locales como variables de instancia ocultas en la clase anónima (puede verlas si examina el código de byte). Pero si no fueran definitivos, uno podría esperar que la clase anónima y el método al ver los cambios que la otra le hace a la variable. Para mantener la ilusión de que solo hay una variable en lugar de dos copias, tiene que ser definitiva.
El uso de final en un parámetro de método no tiene nada que ver con lo que sucede con el argumento en el lado de la persona que llama. Solo tiene la intención de marcarlo como que no cambia dentro de ese método. Cuando trato de adoptar un estilo de programación más funcional, veo el valor en eso.
Nunca uso final en una lista de parámetros, solo agrega el desorden como los encuestados anteriores han dicho. También en Eclipse puede configurar la asignación de parámetros para generar un error, por lo que usar final en una lista de parámetros me parece bastante redundante. Curiosamente, cuando habilité la configuración de Eclipse para la asignación de parámetros que generó un error, detecté este código (así es como recuerdo el flujo, no el código real):
private String getString(String A, int i, String B, String C)
{
if (i > 0)
A += B;
if (i > 100)
A += C;
return A;
}
Jugando al abogado del diablo, ¿qué es exactamente lo que está mal al hacer esto?
Personalmente no uso final en los parámetros del método, porque agrega demasiado desorden a las listas de parámetros. Prefiero imponer que los parámetros del método no se cambian a través de algo como Checkstyle.
Para las variables locales que uso final siempre que sea posible, incluso dejo que Eclipse lo haga automáticamente en mi configuración para proyectos personales.
Ciertamente me gustaría algo más fuerte como C / C ++ const.
Respuesta corta: la versión final
ayuda un poquito pero, en su lugar, use programación defensiva en el lado del cliente.
De hecho, el problema con final
es que solo impone que la referencia no se modifique, lo que permite con alegría que los miembros del objeto al que se hace referencia sean mutados, sin que el autor de la llamada lo sepa. Por lo tanto, la mejor práctica a este respecto es la programación defensiva en el lado de la persona que llama, creando instancias profundamente inmutables o copias profundas de objetos que están en peligro de ser asaltados por API sin escrúpulos.
Sí, excluyendo clases anónimas, legibilidad y declaración de intenciones es casi inútil. ¿Son esas tres cosas sin valor sin embargo?
Personalmente, tiendo a no usar final
para variables y parámetros locales a menos que use la variable en una clase interna anónima, pero ciertamente puedo ver el punto de aquellos que quieren dejar en claro que el valor del parámetro en sí no cambiará ( incluso si el objeto al que se refiere cambia su contenido). Para aquellos que descubren que eso se suma a la legibilidad, creo que es algo completamente razonable.
Su punto sería más importante si alguien realmente afirmara que mantuvo los datos constantes de una manera que no lo hace, pero no recuerdo haber visto tales afirmaciones. ¿Estás sugiriendo que hay un grupo significativo de desarrolladores que sugieren que final
tiene más efecto que lo que realmente tiene?
EDITAR: Realmente debería haber resumido todo esto con una referencia de Monty Python; la pregunta parece algo similar a preguntar "¿Qué han hecho los romanos por nosotros?"
Seguimiento por lo que la publicación de Michel. Yo mismo me hice otro ejemplo para explicarlo. Espero que pueda ayudar.
public static void main(String[] args){
MyParam myParam = thisIsWhy(new MyObj());
myParam.setArgNewName();
System.out.println(myParam.showObjName());
}
public static MyParam thisIsWhy(final MyObj obj){
MyParam myParam = new MyParam() {
@Override
public void setArgNewName() {
obj.name = "afterSet";
}
@Override
public String showObjName(){
return obj.name;
}
};
return myParam;
}
public static class MyObj{
String name = "beforeSet";
public MyObj() {
}
}
public abstract static class MyParam{
public abstract void setArgNewName();
public abstract String showObjName();
}
Del código anterior, en el método thisIsWhy () , en realidad no asignamos el [argumento MyObj obj] a una referencia real en MyParam. En su lugar, solo usamos el [argumento MyObj obj] en el método dentro de MyParam.
Pero después de que terminemos el método thisIsWhy () , ¿ debería seguir existiendo el argumento (objeto) MyObj?
Parece que debería, porque podemos ver en main que aún llamamos el método showObjName () y necesitamos llegar a obj . ¡MyParam todavía usará / llegará al argumento del método, incluso el método ya fue devuelto!
Cómo Java realmente logra esto es generar una copia también es una referencia oculta del argumento MyObj obj dentro del objeto MyParam (pero no es un campo formal en MyParam, por lo que no podemos verlo)
Como llamamos "showObjName", usará esa referencia para obtener el valor correspondiente.
Pero si no pusimos el argumento final, lo que lleva a una situación, podemos reasignar una nueva memoria (objeto) al argumento MyObj obj .
Técnicamente no hay ningún choque en absoluto! Si se nos permite hacer eso, a continuación será la situación:
- Ahora tenemos un punto oculto de [MyObj obj] a un [Memoria A en el montón] ahora en el objeto MyParam.
- También tenemos otro [MyObj obj], que es el argumento que apunta a una [Memoria B en el montón] ahora en vivo en este método.
No choque, pero "¡¡CONFUSANDO !!" Porque todos están usando el mismo "nombre de referencia" que es "obj" .
Para evitar esto, configúrelo como "final" para evitar que el programador haga el código "propenso a errores".
Una razón adicional para agregar declaraciones finales a los parámetros es que ayuda a identificar las variables que necesitan ser renombradas como parte de una refactorización "Extraer método". Descubrí que agregar un parámetro final a cada parámetro antes de comenzar una refactorización de método grande rápidamente me dice si hay algún problema que deba resolver antes de continuar.
Sin embargo, generalmente los elimino como superfluos al final de la refactorización.
Uso final todo el tiempo en parámetros.
¿Agrega eso mucho? Realmente no.
¿Lo apagaría? No.
La razón: encontré 3 errores en los que las personas escribieron código descuidado y no lograron establecer una variable miembro en los accesores. Todos los errores resultaron difíciles de encontrar.
Me gustaría ver que esto sea lo predeterminado en una futura versión de Java. La cosa de pasar por valor / referencia dispara a una gran cantidad de programadores junior.
Una cosa más ... mis métodos tienden a tener un bajo número de parámetros, por lo que el texto adicional en una declaración de método no es un problema.
Ya que Java pasa copias de argumentos, siento que la relevancia de los mismos es bastante limitada. Supongo que esto viene de la era de C ++ donde se podría prohibir que se cambie el contenido de referencia haciendo una const char const *
. Siento que este tipo de cosas te hace creer que el desarrollador es un estúpido inherente como un imbécil y necesita estar protegido contra cada personaje que escribe. Con toda humildad debo decir que escribo muy pocos errores, aunque presento la final
menos que no quiera que alguien anule mis métodos y esas cosas ... Tal vez solo soy un desarrollador de la vieja escuela ... O