Private static members in sealed-interfaces?
Brian Goetz
brian.goetz at oracle.com
Mon Jun 23 16:03:00 UTC 2025
OK, good. SO the good news is that we agree that the current asymmetric
treatment of private methods-but-not-other-members in interfaces should
be fixed. The bad news is that it is not anywhere near the top of
anyone's priority list :(
On 6/23/2025 11:12 AM, Swaranga Sarma wrote:
> Ah, yes. When you said members, I was thinking of non-static instance
> fields. Yes, this is all 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.
> Thank you.
>
> > 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.)
> Yes, a much better way to say what I was trying to emphasize with my
> example using the sealed classes hierarchy.
>
> Regards
> Swaranga
>
>
> On Mon, Jun 23, 2025 at 5:19 AM Brian Goetz <brian.goetz at oracle.com>
> wrote:
>
> 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/738c1d34/attachment-0001.htm>
More information about the amber-dev
mailing list