简体   繁体   中英

Java - Alternatives to forcing subclass to have a static method

I often find I want to do something like this:

class Foo{
public static abstract String getParam();
}

To force a subclasses of Foo to return a parameter.

I know you can't do it and I know why you can't do it but the common alternative of:

class Foo{
public abstract String getParam();
}

Is unsatisfactory because it requires you to have an instance which is not helpful if you just want to know the value of the parameter and instantiating the class is expensive.

I'd be very interested to know of how people get around this without getting into using the "Constant Interface" anti pattern.

EDIT: I'll add some more detail about my specific problem, but this is just the current time when I've wanted to do something like this there are several others from the past.

My subclasses are all data processors and the superclass defines the common code between them which allows them to get the data, parse it and put it where it needs to go. The processors each require certain parameters which are held in an SQL database. Each processor should be able to provide a list of parameters that it requires and the default values so the configuration database can be validated or initialised to defaults by checking the required parameters for each processor type. Having it performed in the constructor of the processor is not acceptable because it only needs to be done once per class not once per object instance and should be done at system startup when an instance of each type of class may not yet be needed.

The best you can do here in a static context is something like one of the following:

a. Have a method you specifically look for, but is not part of any contract (and therefore you can't enforce anyone to implement) and look for that at runtime:

 public static String getParam() { ... };
 try {
     Method m = clazz.getDeclaredMethod("getParam");
     String param = (String) m.invoke(null);
 }
 catch (NoSuchMethodException e) {
   // handle this error
 }

b.Use an annotation, which suffers from the same issue in that you can't force people to put it on their classes.

@Target({TYPE})
@Retention(RUNTIME)
public @interface Param {
   String value() default "";
}

@Param("foo")
public class MyClass { ... }


public static String getParam(Class<?> clazz) {
   if (clazz.isAnnotationPresent(Param.class)) {
      return clazz.getAnnotation(Param.class).value();
   }
   else {
      // what to do if there is no annotation
   }
}

I agree - I feel that this is a limitation of Java. Sure, they have made their case about the advantages of not allowing inherited static methods, so I get it, but the fact is I have run into cases where this would be useful. Consider this case:

I have a parent Condition class, and for each of its sub-classes, I want a getName() method that states the class' name. The name of the sub-class will not be the Java's class name, but will be some lower-case text string used for JSON purposes on a web front end. The getName() method will not change per instance, so it is safe to make it static. However, some of the sub-classes of the Condition class will not be allowed to have no-argument constructors - some of them I will need to require that some parameters are defined at instantiation.

I use the Reflections library to get all classes in a package at runtime. Now, I want a list of all the names of each Condition class that is in this package, so I can return it to a web front end for JavaScript parsing. I would go through the effort of just instantiating each class, but as I said, they do not all have no-argument constructors. I have designed the constructors of the sub-classes to throw an IllegalArgumentException if some of the parameters are not correctly defined, so I cannot merely pass in null arguments. This is why I want the getName() method to be static, but required for all sub-classes.

My current workaround is to do the following: In the Condition class (which is abstract), I have defined a method:

public String getName () {
    throw new IllegalArugmentException ("Child class did not declare an overridden getName() method using a static getConditionName() method.  This must be done in order for the class to be registerred with Condition.getAllConditions()");
}

So in each sub-class, I simply define:

@Override
public String getName () {
    return getConditionName ();
}

And then I define a static getConditionName() method for each. This is not quite "forcing" each sub-class to do so, but I do it in a way where if getName() is ever inadvertently called, the programmer is instructed how to fix the problem.

It seems to me you want to solve the wrong problem with the wrong tool. If all subclasses define (can't really say inherit) your static method, you will still be unable to call it painlessly (To call the static method on a class not known at compile time would be via reflection or byte code manipulation).

And if the idea is to have a set of behaviors, why not just use instances that all implement the same interface? An instance with no specific state is cheap in terms of memory and construction time, and if there is no state you can always share one instance (flyweight pattern) for all callers.

If you just need to couple metadata with classes, you can build/use any metadata facility you like, the most basic (by hand) implementation is to use a Map where the class object is the key. If that suits your problem depends on your problem, which you don't really describe in detail.

EDIT: (Structural) Metadata would associate data with classes (thats only one flavor, but probably the more common one). Annotations can be used as very simple metadata facility (annotate the class with a parameter). There are countless other ways (and goals to achieve) to do it, on the complex side are frameworks that provide basically every bit of information designed into an UML model for access at runtime.

But what you describe (processors and parameters in database) is what I christened "set of behaviors". And the argument "parameters need to be loaded once per class " is moot, it completely ignores the idioms that can be used to solve this without needing anything 'static'. Namely, the flyweight pattern (for having only once instance) and lazy initialization (for doing work only once). Combine with factory as needed.

I'm having the same problem over and over again and it's hard for me to understand why Java 8 preferred to implement lambda instead of that.

Anyway, if your subclasses only implement retrieving a few parameters and doing rather simple tasks, you can use enumerations as they are very powerful in Java: you can basically consider it a fixed set of instances of an interface. They can have members, methods, etc. They just can't be instanciated (as they are "pre-instanciated").

public enum Processor {
    PROC_IMAGE {
        @Override
        public String getParam() {
            return "image";
        }
    },

    PROC_TEXT {
        @Override
        public String getParam() {
            return "text";
        }
    }
    ;

    public abstract String getParam();

    public boolean doProcessing() {
        System.out.println(getParam());
    }
}

The nice thing is that you can get all "instances" by calling Processor.values() :

for (Processor p : Processorvalues()) {
    System.out.println(String.format("Param %s: %s", p.name(), p.getParam()));
    p.doProcessing();
}

If the processing is more complex, you can do it in other classes that are instanciated in the enum methods:

@Override
public String getParam() {
    return new LookForParam("text").getParam();
}

You can then enrich the enumeration with any new processor you can think of.

The down side is that you can't use it if other people want to create new processors, as it means modifying the source file.

You can use the factory pattern to allow the system to create 'data' instances first, and create 'functional' instances later. The 'data' instances will contain the 'mandatory' getters that you wanted to have static . The 'functional' instances do complex parameter validation and/or expensive construction. Of course the parameter setter in the factory can also so preliminary validation.

public abstract class Processor { /*...*/ }

public interface ProcessorFactory {
    String getName(); // The  mandatory getter in this example

    void setParameter(String parameter, String value);

    /** @throws IllegalStateException when parameter validation fails */
    Processor construct();
}

public class ProcessorA implements ProcessorFactory {
    @Override
    public String getName() { return "processor-a"; }

    @Override
    public void setParameter(String parameter, String value) {
        Objects.requireNonNull(parameter, "parameter");
        Objects.requireNonNull(value, "value");
        switch (parameter) {
            case "source": setSource(value); break;
            /*...*/
            default: throw new IllegalArgumentException("Unknown parameter: " + parameter);
        }
    }

    private void setSource(String value) { /*...*/ }

    @Override
    public Processor construct() {
        return new ProcessorAImpl();
    }

    // Doesn't have to be an inner class. It's up to you.
    private class ProcessorAImpl extends Processor { /*...*/ }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM