3

Recently, I'm reading Java Source Code, e.g. ArrayList, ArrayDeque, LinkedList, etc. I found when they want to use some class field in class method, they will always declare a final local variable which equals to the field. However, if I haven't read the source code, I will never consider doing this thing. Is it a good practice to do so? Or why does Java Source Code choose to do so? What's the advantage, like the performance?

Reference:

LinkedList source code

ArrayList source code

ArrayDeque source code

Example: getFirst() in LinkedList

public E getFirst() {
    final Node<E> f = first;
    if (f == null)
        throw new NoSuchElementException();
    return f.item;
}

boolean delete(int i) in ArrayDeque

    private boolean delete(int i) {
        checkInvariants();
        final Object[] elements = this.elements;
        final int mask = elements.length - 1;
        final int h = head;
        final int t = tail;
        final int front = (i - h) & mask;
        final int back  = (t - i) & mask;

        // Invariant: head <= i < tail mod circularity
        if (front >= ((t - h) & mask))
            throw new ConcurrentModificationException();

        // Optimize for least element motion
        if (front < back) {
            if (h <= i) {
                System.arraycopy(elements, h, elements, h + 1, front);
            } else { // Wrap around
                System.arraycopy(elements, 0, elements, 1, i);
                elements[0] = elements[mask];
                System.arraycopy(elements, h, elements, h + 1, mask - h);
            }
            elements[h] = null;
            head = (h + 1) & mask;
            return false;
        } else {
            if (i < t) { // Copy the null tail as well
                System.arraycopy(elements, i + 1, elements, i, back);
                tail = t - 1;
            } else { // Wrap around
                System.arraycopy(elements, i + 1, elements, i, mask - i);
                elements[mask] = elements[0];
                System.arraycopy(elements, 1, elements, 0, t);
                tail = (t - 1) & mask;
            }
            return true;
        }
    }
maplemaple
  • 1,297
  • 6
  • 24
  • While I don’t know the answer, I am speculating that it may be an optimization trick that you make in methods that are called millions of times in each of millions of programs, not something to do in ordinary code. It may also be that the trick was good for some Java version once and that the compiler can do it for us today so we no longer need. So I wouldn’t bother caring. I coded Java since 1998 and never discovered any need or even reason to do this. – Ole V.V. Jun 19 '21 at 04:51
  • Use `final` anywhere you can. Non-abstract classes should be `final` unless you have a good reason not to, methods should be `final` unless you specifically plan to allow overriding, instance and local variables should be `final` unless you're going to change them. On classes and functions, it provides a powerful guarantee to users and to the JVM, and on local variables it makes it clear to anyone reading the code how it's used. Newer languages (Rust, Scala, Kotlin, etc.) realized this and made `final` the default – Silvio Mayolo Jun 19 '21 at 22:35
  • @SilvioMayolo That’s an opinion. The code guidelines for my previous project said something similar. According to the code guidelines for my current project I am not allowed to do as you say. – Ole V.V. Jun 20 '21 at 07:04

1 Answers1

1

This is a micro-optimisation which probably makes sense for standard library code which is going to be used by lots and lots of people and read by significantly fewer people. I wouldn't recommend doing this in your own code unless performance is a real concern and you've profiled your application to determine which parts of the code are the real bottlenecks, since otherwise the performance gain is likely to be negligible, so the main effect will be to make your code more verbose.

Using local variables should have some very minor performance benefits, because:

  • Local variables will be allocated either on the stack or in registers, which are faster to access than other memory, so doing only a single field access from the heap definitely minimises the number of slower accesses (at best, accessing memory on the heap can only be as fast as accessing a local variable, but at worst it can be slower).
  • The bytecode instructions for accessing local variables are shorter. The first four local variables can be loaded using aload0, aload1, aload2 and aload3, which are a single byte each, and aload is two bytes including an index to the local variable to be loaded. In comparison, getfield takes three bytes, two of which are used to reference the field being accessed.

There is also potentially a semantic difference in non-synchronised methods, since the local variable will hold the same value even if the field is changed by another thread during the method execution. However, most of the standard library classes you'll see this pattern in are not meant to be thread-safe, and even if the class is meant for concurrency, doing this doesn't necessarily make the method thread-safe anyway.

kaya3
  • 47,440
  • 4
  • 68
  • 97