0

Let's say we have a class with the following method:

public class Entry {
    private String name;

    public static Entry getOrCreate(String name) {
        // ...
        return new Entry(name);
    }
}

This class may be subclassed (e.g. SubEntry), and the logic behind "getOrCreate" does not change. But the subclasses should not return a new object of the type Entry, but of the type of the respective subclass (e.g. return SubEntry(name))

How can I realize this without reimplementing the method getOrCreate for every subclass of Entry? Is there a term for this kind of technique?

soerface
  • 6,417
  • 6
  • 29
  • 50
  • 1
    I think generics will get you there. See http://docs.oracle.com/javase/tutorial/java/generics/types.html – blurfus Feb 04 '15 at 20:06
  • 1
    @ochi, generics + reflection to be more precise, as he will need reflection to create the instance if using generics – Alex Feb 04 '15 at 20:07
  • @AndyBrown fair enough! I am just not sure how rigid the requirement for the method to be static is. Also, you can always return an `Object` right? – blurfus Feb 04 '15 at 20:11
  • This could help: http://stackoverflow.com/questions/14353620/get-instance-of-subclass-with-superclass-static-method But really it could be easier to just add the static method to each subclass. If there's really a lot of repeated code you might be able to factor it out into a helper function in the base class. – jas Feb 04 '15 at 20:19
  • How are you looking to create a `SubEntry` object, do you want to call: `Entry.getOrCreate(name)`, or `SubEntry.getOrCreate(name)`, or `new SubEntry(name)`? – mnd Feb 04 '15 at 20:24

3 Answers3

1

Subclassing Entry does not affect the getOrCreate method because static methods are not part of a class instance; they do not logically belong in any class.

If you instead move getOrCreate into a non-static Factory class, you can use some Generics magic to determine the returned type:

public class Entry {
    private String name;
}

abstract class AbstractEntryFactory<T extends Entry>
    public abstract T getOrCreate(String name);
}

public class EntryFactory extends AbstractEntryFactory<Entry>
    @Override
    public Entry getOrCreate(String name) {
        // ...
        return new Entry(name);
    }
}

public class SubEntryFactory extends AbstractEntryFactory<SubEntry>
    @Override
    public SubEntry getOrCreate(String name) {
        // ...
        return new SubEntry(name);
    }
}

Actually calling the getOrCreate would look different from what it would look like with your code. Instead of this:

Entry myEntry = Entry.getOrCreate("my name");

It would instead look like this:

Entry myEntry = new EntryFactory().getOrCreate("my name");

Or this:

SubEntry myEntry = new SubEntryFactory().getOrCreate("my name");
Matthew Leidholm
  • 4,199
  • 3
  • 24
  • 26
0

Assuming you wanted to be able to call Entry.getOrCreate() to create a type of SubEntry, you'll have to pass along some extra information. The reason is that the getOrCreate() method is not inherited by SubEntry, since it is a static method. So if you want to call it the way I mentioned, you'll have to pass along the class name that you want to create. In the code below there are no checks to validate that Class clazz is an Entry or a subtype, but this gives you a start.

import java.lang.reflect.Constructor;

public class TestClass {
    public static void main(String[] args) {
        Entry entry = (Entry)Entry.getOrCreate("entry", Entry.class);
        SubEntry subEntry = (SubEntry)SubEntry.getOrCreate("subEntry", SubEntry.class);

        System.out.println("entry class: " + entry.getClass().getName());
        System.out.println("subEntry class: " + subEntry.getClass().getName());
    }
}

class Entry {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public static Object getOrCreate(String name, Class clazz) {
        // If a constructor is created that takes a String, such as "public Entry(String name)",
        // then each sub class will need to implement that method. Instead I used a getter and
        // setter for the name attribute.
        try {
            Entry entry = (Entry)clazz.newInstance();
            entry.setName(name);
            return entry;
        }
        catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

class SubEntry extends Entry {
}

The end result is this output:

entry class: Entry
subEntry class: SubEntry
mnd
  • 2,709
  • 3
  • 27
  • 48
0

There are two questions you are asking:

  1. How do I do this?
  2. What is this technique called?

The second one is much more important than the first.

It seems to me like what you are trying to achieve is similar to the concept of cloning (link) or virtual constructor. But you would like this to be a static method, which raises the question as to why? Since a static method is tied to a certain class, not an instance, you should call it through that class in which case you may just as well explicitly be calling new. But having searched for "retrive class in static context" I would say it is not possible to do exactly what you wrote in the question.

If you convert the static method to a normal method, this can be done by using reflection:

class Entry {
    private String name;
    public Entry(String name) {
        this.name = name;
    }
    public Entry() {
        this.name = null;
    }
    public Entry getOrCreate(String name) {
        try {
            return getClass().getConstructor(String.class).newInstance(name);
        } catch (Exception e) {
            return new Entry(name);
        }
    }
}

class BetterEntry extends Entry {
    public BetterEntry(String name) {
        super(name);
    }
    public BetterEntry() {
        super();
    }
}

You would then be calling the function from an instance, like so:

Entry a = new Entry().getOrCreate("First");
Entry b = new BetterEntry().getOrCreate("Second");
Entry c = b.getOrCreate("Third");

The dynamic types of a, b, c are Entry, BetterEntry and BetterEntry. You could leave out the default constructors, but I added them to make calling getOrCreate feel more like a static method.

If you really want the method to be static, the simplest way would be to just reimplement this function in every subclass.

jepio
  • 2,276
  • 1
  • 11
  • 16