Создайте объект класса, в котором я нахожусь - PullRequest
0 голосов
/ 17 мая 2018

У меня есть куча классов, назовем их Foo1, Foo2, Foo3.Каждый из них разделяет родительский SuperFoo и имеет аналогичную основную функцию.Каждый создает экземпляр класса, а затем вызывает для него bar ().

По сути, сейчас у меня есть:

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();
  }
}

По сути, это означает, что я пишу ту же функциюснова и снова.

Могу ли я просто иметь в SuperFoo main, который наследуется и использует отражение для создания экземпляра подкласса?Или есть какой-то другой способ объединить их в одну функцию?

Ответы [ 3 ]

0 голосов
/ 17 мая 2018

Если вы на самом деле все еще хотите main в каждом классе, но при этом SuperFoo создайте экземпляр подкласса и вызовете bar(), вы можете сделать это с помощью ссылок на методы и 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 );
    }
}

Любая общая предварительная инициализация может быть перемещена в метод SuperFoo::create до вызова поставщика для создания экземпляра подкласса. Например, открытие соединения с базой данных.

И у вас все еще есть отдельные методы подкласса main для конкретной инициализации экземпляра. Например, Foo1::main, Foo2::main и Foo3:main могут устанавливать (но не открывать) соединения с различными базами данных.


На основании комментария в ответе Эрнеста, в котором содержится ссылка на ваш код комикеттера, и просматривается эта кодовая база ...

Похоже, вы хотите 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();
            }
        }
    }
}

В файле META-INF/services/your.package.name.FooInterface вам нужны строки:

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

Вызвать Main::main с аргументами: имя ( примечание: не имя класса) или имена (во множественном числе) тех Foo, которые вы хотите, или без имени для использования всех Foo.

Обратите внимание, что class Main не знает Foo1 или Foo2 или Foo3. Загрузчик службы ищет вещи в пути к классам, которые содержат META-INF/services/your.package.name.FooInterface. Позже вы можете добавить больше jar-файлов с большим количеством FooInterface сервисов для вещей, о которых основное приложение не знает (Foo4, Foo5, ...), и они автоматически станут поддерживаться приложением.

0 голосов
/ 17 мая 2018

Дети, не пытайтесь делать это дома.Этот хак, безусловно, не рекомендуется для производственного кода:

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 { }

При запуске через Foo1 и т. Д. Он использует тот факт, что лаунчер загрузит Foo1 и т. Д. Для определения местоположения main метод, чтобы в конечном итоге вызвать унаследованный SuperFoo.main.Распознавание того, какой из подклассов был загружен, будет сделано с помощью магии темного отражения.

Менее хакерский, но все еще сомнительный с точки зрения дизайна программного обеспечения, решение будет:

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; } }

Чтобы свести к минимуму ошибки копирования и вставки (или, точнее, забыть адаптировать скопированный код), вы можете использовать

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(); } }

Но было бы надежнее иметь метод main в подклассах:

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); }
}

Теперь принудительно запускается приложение через один из подклассов, т. Е. Оно не может быть вызвано напрямую через SuperFoo.Но этот код требует Java 9.

0 голосов
/ 17 мая 2018

Если эти реализации метода main не представляют разные приложения, требующие разных точек входа, вам не нужно реализовывать более одного метода main.И это должно полностью решить проблему.

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();
    }
}

В качестве альтернативы, вы можете передать имя класса и использовать отражение:

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

И приложение может быть запущено с любым числом дляпервый случай или конкретное имя класса для второго случая в качестве аргументов программы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...