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> V
get
(int depth) Retrieve the value previously stashed bypush
at positiondepth
down from the stop of the stack.static <V> V
get
(int depth, V value) Retrieve the value previously stashed bypush
at stack depthdepth
, 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 bypush
, but only if the given field value is null, indicating the field is still uninitialized.static void
pop()
Discard any value previously saved bypush
in the current thread.static <P> P
Stash 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 bypush
at positiondepth
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 depthdepth
IllegalArgumentException
- ifdepth
is 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
value
is 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:
value
if non-null, otherwise the non-null value previously stashed bypush()
- Throws:
IllegalStateException
- ifvalue
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 bypush
at stack depthdepth
, but only if the given field value is null, indicating the field is still uninitialized.If
value
is 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:
value
if non-null, otherwise the non-null value previously stashed bypush()
- Throws:
IllegalArgumentException
- ifdepth
is negativeIllegalStateException
- ifvalue
is null and there is no value at depthdepth
currently stashed by the current thread
-
pop
public static void pop()Discard any value previously saved bypush
in the current thread.- Throws:
IllegalStateException
- if there is no value currently stashed by the current thread
-