0

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

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<T> {
    @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?

Ryan M
  • 18,333
  • 31
  • 67
  • 74

1 Answers1

6

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<Example> {
    @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'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.

rzwitserloot
  • 85,357
  • 5
  • 51
  • 72