0

I have a bunch of classes let's call them Foo1, Foo2, Foo3. Each of them shares a parent SuperFoo and has a similar main function. Each creates an instance of the class and then calls bar() on it.

Essentially, what I have now is:

class Foo1 extends SuperFoo {
  public static void main(String[] args) {
    new Foo1().bar();
  }
}

class Foo2 extends SuperFoo {
  public static void main(String[] args) {
    new Foo2().bar();
  }
}

class Foo3 extends SuperFoo {
  public static void main(String[] args) {
    new Foo3().bar();
  }
}

Basically, this means I'm writing the same function over and over again.

Could I just have a main in SuperFoo that is inherited and uses reflection to create an instance of the subclass? Or is there some other way to merge these into a single function?

Joshua Snider
  • 705
  • 1
  • 8
  • 34
  • 1
    Well static methods cannot be inherited so that idea is out. – markspace May 17 '18 at 13:45
  • Well, they are not inherited, but accissable. ( https://stackoverflow.com/questions/10291949/are-static-methods-inherited-in-java ) – GhostCat May 17 '18 at 13:46
  • But still, markspace has a point here. To do reflection, you need some Class object to work on. The problem is: there is really just **one** version of that main() method (the one in SuperFoo). And in that method ... you probably would have to walk up the call stack in order to identify on which class main was invoked. – GhostCat May 17 '18 at 13:50
  • 1
    Yes - pass the class name in `args` and use that String to [create a new instance](https://stackoverflow.com/questions/9886266/is-there-a-way-to-instantiate-a-class-by-name-in-java). – Andrew S May 17 '18 at 13:52
  • @AndrewS: Something like that seems like it would work. Although I'm running it with `mvn exec:java -Dexec.mainClass=Foo1`. Edit: Just checked and args is empty when run with mvn. I'll see if there's a way to change that. – Joshua Snider May 17 '18 at 13:57
  • 1
    [Maven: How to run a .java file from command line passing arguments](https://stackoverflow.com/questions/10108374/maven-how-to-run-a-java-file-from-command-line-passing-arguments) – Andrew S May 17 '18 at 14:02
  • @GhostCat the problem is, if the `main` method is inherited and only accessed via subclass, there is no caller on the stack. – Holger May 17 '18 at 15:22
  • @Holger Which is what I assumed. I was distracted before putting up another comment to clarify that. – GhostCat May 17 '18 at 15:23

3 Answers3

2

Unless these main method implementations represent different applications warranting different entry points, you don't need to implement more than one main method. And this should solve the problem altogether.

public class Main {
    public static void main(String... args) {
        int fooVersion = Integer.parseInt(args[0]);

        SuperFoo superFoo;
        switch(int) {
            case 1: {
                superFoo = new Foo1();
                break;
            }
            case 2: {
                superFoo = new Foo2();
                break;
            }
            case 3: {
                superFoo = new Foo3();
                break;
            }
            default: {
                throw new IllegalArgumentException();
            }
        }

        superFoo.bar();
    }
}

Alternatively, you can pass the class name and use reflection:

SuperFoo superFoo = (SuperFoo) Class.forName(args[0])).newInstance();
superFoo.bar();

And the application can be started with either a number for the first case, or a concrete class name for the second case, as program arguments.

ernest_k
  • 44,416
  • 5
  • 53
  • 99
  • I was hoping there was a fancier way to do this, but I'll accept this in a few hours if noone comes up with a better answer. – Joshua Snider May 17 '18 at 14:27
  • 1
    @JoshuaSnider Just to be precise about this: the whole design smells fishy. The above is one ugly way to solve the problem you described here, but the *real* answer would be to step back, and look into the *underlying* problem that you are solving with the code you put up here. Because, most likely, you should rethink that approach and solve the underlying problem in a different way. Think XY problem. – GhostCat May 17 '18 at 15:25
  • @GhostCat: This is for https://github.com/jsnider3/comicgetter. I have an interface for downloading webcomics (SuperFoo) and then multiple implementations (Foo1, Foo2, etc.) for specific webcomics. Running SuperFoo's main downloads every webcomic, downloading an implementation's main just downloads that webcomic. I was thinking of adding a GUI to let you pick which webcomic you wanted to download, but that's in the future. – Joshua Snider May 17 '18 at 15:51
2

If you actually still want main in each class, but have SuperFoo create the subclass instance and call bar(), you can do this with method references, and a Supplier<SuperFoo>

class SuperFoo {

    static void create(Supplier<SuperFoo> supplier) {
        SuperFoo foo = supplier.get();
        foo.bar();
    }

    void bar() { ... }
}


class Foo1 extends SuperFoo {
    public static void main(String[] args) {
        SuperFoo.create( Foo1::new );
    }
}

class Foo2 extends SuperFoo {
    public static void main(String[] args) {
        SuperFoo.create( Foo2::new );
    }
}

class Foo3 extends SuperFoo {
    public static void main(String[] args) {
        SuperFoo.create( Foo3::new );
    }
}

Any common pre-initialization can be moved into the SuperFoo::create method before the supplier is invoked to create the instance of the subclass. For example, opening a connection to a database.

And you still have the individual subclass main methods for instance specific initialization. For example, Foo1::main, Foo2::main and Foo3:main could setup (but not open) connections to different databases.


Based on a comment in Ernest's answer, which gives a link to your comicgetter code, and perusing that code base...

It looks like you want a ServiceLoader

interface FooInterface {
    String getName();
    void bar();
}

abstract class SuperFoo implements FooInterface {
    ...
}

public class Foo1 extends SuperFoo {
    public String getName() { return "Name1"; }
    public void bar() { ... }
}

class Main /* extends nothing, implements nothing! */ {
    public static void main(String args[]) {
        List<String> names = List.of(args);
        boolean all = names.size() == 0;

        ServiceLoader<FooInterface> loader = ServiceLoader.load(FooInterface.class);
        for (FooInterface foo : loader) {
            if (all  ||  names.contains(foo.getName()) {
                foo.bar();
            }
        }
    }
}

In a file META-INF/services/your.package.name.FooInterface, you need the lines:

your.package.name.Foo1
your.package.name.Foo2
your.package.name.Foo3

Invoke Main::main with, as arguments, the name (note: not class-name), or names (plural), of the Foo's you want, or with no name to use all Foo's.

Note that class Main has no knowledge of Foo1 or Foo2 or Foo3. The service loader searches for things on the class path that contain META-INF/services/your.package.name.FooInterface. You can add more jar-files later with more FooInterface services for things the main application does not know about (Foo4, Foo5, ...), and these will automatically become supported by the application.

AJNeufeld
  • 8,526
  • 1
  • 25
  • 44
2

Kids, don’t try this at home. This hack surely is not recommended for production code:

class SuperFoo {
    public static void main(String[] args) {
        try {
            ClassLoader cl = SuperFoo.class.getClassLoader();
            Field f = ClassLoader.class.getDeclaredField("classes");
            f.setAccessible(true);
            List<Class<?>> list = (List<Class<?>>)f.get(cl);
            for(Class<?> c: list) {
                if(c.getSuperclass() == SuperFoo.class) {
                    ((SuperFoo)c.getDeclaredConstructor().newInstance()).bar();
                    return;
                }
            }
        } catch(ReflectiveOperationException ex) {
            throw new IllegalStateException(ex);
        }
        System.out.println("Not launched via a subclass of SuperFoo");
    }
    void bar() {
        System.out.println(getClass().getName()+".bar()");
    }
}
class Foo1 extends SuperFoo { }
class Foo2 extends SuperFoo { }
class Foo3 extends SuperFoo { }

When launching this via Foo1, etc., it utilizes the fact that the launcher will load Foo1, etc. for locating the main method to eventually invoke the inherited SuperFoo.main. Recognizing which of the subclasses has been loaded will be done with dark reflection magic.

A less hacky, but still questionable from the software design point of view, solution would be:

class SuperFoo {
    static Class<? extends SuperFoo> SUB_CLASS;
    public static void main(String[] args) {
        if(SUB_CLASS == null)
            System.out.println("Not launched via a subclass of SuperFoo");
        else try {
            SUB_CLASS.getDeclaredConstructor().newInstance().bar();
        } catch(ReflectiveOperationException ex) {
            throw new IllegalStateException(ex);
        }
    }
    void bar() {
        System.out.println(getClass().getName()+".bar()");
    }
}
class Foo1 extends SuperFoo { static { SUB_CLASS = Foo1.class; } }
class Foo2 extends SuperFoo { static { SUB_CLASS = Foo2.class; } }
class Foo3 extends SuperFoo { static { SUB_CLASS = Foo3.class; } }

To minimize copy&paste errors (or more precisely forgetting to adapt the copied code), you could use

class SuperFoo {
    static MethodHandles.Lookup SUB_CLASS_CTX;
    public static void main(String[] args) {
        if(SUB_CLASS_CTX == null)
            System.out.println("Not launched via a subclass of SuperFoo");
        else try {
            ((SuperFoo)SUB_CLASS_CTX.findConstructor(SUB_CLASS_CTX.lookupClass(),
                                                     MethodType.methodType(void.class))
                .invoke()).bar();
        } catch(Throwable ex) {
            throw new IllegalStateException(ex);
        }
    }
    void bar() {
        System.out.println(getClass().getName()+".bar()");
    }
}
class Foo1 extends SuperFoo { static { SUB_CLASS_CTX = MethodHandles.lookup(); } }
class Foo2 extends SuperFoo { static { SUB_CLASS_CTX = MethodHandles.lookup(); } }
class Foo3 extends SuperFoo { static { SUB_CLASS_CTX = MethodHandles.lookup(); } }

But it would be surely cleaner to have a main method in the subclasses:

class SuperFoo {
    protected static void doTheWork(String[] args) {
        Class<? extends SuperFoo> cl =
            StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE)
                .getCallerClass().asSubclass(SuperFoo.class);
        try {
            cl.getDeclaredConstructor().newInstance().bar();
        } catch(ReflectiveOperationException ex) {
            throw new IllegalStateException(ex);
        }
    }
    void bar() {
        System.out.println(getClass().getName()+".bar()");
    }
}
class Foo1 extends SuperFoo {
    public static void main(String[] args) { doTheWork(args); }
}
class Foo2 extends SuperFoo {
    public static void main(String[] args) { doTheWork(args); }
}
class Foo3 extends SuperFoo {
    public static void main(String[] args) { doTheWork(args); }
}

Now, it is enforced that the application is launched via one of the subclasses, i.e. it can’t get invoked via SuperFoo directly. But this code requires Java 9.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • 1
    This is what I originally asked for, but actually seeing it makes me want to change my mind. – Joshua Snider May 17 '18 at 15:54
  • Well, making you changing your mind, is actually a success considering the hacky nature of the solutions. Or, as we call it, “for educational purpose only”… – Holger May 17 '18 at 16:00
  • While I love the hacky/evilness of this answer, I think I can poke a hole it in the second & third approaches. If (say) `class Foo1` has a reference to, or in any way causes `class Foo2` to be loaded, `SUB_CLASS` or `SUB_CLASS_CTX` will be initialized by `Foo1`'s static initialized, and then overwritten by `Foo2`'s static initializer, and then `SuperFoo::main` will create an instance of the wrong type. You could fix this by calling a static first-time-only setter method in `SuperFoo` from the `Foo*` static initializers. – AJNeufeld May 17 '18 at 16:07
  • @AJNeufeld these approaches were not meant to be safe. You could fix them, but I doubt that it is worth the effort. After all, they are only for demonstration. Only the last one has some degree of reliability. It still can’t enforce a particular main class, but at least ensure the right caller. It may still be the case that the `main` method has been invoked by another class instead of the launcher. – Holger May 17 '18 at 16:10