Java - make static nested class visible to all, but only constructed by parent and subclasses of parent
Asked Answered
W

2

5

I have a parent class containing a struct-like static nested class. The nested class must be public, as it should be returned to other classes where its contents are acted upon. However, only the parent class and its subclasses should be able to instantiate the nested class, as they know how to define its contents. All classes shown below are in different packages.

public abstract class Parent
{
    public static class Data
    {
        public final String data1
        ...

        public Data(String d1, ...)
        {
            data1 = d1;
            ...
        }
    }

    public abstract Data getData();
}

public final class Subclass extends Parent
{
    @Override
    public Data getData()
    {
        return new Data(.....);
    }
}

public class SomeOtherClass
{
    public void someMethod()
    {
        final Data d = new Subclass().getData();
        System.out.println(d.data1);
    }
}

Declaring the Data class protected would stop getData() from working properly. Decreasing the access modifier on the constructor of Data would prevent subclasses of Parent from working properly. What I want is something like protected-by-parent, which I am guessing does not exist in Java.

Is there a suitable workaround? One possibility I can see is creating a protected method in Parent that effectively mirrors, calls, and returns from the constructor of Data (which would be made private). This however seems a bit messy; does anybody know of a better way/design?

Wailful answered 21/5, 2018 at 8:29 Comment(0)
W
3

You cannot make a constructor of a static member class protected for the subclasses of the outer class. That expressiveness doesn't directly exist in the Java language.

But, you can make the constructor of the static member class Data private - in that case, the outer class can still access it. And the outer class can define a protected factory method that invokes the Data constructor - by making it protected, it is only accessible to Parent itself, subclasses, and all classes in the same package (as is always the case for the protected modifier)

public abstract class Parent
{
    protected static Data createData(String d1, ...) {
        return new Data(d1, ...);
    }

    public static class Data
    {
        public final String data1
        ...

        private Data(String d1, ...)
        {
            data1 = d1;
            ...
        }
    }

    public abstract Data getData();
}
Wold answered 21/5, 2018 at 9:5 Comment(4)
This is literally what I suggest in the final paragraph of the question. Thank you for providing an example, however.Wailful
I see that now. Why would you call that "messy" ?Wold
I suppose messy is not the correct word. What I meant is that it seems like a bit of a bad-style awkward hack, although perhaps it is fine. Looking at the other solution involving a protected implementation of a public interface, this actually seems less "messy". Can anyone comment on merits and drawbacks of either?Wailful
Without further evidence for the other method being better, I'm marking this one as the accepted answer, as it is the one I am currently using.Wailful
S
3

A better solution would be to make the nested class protected, and make it implement a public interface. Only the interface will be exposed to outside classes, and the nested class itself will remain an implementation detail.

public abstract class Parent
{

    public interface Data {
        public String getData1();
    }

    protected static class DataImpl implements Data
    {
        private final String data1;
        ...

        protected DataImpl(String d1, ...)
        {
            data1 = d1;
            ...
        }
        public String getData1(){
            return data1;
        }
    }

    public abstract Data getData();
}

public final class Subclass extends Parent
{
    @Override
    public Data getData()
    {
        return new DataImpl(.....);
    }
}

public class SomeOtherClass
{
    public void someMethod()
    {
        final Data d = new Subclass().getData();
        System.out.println(d.getData1());
    }
}
Siliculose answered 21/5, 2018 at 8:33 Comment(3)
This looks good. It seems inherently more like clever OOP design, but I struggle to see where it actually improves on the proposed solution in terms of functionality. Could you please expand on why this is a better solution?Wailful
@ChristopherRiches It allows you to hide the nested class (which I renamed to DataImpl) from SomeOtherClass, which prevents SomeOtherClass from creating instances of that class, while still allowing SomeOtherClass to access the contents of Data instances (via the interface). I edited the answer to make the DataImpl constructor protected too.Siliculose
My point is: the protected factory method also achieves this with far less code. Is there anything your method does beyond that?Wailful
W
3

You cannot make a constructor of a static member class protected for the subclasses of the outer class. That expressiveness doesn't directly exist in the Java language.

But, you can make the constructor of the static member class Data private - in that case, the outer class can still access it. And the outer class can define a protected factory method that invokes the Data constructor - by making it protected, it is only accessible to Parent itself, subclasses, and all classes in the same package (as is always the case for the protected modifier)

public abstract class Parent
{
    protected static Data createData(String d1, ...) {
        return new Data(d1, ...);
    }

    public static class Data
    {
        public final String data1
        ...

        private Data(String d1, ...)
        {
            data1 = d1;
            ...
        }
    }

    public abstract Data getData();
}
Wold answered 21/5, 2018 at 9:5 Comment(4)
This is literally what I suggest in the final paragraph of the question. Thank you for providing an example, however.Wailful
I see that now. Why would you call that "messy" ?Wold
I suppose messy is not the correct word. What I meant is that it seems like a bit of a bad-style awkward hack, although perhaps it is fine. Looking at the other solution involving a protected implementation of a public interface, this actually seems less "messy". Can anyone comment on merits and drawbacks of either?Wailful
Without further evidence for the other method being better, I'm marking this one as the accepted answer, as it is the one I am currently using.Wailful

© 2022 - 2024 — McMap. All rights reserved.