Class SuperFinal
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.
-
Method Summary
Modifier and TypeMethodDescriptionstatic <V> Vget(int depth) Retrieve the value previously stashed bypushat positiondepthdown from the stop of the stack.static <V> Vget(int depth, V value) Retrieve the value previously stashed bypushat stack depthdepth, but only if the given field value is null, indicating the field is still uninitialized.static <V> Vget(V value) Retrieve the value previously stashed bypush, but only if the given field value is null, indicating the field is still uninitialized.static voidpop()Discard any value previously saved bypushin the current thread.static <P> PStash the given value for later retrieval byget()in the current thread.
-
Method Details
-
push
Stash the given value for later retrieval byget()in the current thread.This method is intended to be used as a parameter within a
this()orsuper()invocation; it returnsparam.Caller should ensure
pop()is invoked to remove the stashed value afterthis()orsuper()returns.- Parameters:
value- the value to stashparam- arbitrary return value- Returns:
param
-
get
public static <V> V get(int depth) Retrieve the value previously stashed bypushat positiondepthdown 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 depthdepthIllegalArgumentException- ifdepthis negative
-
get
public static <V> V get(V value) Retrieve the value previously stashed bypush, but only if the given field value is null, indicating the field is still uninitialized.If
valueis not null, then this method returnsvalue; otherwise, it returns the stashed value as if by invokingget(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:
valueif non-null, otherwise the non-null value previously stashed bypush()- Throws:
IllegalStateException- ifvalueis 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 bypushat stack depthdepth, but only if the given field value is null, indicating the field is still uninitialized.If
valueis not null, then this method returnsvalue; otherwise, it returns the stashed value as if by invokingget(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 valuevalue- the desired value, or null if not yet initialized- Returns:
valueif non-null, otherwise the non-null value previously stashed bypush()- Throws:
IllegalArgumentException- ifdepthis negativeIllegalStateException- ifvalueis null and there is no value at depthdepthcurrently stashed by the current thread
-
pop
public static void pop()Discard any value previously saved bypushin the current thread.- Throws:
IllegalStateException- if there is no value currently stashed by the current thread
-