有没有办法在运行时向类定义中添加一个方法?

huangapple go评论58阅读模式
英文:

Is there a way to add a method to a class definition at runtime?

问题

以下是您要翻译的内容:

有一个更好的问题(下面链接)。我的问题鼓励了不好的编码实践,而没有概述这些实践的风险。

https://stackoverflow.com/questions/6680674/can-a-java-class-add-a-method-to-itself-at-runtime

原始问题如下:


有没有办法在运行时向类定义中添加一个方法?

例如,假设我有以下接口

public interface Singleton<T> {
    @StaticContract
    T getInstanceStatic();
}

在运行时,我将扫描所有带有注解“StaticContract”的方法的类,并将实现的方法的静态版本添加到类定义中。但是,我不知道如何去做这个,或者这是否可能。

在我的当前实现中,如果在初始化期间运行时反射没有找到方法的静态方法,我会抛出一个NoSuchMethodError。最大的问题是开发人员可能不知道他们应该创建一个静态方法,如果他们不熟悉接口的话。非静态的getInstanceStatic() 对于单例来说确实没有什么意义。它只是提醒创建静态方法。

结合使用反射恢复擦除的类型的能力,这将允许我使用泛型远远超出了它们的预期用途。例如,您将不再需要定义和传递一个工厂对象。您可以在工厂生成的类中定义方法。

此外,如果在运行时没有办法做到这一点,那么在编译时是否有办法做到这一点?

英文:

There is a much better question (linked below). My question encouraged bad coding practices without outlining the risks of those practices.

https://stackoverflow.com/questions/6680674/can-a-java-class-add-a-method-to-itself-at-runtime

The original question was the following:


Is there a way to add a method to a class definition at runtime?

For example, lets say I had the following interface

public interface Singleton&lt;T&gt; {
    @StaticContract
    T getInstanceStatic();
}

At runtime, I would scan all classes for methods with the annotation "StaticContract" and add a static version of the implemented method to the class definition. However, I have no idea how I would go about doing this or if this is even possible.

In my current implemention, if runtime reflection doesn't find a static method for a method during initialization, I throw a NoSuchMethodError. The big problem is that the developer might not know that they are supposed to create a static method if they aren't familiar with the interface. Non-static getInstanceStatic() doesn't really make sense with Singletons. It just serves as a reminder to create the static method.

Combined with the ability to recover the erased type using reflection, this would allow me to use generics for far more than they were intended. For example, you would no longer have to define and pass a factory object. You could just define the method in the class that the factory produces.

Also, if there isn't a way to do this during runtime, is there a way to do it during compile time?

答案1

得分: 6

你想要的是可能的!

但不是这样做。对于你实际问题的答案是一个简单而坚定的“不”。但你不想要你在问题中描述的东西。

让我详细解释一下。

首先让我们说你可以在运行时添加方法。你不能*,但让我们假设你可以。

这将一无是处;考虑到以下情况:

public class Example implements Singleton<Example> {
    @StaticContract Example getInstanceStatic() { return new Example(); }
}

我们已经可以看到问题了(这个方法是.. public。它必须是,这是接口的规则。但考虑到你想要这个成为一个单例,那将是非常不好的消息)。

但让我们继续一会。想法是你想能够在其他代码中编写:

    Example.instance();

但是 - 如何?编译器不会让你这样做,因为方法不存在,如果按照你的计划(在运行时添加方法),那么在编译时它永远不会存在,javac 将拒绝编译这个代码。如果不知何故它确实编译了这个代码,那么在运行时,你执行你的魔术技巧并以某种方式添加这个方法,一切都会很好,但这是一个无谓的论点 - 除非使用字节码编辑器拼凑出一个类文件,否则无法获得带有 Example.instance() 编译版本的类文件。

你不想在运行时添加这个

但也许你想在编译时添加它

那个?你可以做到!

策略 #1: Lombok

Project Lombok 允许你编写 @UtilityClass,使其表现得像单例。Lombok故意没有 @Singleton,因为作为一个概念,单例被普遍认为是糟糕的代码风格。如果你一定要这个功能,我猜你可以分支 lombok 并添加它。

策略 #2: 注解处理器

除了 lombok,注解处理器无法添加东西到现有的源文件中。但它们可以创建新的源文件!假设有一个实际的磁盘上的源文件:

@SingletonizeMe
public class Example {
    Example() {} // 如果没有 lombok,你将不得不自己编写这个以确保包外的人无法实例化它...
}

然后你可以编写一个注解处理器,这意味着 javac 将自动产生这个文件:

// 自动生成的代码
package same.pkg.as.your.example;

public class ExampleUtil {
    public static final Example EXAMPLE_INSTANCE = new Example();
}

并将其编译为构建的一部分,包含 ExampleUtil.EXAMPLE_INSTANCE 的任何代码都将被编译,没有任何投诉。注解处理器解决了'好吧,也许在运行时这可能会有效,但我如何告诉 javac 只是按照我想要的方式进行编译,而不拒绝编译它认为没有可能在运行时工作的代码的问题。

策略 #3: 依赖注入系统

从 Dagger 到 Spring 到 Guice,有大量的库支持“依赖注入”,几乎所有这些库都有选项以单例方式注入。快速浏览一下这些 3 个库,一旦按照它们的快速入门教程操作,应该很明显它们是如何工作的。

*) 你可能会认为答案是肯定的,因为有仪器和使用代理技术重新加载类文件的能力。但这是否是“向类添加方法”?不,不是 - 这是重新加载类,如果你尝试添加任何新成员,通常不会工作;VM 中内置的热代码替换技术不允许你更改(或添加、删除)任何签名。

英文:

What you want is possible!

But not like this. The answer to your actual question is a simple, flat out 'No'. But you don't want what you describe in your question.

Let me elaborate.

Let's first say that you could add methods at runtime. You can't*, but let's say you could.

That would accomplish nothing whatsoever; given:

public class Example implements Singleton&lt;Example&gt; {
    @StaticContract Example getInstanceStatic() { return new Example(); }
}

We can already see issues here (this method is.. public. It has to be, that's the rule of interfaces. But given that you want this to be a singleton, that'd be very bad news).

But let's carry on for a moment. The idea is that you want to be able to write, in other code:

    Example.instance();

but - how? The compiler won't LET YOU do that, because the method isn't there, and if we go with your plan (of adding the method at runtime), then at compile time it'll never be there, and javac will refuse to compile this. If somehow it DID compile this, then at runtime, where you pull your magic trick and somehow add this method, all would be well, but that's a moot point - short of hacking together a class file with a bytecode editor, there's no way to obtain a class file with the compiled version of Example.instance().

You don't want to add this at runtime.

But maybe you want to add it at compile time.

And THAT? That you can do!

Strategy #1: Lombok

Project Lombok lets you write @UtilityClass which makes it act singleton-esque. Lombok intentionally does not have @Singleton because as a concept, singletons are so universally deriled as bad code style. I guess you could fork lombok and add it if you must have this.

Strategy #2: Annotation Processors

Other than lombok, annotation processors cannot add things to existing source files. But they can make new ones! Given as actual real bytes on disk source file:

@SingletonizeMe
public class Example {
    Example() {} // without lombok you&#39;re going to have to write this yourself to ensure nobody outside of the package can instantiate this...
}

then you can write an annotation processor which means that javac will automatically produce this file:

// generated code
package same.pkg.as.your.example;

public class ExampleUtil {
    public static final Example EXAMPLE_INSTANCE = new Example();
}

and compile it as part of the build, and any code that contains ExampleUtil.EXAMPLE_INSTANCE will just be compiled right along, without any complaints. Annotation Processors solve the problem of 'okay, maybe at runtime this would work but how do I explain to javac to just do what I want without it refusing to compile code that it thinks stands no chance of working at runtime?'.

Strategy #3: Dependency injection systems

From dagger to spring to guice, there are tons of libraries out there that do 'dependency injection', and pretty much all of them have an option to inject things singleton style. Give those 3 libraries a quick look, it should be fairly obvious how that works once you follow their get-started-quick tutorials.

*) You'd think the answer is yes, what with instrumention and the ability to use agent technology to reload a class file. But is that 'adding a method to a class'? No, it is not - it is reloading a class, which does not normally work if you try to add any new members; the hot code replace tech built into VMs doesn't let you change (or add, or remove) any signatures.

huangapple
  • 本文由 发表于 2020年7月29日 01:09:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/63139338.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定