Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
886 views
in Technique[技术] by (71.8m points)

pass by reference - Why should I use the keyword "final" on a method parameter in Java?

I can't understand where the final keyword is really handy when it is used on method parameters.

If we exclude the usage of anonymous classes, readability and intent declaration then it seems almost worthless to me.

Enforcing that some data remains constant is not as strong as it seems.

  • If the parameter is a primitive then it will have no effect since the parameter is passed to the method as a value and changing it will have no effect outside the scope.

  • If we are passing a parameter by reference, then the reference itself is a local variable and if the reference is changed from within the method, that would not have any effect from outside of the method scope.

Consider the simple test example below. This test passes although the method changed the value of the reference given to it, it has no effect.

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;
}
Question&Answers:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

Stop a Variable’s Reassignment

While these answers are intellectually interesting, I've not read the short simple answer:

Use the keyword final when you want the compiler to prevent a variable from being re-assigned to a different object.

Whether the variable is a static variable, member variable, local variable, or argument/parameter variable, the effect is entirely the same.

Example

Let’s see the effect in action.

Consider this simple method, where the two variables (arg and x) can both be re-assigned different objects.

// 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.
}

Mark the local variable as final. This results in a compiler error.

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";  
}

Instead, let’s mark the parameter variable as final. This too results in a compiler error.

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.
}

Moral of the story:

If you want to ensure a variable always points to the same object, mark the variable final.

Never Reassign Arguments

As good programming practice (in any language), you should never re-assign a parameter/argument variable to an object other than the object passed by the calling method. In the examples above, one should never write the line arg = . Since humans make mistakes, and programmers are human, let’s ask the compiler to assist us. Mark every parameter/argument variable as 'final' so that the compiler may find and flag any such re-assignments.

In Retrospect

As noted in other answers… Given Java's original design goal of helping programmers to avoid dumb mistakes such as reading past the end of an array, Java should have been designed to automatically enforce all parameter/argument variables as 'final'. In other words, Arguments should not be variables. But hindsight is 20/20 vision, and the Java designers had their hands full at the time.

So, always add final to all arguments?

Should we add final to each and every method parameter being declared?

  • In theory, yes.
  • In practice, no.
    ? Add final only when the method’s code is long or complicated, where the argument may be mistaken for a local or member variable and possibly re-assigned.

If you buy into the practice of never re-assigning an argument, you will be inclined to add a final to each. But this is tedious and makes the declaration a bit harder to read.

For short simple code where the argument is obviously an argument, and not a local variable nor a member variable, I do not bother adding the final. If the code is quite obvious, with no chance of me nor any other programmer doing maintenance or refactoring accidentally mistaking the argument variable as something other than an argument, then don’t bother. In my own work, I add final only in longer or more involved code where an argument might mistaken for a local or member variable.

#Another case added for the completeness

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
 }

record

Java 16 brings the new records feature. A record is a very brief way to define a class whose central purpose is to merely carry data, immutably and transparently.

You simply declare the class name along with the names and types of its member fields. The compiler implicitly provides the constructor, getters, equals & hashCode, and toString.

The fields are read-only, with no setters. So a record is one case where there is no need to mark the arguments final. They are already effectively final. Indeed, the compiler forbids using final when declaring the fields of a record.

public record Employee( String name , LocalDate whenHired )  // ?? Marking `final` here is *not* allowed.
{
}

If you provide an optional constructor, there you can mark final.

public record Employee(String name , LocalDate whenHired)  // ?? Marking `final` here is *not* allowed.
{
    public Employee ( final String name , final LocalDate whenHired )  // ?? Marking `final` here *is* allowed.
    {
        this.name = name;
        whenHired = LocalDate.MIN;  // ?? Compiler error, because of `final`. 
        this.whenHired = whenHired;
    }
}

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...