Class SuperFinal

java.lang.Object
org.dellroad.stuff.java.SuperFinal

public final class SuperFinal extends Object
A hack to workaround a stupid JLS restriction which requires super() be the first statement in a constructor.

The JLS should permit constructors to perform arbitrary non-this operations and assignments to final fields before invoking super(); this is actually what the JVM allows. Because this is currently disallowed, a class that invokes a superclass constructor that invokes an overridden method can't guarantee that its final fields will be actually be initialized before they get used.

For example, the following seemingly straightforward class will throw a NullPointerException if constructed with a non-empty elems, because the superclass constructor invokes this.add() before this.filter is initialized:


 import java.util.*;
 import java.util.function.*;

 public class FilteredSet<E> extends HashSet<E> {

     private final Predicate<? super E> filter;

     public FilteredSet(Predicate<? super E> filter) {
         this.filter = filter;
     }

     // BUG: throws NullPointerException if "elems" is non-empty!
     public FilteredSet(Predicate<? super E> filter, Collection<? extends E> elems) {
         super(elems);
         this.filter = filter;
     }

     @Override
     public boolean add(E elem) {
         if (!this.filter.test(elem))                         // NullPointerException thrown here!
             throw new IllegalArgumentException("disallowed element");
         return super.add(elem);
     }
 }
 

To work around that problem, you could use this class to temporarily "stash" this.filter's value so that it's accessible during the superclass constructor's execution (before the field is initialized) if needed:


 import java.util.*;
 import java.util.function.*;

 public class FilteredSet<E> extends HashSet<E> {

     private final Predicate<? super E> filter;

     public FilteredSet(Predicate<? super E> filter) {
         this.filter = filter;
     }

     public FilteredSet(Predicate<? super E> filter, Collection<? extends E> elems) {
         super(SuperFinal.push(filter, elems));               // stash "filter" value while super() executes
         SuperFinal.pop();                                    // discard stashed value
         this.filter = filter;
     }

     @Override
     public boolean add(E elem) {
         if (!SuperFinal.get(this.filter)).test(elem))        // use stashed value here if needed
             throw new IllegalArgumentException("disallowed element");
         return super.add(elem);
     }
 }
 

Stashed values are stored on a stack, so pop() must be invoked before the constructor returns to keep the stack properly aligned. If multiple superclasses are stash values, things will work as long as only the top-most value is accessed. In more complicated situations, you can specify the desired stack depth explicitly.

See Also:
  • Method Summary

    Modifier and Type
    Method
    Description
    static <V> V
    get(int depth)
    Retrieve the value previously stashed by push at position depth down from the stop of the stack.
    static <V> V
    get(int depth, V value)
    Retrieve the value previously stashed by push at stack depth depth, but only if the given field value is null, indicating the field is still uninitialized.
    static <V> V
    get(V value)
    Retrieve the value previously stashed by push, but only if the given field value is null, indicating the field is still uninitialized.
    static void
    pop()
    Discard any value previously saved by push in the current thread.
    static <P> P
    push(Object value, P param)
    Stash the given value for later retrieval by get() in the current thread.

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Method Details

    • push

      public static <P> P push(Object value, P param)
      Stash the given value for later retrieval by get() in the current thread.

      This method is intended to be used as a parameter within a this() or super() invocation; it returns param.

      Caller should ensure pop() is invoked to remove the stashed value after this() or super() returns.

      Parameters:
      value - the value to stash
      param - arbitrary return value
      Returns:
      param
    • get

      public static <V> V get(int depth)
      Retrieve the value previously stashed by push at position depth down from the stop of the stack.
      Parameters:
      depth - stack depth of desired value
      Returns:
      the value previously stashed by push()
      Throws:
      IllegalStateException - if there is no value currently stashed by the current thread at depth depth
      IllegalArgumentException - if depth is negative
    • get

      public static <V> V get(V value)
      Retrieve the value previously stashed by push, but only if the given field value is null, indicating the field is still uninitialized.

      If value is not null, then this method returns value; otherwise, it returns the stashed value as if by invoking get(0). Therefore, it is safe to invoke this method either before or after a final field's initialization, as long as that field's eventual value is not null.

      This method is equivalent to get(0, value).

      Parameters:
      value - the desired value, or null if not yet initialized
      Returns:
      value if non-null, otherwise the non-null value previously stashed by push()
      Throws:
      IllegalStateException - if value is null and there is no value currently stashed by the current thread
    • get

      public static <V> V get(int depth, V value)
      Retrieve the value previously stashed by push at stack depth depth, but only if the given field value is null, indicating the field is still uninitialized.

      If value is not null, then this method returns value; otherwise, it returns the stashed value as if by invoking get(depth). Therefore, it is safe to invoke this method either before or after a final field's initialization, as long as that field's eventual value is not null.

      Parameters:
      depth - stack depth of desired value
      value - the desired value, or null if not yet initialized
      Returns:
      value if non-null, otherwise the non-null value previously stashed by push()
      Throws:
      IllegalArgumentException - if depth is negative
      IllegalStateException - if value is null and there is no value at depth depth currently stashed by the current thread
    • pop

      public static void pop()
      Discard any value previously saved by push in the current thread.
      Throws:
      IllegalStateException - if there is no value currently stashed by the current thread