Прежде всего нужно заметить, что
запустить класс нельзя, его можно только объявить, а запускать можно методы экземпляров этого класса. Почему я и подумал, что вопрос неправильно сформулирован.
Но если уж вопрос именно об объявлении новых классов в рантайме, то это называется кодогенерацией. И, во-первых, рефлексия в этом бессильна, а во-вторых, это очень сложная тема, озадачить которой начинающего программиста мог только очень мерзкий тролль.
Генерировать можно как 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, просто засунуть код в строковый литерал, подставлять в этот литерал разные имена и подсовывать компилятору. Но это не так интересно!
Продолжение в первом комментарии, а то я в лимит символов не укладываюсь.