Return a class, not an instance, of a concrete cla

2019-09-01 16:43发布

问题:

If I have a class hierarchy like this

AbstractSuperClass
    ConcreteClassA
    ConcreteClassB

is it possible to have a static method in AbstractSuperClass which returns the class - not an instance - of one of the two concrete classes?

I've tried returning Class<AbstractSuperClass>, but the IDE (Android Studio) says

Incompatible types.
Required: Class <com.example.AbstractSuperClass>
Found:    Class <com.example.ConcreteClassA>

Here's an example of what I'm thinking, but which isn't working:

public abstract class AbstractSuperClass{

    public abstract void someAbstractMethod();


    public static String getSomeText(){ 
        return "This is AbstractSuperClass"; 
    };

    public static Class<AbstractSuperClass> getConcreteClass(int x){
        switch( x ){
            case 0: return ConcreteClassA.class;
            case 1: return ConcreteClassB.class;
        }
    }
}


public class ConcreteClassA extends AbstractSuperClass{
    public abstract void someAbstractMethod(){
        // Do something
    }

    public static String getSomeText(){ 
        return "This is ConcreteClassA"; 
    };
}



public class ConcreteClassB extends AbstractSuperClass{
    public abstract void someAbstractMethod(){
        // Do something
    }

    public static String getSomeText(){ 
        return "This is ConcreteClassB"; 
    };
}



AbstractSuperClass.getConcreteClass(1).getSomeText(); // Should return "This is ConcreteClassB"

Is this simply impossible in Java, or am I just doing it wrong?

回答1:

I'm not sure what it is needed for, but there are several issues in your code.

  1. You can not return Class because you are returning a class that extends the AbstractSuperClass. So you have to inform the compiler about it and change the return type to Class
  2. You can not call the any (doesn't matter static or not static method) directly from class. You need to use the reflections API, so you method call will look like this:

    final Class clazz = AbstractSuperClass.getConcreteClass(1); Object result = clazz.getMethod("getSomeText").invoke(clazz);

So that's the complete code of you class.

package com.mycompany.sandbox;

import java.lang.reflect.InvocationTargetException;
import java.util.Objects;
import java.util.logging.Level;
import java.util.logging.Logger;


abstract class AbstractSuperClass{

    public abstract void someAbstractMethod();


    public static String getSomeText(){ 
        return "This is AbstractSuperClass"; 
    };

    public static Class<? extends AbstractSuperClass> getConcreteClass(int x){
        switch( x ){
            case 0: return ConcreteClassA.class;
            case 1: return ConcreteClassB.class;
        }
      return null;
    }
}


class ConcreteClassA extends AbstractSuperClass{
    public void someAbstractMethod(){
        // Do something
    }

    public static String getSomeText(){ 
        return "This is ConcreteClassA"; 
    };
}



class ConcreteClassB extends AbstractSuperClass{
    public void someAbstractMethod(){
        // Do something
    }

    public static String getSomeText(){ 
        return "This is ConcreteClassB"; 
    };
}




// one class needs to have a main() method
public class HelloWorld
{
  // arguments are passed using the text field below this editor
  public static void main(String args[])
  {
      try {
          final Class<? extends AbstractSuperClass> clazz = AbstractSuperClass.getConcreteClass(1);
          Object result = clazz.getMethod("getSomeText").invoke(clazz);
          System.out.println(Objects.toString(result, "<NULL>"));
      } catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
          Logger.getLogger(HelloWorld.class.getName()).log(Level.SEVERE, null, ex);
      }
  }
}


回答2:

You can't invoke a static method directly from a Class like you are trying to do, you will need reflection to invoke your getSomeText() method. Like,

try {
    Method m = AbstractSuperClass.getConcreteClass(1).getMethod("getSomeText", 
            new Class[0]);
    System.out.println(m.invoke(null, new Object[0]));
} catch (Exception e) {
    e.printStackTrace();
}

Then you can fix your getConcreteClass method by using capture-of extends AbstractSuperClass (producer extends, consumer super). And you must have a default return for the case where nothing matches. Like,

public static Class<? extends AbstractSuperClass> getConcreteClass(int x) {
    switch (x) {
    case 0: return ConcreteClassA.class;
    case 1: return ConcreteClassB.class;
    }
    return null;
}

Which I ran (and got)

This is ConcreteClassB


回答3:

For this method,

public Class<AbstractSuperClass> getConcreteClass(int x){
    switch( x ){
        case 0: return ConcreteClassA.class;
        case 1: return ConcreteClassB.class;
    }
}

the signature should be

public Class<? extends AbstractSuperClass> getConcreteClass(int x)

which means that the return value can be AbstractSuperClass.class or any subtype of it.



回答4:

Try this:

public abstract class AbstractSuperClass {

    public abstract void someAbstractMethod();

    public static String getSomeText() { 
        return "This is AbstractSuperClass"; 
    };

    public Class<? extends AbstractSuperClass> getConcreteClass() {
        return this.getClass();
    }
}

In fact you don't need the int parameter as the instance of the subclass will know its type without the need of a hint.