Home > other >  Java: make static methods of one class mirror instance methods of another class
Java: make static methods of one class mirror instance methods of another class

Time:04-22

I have a POJO like this:

public class Foo {
    private String bar1;
    private String bar2;
    //...

    public String getBar1() { return bar1; } 
    public void setBar1(String bar1) { this.bar1 = bar1; }
    public String getBar2() { return bar2; } 
    public void setBar2(String bar2) { this.bar2 = bar2; }
    //...
}

As an alternative to Java reflection (which is quite slow in general), I would like to define a class with static methods like this:

public class FooStatic {

    public static String getBar1(Foo foo) { return foo.getBar1(); } 
    public static void setBar1(Foo foo, String bar1) { foo.setBar1(bar1); }
    public static String getBar2(Foo foo) { return foo.getBar2(); } 
    public static void setBar2(Foo foo, String bar2) { foo.setBar2(bar2); }
    //...
}

which enforces the creation/deprecation of a static method every time the Foo class is updated. For example, if the field bar2 is deleted in FooStatic, then the static methods getBar2() and setBar2() in FooStatic should be identified by the compiler to be removed. If a new variable bar3 is added to Foo with getters and setters, the compiler should enforce the creation of new static methods getBar3() and setBar3() in FooStatic. Moreover, I have multiple POJOs, and would like a solution which scales. Is this possible?

CodePudding user response:

Yes... sort of. It's very complicated.

Annotation Processors are compiler plugins that run at certain times during the compilation process. It gets complex fast - IDEs and build tools are 'incremental' (they don't want to recompile your entire code base everytime you change a single character, of course), for example.

Annotation processors can do a few things:

  • They can run as part of the compilation processes. This can be done automatically - they just need to be on the classpath, is all
  • They can be triggered due to the presence of an annotation.
  • They can read the signatures of existing files (the names of fields and methods, the parameter names, parameter types, return type, and throws clause, and the type of fields, and the extends and implements clauses, and the param names and types of the constructors). They can't read the body content (initializing expressions, method and constructor bodies). But I think you just need the signatures here.
  • They can make new files. They can even make new java files which will then automatically get compiled along with the rest.

Thus, you have a route here: Make an annotation, then make an annotation processor. For example, you could set it up so that you manually write:

@com.foo.Hossmeister.Singletonify
class Example {
  void foo1() {}
  String foo2(String arg) throws IOException {}
}

and have an Annotation Processor (which also has that com.foo.Hossmeister.Singletonify annotation), which, if it is on the classpath, automatically generates and ensures that all other code can automatically see this file:

// Generated
class ExampleSingleton {
  private ExampleSingleton() {}
  private static final Example INSTANCE = new Example();

  public void foo1() {
    INSTANCE.foo1();
  }

  public static String foo2(String arg) throws IOException {
    return INSTANCE.foo2(arg);
  }
}

But, annotation processors are tricky beasts to write, and they can be quite a drag on the compilation process. Still, that's the only way to get what you want. Now you have something to search the web for / read up on :)

You start by making a separate project that defines the annotation, has the annotation processor (a class that extends AbstractProcessor), pack that into a jar, and make sure the manifest includes an SPI file that tells java that your class that extends AbstractProcessor is an annotation processor, and then it'll be picked up automatically. I'll give you the annotation definition:

In a file named Singletonify.java:

@Retention(RetentionPolicy.CLASS)
@Target(ElementType.TYPE)
public @interface Singletonify {}

But... wait!

The concept of singletons is often problematic. Singletons should be 'stateless' - and if they are stateless, why isn't your Foo class just filled with entirely static methods, obviating the need for your "static mirror class"? If it is stateful, you now have global state which is a virtually universally decried anti-pattern. You don't want global state, it makes reasoning about control flow impossible.

A second problem is testability - because static stuff doesn't 'do' inheritance, you can't (easily) make test implementations of static methods. With non-static stuff this is much easier.

This problem is more generally solved by so-called Dependency Injection frameworks such as Dagger, Guice, or Spring. They let you write code that just 'gets' an instance of your Foo class, without callers having to actually figure out where to get this instance from: The Dependency Injection framework takes care of it. It lets you do things like "Have a singleton of this object... per web session". Which is pretty powerful stuff.

I think what you probably want is a DI system. You may want to investigate a bit before spending the 2 weeks writing that annotation processor.

  • Related