0

Let's say I have an abstract class A exposing a method called getE() which returns an object of type E which is an enum. The value returned by getE() will be defined per subclass. Put differently, every object of a certain subclass of A will always return the same value if getE() is called.

This is how I have implemented that system right now:

public enum E {
    VAL_1, VAL_2
}

public abstract class A {

    private final E e;

    protected A(E e) {
        this.e = e;
    }

    public E getE() {
        return e;
    }

}

public class A1 extends A {

    public A1() {
        super(E.VAL_1);
    }

}

public class A2 extends A {

    public A2() {
        super(E.VAL_2);
    }

}

But this wastes memory, as every instance of A has to keep a reference to E, and also clutters A's constructor.

Another solution I have thought of is this one:

public abstract class A {

    public abstract E getE();

}

public class A1 extends A {

    @Override
    public E getE() {
        return E.VAL_1;
    }

}

public class A2 extends A {

    @Override
    public E getE() {
        return E.VAL_2;
    }

}

But adding new methods to a class probably is memory-intenisve as well and might cause confusion.

Which of these ways do you think is better or are there other alternatives?

I have already asked this question on Code Review and Stack Overflow and got redirected to here.

2
  • 2
    Do you actually, really care about how much memory this uses? What profiling have you done which has shown this to be an issue? Commented Jun 30, 2021 at 15:58
  • I mostly care about the constructor-cluttering of the first and the confusion created by the second version. The memory used is more of a side-effect.
    – cegredev
    Commented Jun 30, 2021 at 16:01

3 Answers 3

2

Neither option is memory consuming. As a matter of fact, is reduces memory consumption and is the basis of the flightweight design pattern which sole purpose is to save memory while creating a lot of objects that share common data.

I read your comment to @martin-mmat 's answer and you should add that clarification to the question since the A,A1,B,B1 names are really bad for understanding the problem. So I will quote it here:

I am not defeating the purpose of polymorphism. In my actual project A is called OperatingSystem and represents, well, an operating system. E is called OSFamily and is either WINDOWS, MAC, LINUX or UNKNOWN. There are four subclasses of OperatingSystem, each representing a different one of those families and containing different information. – Schred

That said, the second option makes more sense since A will be the base of the hierarchy and therefore it will not be of any particular operating system, it will be just an operating system. In fact, the subclasses of that abstract class will be the actual start points of each OS family. I see no confusion in this second version.

A couple of changes, though. I would:

  • Make A an interface, for more flexibility
  • Make getE in A1 and A2 final, so sunclasses cannot change their OSFamily
  • Make A1 and A2 abstract since there's no point in instiating the very starting of a branch.
enum OSFamily {
    UNIX, WINDOWS, LINUX
}

interface OS {
    public abstract OSFamily getOSFamily();
}

abstract class Unix implements OS {
    @Override
    public final OSFamily getOSFamily() {
        return OSFamily.UNIX;
    }
}

abstract class Linux implements OS {
    @Override
    public final OSFamily getOSFamily() {
        return OSFamily.LINUX;
    }
}

class BSD extends Unix {}
class OSX extends BSD {}
class Android extends Linux {}

public class Test {
    public static void main (String args[]){
        OS myBSD = new BSD();
        OS myOSX = new OSX();
        OS myAndroid = new Android();
        System.out.println(myBSD.getOSFamily());
        System.out.println(myOSX.getOSFamily());
        System.out.println(myAndroid.getOSFamily());
    }    
}

Output:

UNIX
UNIX
LINUX
3
  • Making the methods final is a great idea, thank you! Making the subclasses abstract won't make sense for all cases of course, but I'll definitely consider it for some.
    – cegredev
    Commented Jun 30, 2021 at 17:55
  • @Schred I suggest making only the branch starters abstract, like "Unix", "Linux", "Windows" because you will be instantiating some concrete incarnation of Windows and not an abstract, seminal, Windows concept. Commented Jun 30, 2021 at 18:01
  • That's probably what I'll do, thanks!
    – cegredev
    Commented Jun 30, 2021 at 18:19
1

Technically there is no issue with either of your solutions. Memory use is negligible. They do raise the question what you are trying to achieve and whether using an enum as a means to tell what type you are dealing with makes sense.

Why do you think inheritance will be helpful when you are defeating the purpose of polymorphism?

The interesting questions can only be answered if you tell us a bit about the problem, using meaningful names for the classes.

2
  • I am not defeating the purpose of polymorphism. In my actual project A is called OperatingSystem and represents, well, an operating system. E is called OSFamily and is either WINDOWS, MAC, LINUX or UNKNOWN. There are four subclasses of OperatingSystem, each representing a different one of those families and containing different information. If you need I can update the question with more details.
    – cegredev
    Commented Jun 30, 2021 at 16:40
  • 1
    @Schred I am suggesting you are defeating the purpose of polymorphism because when applied properly you would have no need to ask for type specific properties. You could just treat each object as an operating system. This is a somewhat generalized and speculative statement though which is why I suggested to provide more context. Commented Jul 1, 2021 at 18:10
0

Adding instance fields adds to both the instance object sizes as well as the size of the (internal) representation of the class to which field was added — so, due to the former (the object size increase) adding an instance field scale as follows: the more instance objects, the more memory consumed.  Due to the latter (increase in metadata to represent the class) the representation of the class to which the instance field is added is minimally larger.

Adding overridable instance methods (and overriding in various implementations) adds to the size of the (internal) representation of the classes, though minimally.  So, the more classes, the more memory consumed, but since this memory for class representation (code & vtables) will be shared among all instances, this will not increase the size of object instances.

You will have to consider your workload, but typically, even class count in the thousands is a small number, and, classes are usually fixed in number at runtime, whereas instances can be in the millions as they are dynamically created.


How should an abstract class require a value that will be constant for each subclass?

For memory consumption, the latter approach (overrides) is preferred for large numbers of instances.

Generally speaking (regardless of memory consumption), I would prefer using overrides since this is constant but different information for each subclass.  If a subclass wants to have an internal field to assist them in implementation of this override, they can do that.  But I find it a bit presumptuous to force that implementation on every subclass.  (Further, you may find that it is not always a constant.)

1
  • I'm not that concerned about the memory usage anymore, although this is how I thought it worked. To me what matters the most is what approach is the cleanest, and I've decided to use abstract methods.
    – cegredev
    Commented Jun 30, 2021 at 18:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.