Private static members in sealed-interfaces?
Brian Goetz
brian.goetz at oracle.com
Mon Jun 23 12:19:46 UTC 2025
Interfaces are allowed to have (public) fields today, but they are
always static. So we're just talking about access control.
My point is that I think the anomaly you are really raising is that
access control for interface members is not uniform, but without a clear
justification other than history. Private _methods_ were introduced
into interfaces in Java 8 (to go with default methods), but there's no
good reason why private fields (interfaces already have fields, they are
static) or private nested classes did not get the same treatment. This
is a topic for potential cleanup at some point.
But, if I understand your clarification, you are saying: "sealed classes
let me treat all the implementations of a type as being a single big
implementation, and sometimes these might want to share state, and the
sealed class is a sensible place to put state shared across all
implementations"? (And then taking advantage of the nestmate status of
co-declared classes, so that "private" isn't really private to the
interface, but private to the source file.)
On 6/23/2025 8:04 AM, Swaranga Sarma wrote:
> Hi Brian, I am afraid I am not seeing how I am making a case for
> private members in interfaces. That would be introducing state into an
> interface and seems pretty large change to the language spec.
>
> As for the suggestion, yes, the post does make it seem like it is
> specific to sealed interfaces but wasn’t intended as such. I was using
> it as a real example from my work which uses a sealed hierarchy.
> Although now that I think about it, for regular interfaces I don’t see
> how it would be helpful if these fields are private to the interface.
>
> I have felt its need more in sealed interfaces where I want to share
> common static fields and methods across the sub interfaces/records.
>
> Regards
> Swaranga
>
>
> On Mon, Jun 23, 2025 at 4:50 AM Brian Goetz <brian.goetz at oracle.com>
> wrote:
>
> What I see here is an argument for private members in interfaces,
> but this has nothing to do with sealed interfaces. Why is sealing
> relevant to this suggestion?
>
>
> On 6/22/2025 11:18 PM, Swaranga Sarma wrote:
>> Traditionally Java has not allowed private static members inside
>> of an interface but now with sealed interfaces, I am wondering if
>> there is a case to be made. Here is a real-world use-case where I
>> felt this was needed recently.
>>
>> I have a sealed interface hierarchy for a simple state machine
>> transition result like below:
>>
>> ```
>> sealed interface TransitionCheckResult {
>> sealed interface BootstrapTransitionCheckResult
>> extends TransitionCheckResult {
>> record AllowedTransition(String
>> destinationStateId, Map<String, String> metadata) {}
>> record DisallowedTransition(Map<String, String> metadata) {}
>> }
>>
>> sealed interface IntermediateTransitionCheckResult
>> extends TransitionCheckResult {
>> record AllowedTransition(String destinationStateId,
>> Map<String, String> metadata,
>> List<Actions> cleanupActions) {}
>> record DisallowedTransition(Map<String, String> metadata) {}
>> }
>> }
>> ```
>>
>> Basically the "transition check result" is broken up into a
>> bootstrap transition check result for the initial state
>> transition check and an "intermediary transition check result"
>> for an intermediate node in the state machine each of which have
>> their allowed and disallowed transitions with slightly varying
>> attributes. So a sealed interface with records seemed to fit the
>> bill here.
>>
>> Now for the destinationStateId, there are some constraints like
>> length and Pattern (among others). I wanted to add a private
>> static final Pattern variable in the TransitionCheckResult
>> interface which I then use to validate the passed in
>> destinationStateId. But unfortunately, this is not allowed. I
>> think I have the following options none of which seem great:
>> 1. Make the Pattern public; no-body else needs access to the
>> Pattern field so this feels unnecessary.
>> 2. Move the pattern field to inside the record class - this would
>> work but I have two records that require the same validation and
>> either I have to repeat myself or add the pattern to one of them
>> and reference it in the other. Again, feels like I shouldn't have
>> to introduce this otherwise unnecessary dependency.
>> 3. Create a DestinationStateId record where I can fully
>> encapsulate the validation in one place and change
>> the AllowedTransition record declarations to use this record.
>> This feels like the best approach but then in another part of the
>> app, I lose the ability to do switch on the destinationStateId
>> like this:
>>
>> ```
>> static final RESOLVED_STATE_ID = new PolicyStateId("resolved);
>> PolicyStateId stateId = ...;
>> switch(stateId.name()) {
>> case RESOLVED_STATE_ID.name() -> ... // not allowed
>> ...
>> }
>> ```
>> There are slight variations of these approaches (like using
>> a when clause to address the switch problem or using a static
>> inner Validator class inside the top-level sealed interface) but
>> it feels verbose.
>>
>> Thoughts? Especially now with sealed interfaces and records,
>> there could be more cases where this could be useful.
>>
>> Regards
>> Swaranga
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20250623/c0f692a1/attachment-0001.htm>
More information about the amber-dev
mailing list