@LookAtIos

Как запустить 10000 классов в java?

Интересное задание по java: запустить в проекте 1000 классов, которые выводят номер класса. Просто копировать и компилировать, наверное, не то. Какие лучшие способы предложите для решения задачи?
  • Вопрос задан
  • 403 просмотра
Решения вопроса 1
sergey-gornostaev
@sergey-gornostaev Куратор тега Java
Седой и строгий
Прежде всего нужно заметить, что запустить класс нельзя, его можно только объявить, а запускать можно методы экземпляров этого класса. Почему я и подумал, что вопрос неправильно сформулирован.

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

Генерировать можно как java-код, так и байткод. Первый способ использует популярная библиотека Lombok. Для него понадобиться механизм работы с AST и механизм взаимодействия с компилятором. Oracle JDK и Open JDK предоставляют Compiler API, реализующий оба механизма. Кроме того, есть и другие инструменты для работы с AST, например Eclipse JDT. Я покажу применение пакета com.sun.tools.javac из Compiler API.
Сноска
До Java 9 пакет com.sun.tools.javac был упакован в tools.jar, поставляемый вместе с JDK. Начиная с 9-ки API компилятора вынесли в модуль jdk.compiler, не экспортирующие свои пакеты. Теперь можно не указывать путь до tools.jar в classpath, но нужно добавлять экспорты. В остальном ничего не изменилось.
import java.lang.reflect.Method;
import java.net.URI;
import java.util.ArrayList;
 
import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.JavaFileObject;
import javax.tools.SimpleJavaFileObject;
import javax.tools.ToolProvider;

import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.file.JavacFileManager;
import com.sun.tools.javac.model.JavacElements;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.Name;


/**
 * Класс эмулирующий для компилятора файлы исходного кода
 * и позволяющий компилировать код прямо из памяти
 */
class JavaSourceFromString extends SimpleJavaFileObject {
    private final String code;

    public JavaSourceFromString(String name, String code) {
        super(URI.create("string:///" + name.replace('.','/') + Kind.SOURCE.extension), Kind.SOURCE);
        this.code = code;
    }

    @Override
    public CharSequence getCharContent(boolean ignoreEncodingErrors) {
        return code;
    }
}
 
public class CompilerDemo {
    private static final String BASE_NAME = "DynamicHello";
    private static final ClassLoader classLoader = ToolProvider.getSystemToolClassLoader();
    private static final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();

    /**
     * Метод формирующий абстрактное синтаксическое дерево
     * и преобразующий его в исходный код
     */
    private static String generateSource(String className) {
        Context ctx = new Context();
        JavacFileManager.preRegister(ctx);
        TreeMaker treeMaker = TreeMaker.instance(ctx);
        JavacElements elements = JavacElements.instance(ctx);

        JCTree.JCModifiers modifiers = treeMaker.Modifiers(Flags.PUBLIC);

        // Тело генерируемого метода
        // выводящее имя класса
        JCTree.JCBlock methodBody = treeMaker.Block(0, List.of(
            treeMaker.Exec(
                treeMaker.Apply(
                    List.<JCTree.JCExpression>nil(),
                    treeMaker.Select(
                        treeMaker.Select(
                            treeMaker.Ident(
                                elements.getName("System")
                            ),
                            elements.getName("out")
                        ),
                        elements.getName("println")
                    ),
                    List.<JCTree.JCExpression>of(
                        treeMaker.Binary(
                            JCTree.Tag.PLUS,
                            treeMaker.Literal("I am "),
                            treeMaker.Apply(
                                List.<JCTree.JCExpression>nil(),
                                treeMaker.Select(
                                    treeMaker.Apply(
                                        List.<JCTree.JCExpression>nil(),
                                        treeMaker.Select(
                                            treeMaker.Ident(
                                                elements.getName("this")
                                            ),
                                            elements.getName("getClass")
                                        ),
                                        List.<JCTree.JCExpression>nil()
                                    ),
                                    elements.getName("getName")
                                ),
                                List.<JCTree.JCExpression>nil()
                            )
                        )
                    )
                )
            )
        ));

        // Определение генерируемого метода
        JCTree.JCMethodDecl method = treeMaker.MethodDef(
            modifiers,
            elements.getName("introduceYourself"),
            treeMaker.Type(new Type.JCVoidType()),
            List.<JCTree.JCTypeParameter>nil(),
            List.<JCTree.JCVariableDecl>nil(),
            List.<JCTree.JCExpression>nil(),
            methodBody,
            null
        );

        // Определение генерируемого класса
        JCTree.JCClassDecl tree = treeMaker.ClassDef(
            modifiers,
            elements.getName(className),
            List.<JCTree.JCTypeParameter>nil(),
            null,
            List.<JCTree.JCExpression>nil(),
            List.of(method)
        );

        return tree.toString();
    }

    /**
     * Метод компилирующий исходный код
     */
    public static void compile(Iterable<? extends JavaFileObject> compilationUnits) {
        CompilationTask task = compiler.getTask(null, null, null, null, null, compilationUnits);
        task.call();    
    }


    /**
     * Метод запускающий сгенерированные классы с помощью рефлексии
     */    
    public static void loadAndRun(String className) throws ReflectiveOperationException {
        Class<?> cls = classLoader.loadClass(className);
        
        Method method = cls.getDeclaredMethod("introduceYourself");
        method.invoke(cls.newInstance());    
    }

    public static void main(String[] args) throws Exception {
        java.util.List<JavaFileObject> sources = new ArrayList<>();

        // Генерируем исходный код десятка классов
        for (int x = 0; x < 10; x++) {
            String className = BASE_NAME + x;
            sources.add(new JavaSourceFromString(className, generateSource(className)));
        }

        // Компилируем сгенерированный код
        compile(sources);
        
        // Запускаем скомпилированные классы
        for (int x = 0; x < 10; x++) {
            loadAndRun(BASE_NAME + x);
        }
    }
}

Конечно, можно было не заморачиваться с AST, просто засунуть код в строковый литерал, подставлять в этот литерал разные имена и подсовывать компилятору. Но это не так интересно!

Продолжение в первом комментарии, а то я в лимит символов не укладываюсь.
Ответ написан
Пригласить эксперта
Ответы на вопрос 1
iLLuzor
@iLLuzor
Java, Kotlin, Android Developer
Самое простое - циклы.
Посложней - циклы и потоки.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы