Reflection e herança[Resolvido]

6 respostas
D

Olá pessoal,

Estou usando reflection e abstraí o problema que estou tendo para o seguinte conjunto de classes para facilitar o entendimento:

package reflectiontest;

public abstract class Xpto {


}
package reflectiontest;

public class XptoChild extends Xpto {


}
package reflectiontest;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;

public class Foo {
	private Xpto xpto;

	public Foo(Xpto xpto){
		this.xpto = xpto;
	}
	
	public static void main(String[] args) throws SecurityException, IllegalArgumentException, ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
		Xpto xpto = (Xpto) createInstance(XptoChild.class, new Object[0]);
		Foo foo = (Foo) createInstance(Foo.class, new Object[] { xpto });
		
		System.out.println("test");
	}
	
	private static Object createInstance(Class<?> classDefinition, Object ... args) throws ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException {
		if (args != null && args.length > 0){
			Class<?>[] parameterTypes = new Class[args.length];
			
			for (int i = 0; i < args.length; i++){
				parameterTypes[i] = args[i].getClass();
			}			
			
			Constructor<?> constructor = classDefinition.getDeclaredConstructor(parameterTypes);
			return constructor.newInstance(args);
			
		}else{
			return classDefinition.newInstance();
		}
	}
}

Estou recebendo o seguinte erro:
[color=red]Exception in thread "main" java.lang.NoSuchMethodException: reflectiontest.Foo.(reflectiontest.XptoChild)
at java.lang.Class.getConstructor0(Class.java:2706)
at java.lang.Class.getDeclaredConstructor(Class.java:1985)
at reflectiontest.Foo.createInstance(Foo.java:28)
at reflectiontest.Foo.main(Foo.java:15)[/color]

Por reflection, o construtor não pode ser encontrado uma vez que o parâmetro que estou passando (XptoChild) não é identificado como classe filha de Xpto (Parâmetro do construtor da classe Foo).

Obrigado pela atenção e até mais,

6 Respostas

E

De fato, o construtor Foo (Xpto) existe mas não existe o construtor Foo (XptoChild), que é o que você procurou. Você teria de procurar um construtor compatível (em vez de um construtor exatamente igual) para poder chamá-lo.

A

Coincidência isso…

Essa semana estava lendo um artigo sobre linguagens dinâmicas que citava como vantagem chamar um método pelo nome.

Lembrei do CallByName do “finado” VB6 e comecei a implementar um método assim no Java…

Fiz um código muito semelhante ao seu (para invocar métodos ao invés de criar instâncias), e parei justamente no mesmo ponto…

Comecei a considerar as soluções possíveis, mas lembrei que tinha um deadline a cumprir, e voltei para o trabalho.

Vou acompanhar este tópico para ver uma solução prática para isso.

E

O que você deve fazer é o seguinte:

a) Você quer o construtor que recebe um parâmetro, cujo tipo é XptoChild.

b) Você receberá uma exceção, porque tal construtor não foi definido, conforme você mesmo constatou. Se ocorrer isso, você terá de encontrar um construtor “compatível” (ou seja, que tenha o mesmo número de parâmetros, e cujos tipos dos parâmetros satisfaçam a condição “isAssignableFrom”. Obviamente isso leva um certo tempo, provavelmente você terá de fazer um “cache” disso se esse construtor tiver de ser encontrado várias vezes seguidas.

http://download.oracle.com/javase/7/docs/api/java/lang/Class.html#getDeclaredConstructors()
http://download.oracle.com/javase/7/docs/api/java/lang/reflect/Constructor.html#getParameterTypes()
http://download.oracle.com/javase/7/docs/api/java/lang/Class.html#isAssignableFrom(java.lang.Class)

Note que os outros métodos da java.lang.Class (como getDeclaredMethods) também são tão restritivos quanto getDeclaredConstructors, ou seja, não acham os métodos que tenham parâmetros “compatíveis” mas sim os que têm parâmetros com os tipos exatos.

D

Obrigado pessoal pelos comentários.

Eu só vi a primeira mensagem. Acabei de ver as demais. Acredito que por melhorias do GUJ, não é enviado um e-mail para cada mensagem.

Consegui resolver buscando o construtor compatível. Segue o código:

private static Object createInstance(Class<?> classDefinition, Object ... args) throws ClassNotFoundException, SecurityException, NoSuchMethodException, IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException {		
		if (args != null && args.length > 0){
			Class<?>[] parameterTypes = new Class[args.length];
			
			for (int i = 0; i < args.length; i++){
				parameterTypes[i] = args[i].getClass();
			}			
			
			Constructor<?> constructor = null;
			try {
				constructor = classDefinition.getDeclaredConstructor(parameterTypes);
				return constructor.newInstance(args);
			} catch (Exception e) {
				e.printStackTrace();
			}
			
			Constructor<?>[] constructors = classDefinition.getConstructors();
			for (Constructor<?> c : constructors) {
				Class<?>[] constructorParameters = c.getParameterTypes();
				
				if (parameterTypes != null && constructorParameters != null && parameterTypes.length == constructorParameters.length){
					boolean ok = true;
					for (int i = 0; i < constructorParameters.length; i++) {
						if (!constructorParameters[i].isAssignableFrom(parameterTypes[i])){
							ok = false;
							break;
						}
					}
					
					if (ok){
						try {
							Object ret = c.newInstance(args);
							return ret;
						} catch (Exception e) {
							e.printStackTrace();
						}
					}
				}
				
			}
			
			return null;
		}else{
			return classDefinition.newInstance();
		}
	}

Eu tenho que marcar este tópico como resolvido?

Até mais e mais uma vez obrigado pela colaboração!

A

danilomunoz , para marcar como resolvido edite o título do seu primeiro post com a tag [Resolvido].

Mas antes disso uma observação, que pode ou não ser um problema para você.

No código que você fez, apesar de achar um construtor válido, nem sempre ele achará o melhor construtor possível.

Imagine que sua classe Foo esteja assim:

public class Foo {  
    private Xpto xpto;  

    public Foo(Object o) {
      System.out.println("Um construtor mais genérico");
    }
  
    public Foo(Xpto xpto){  
        System.out.println("Um construtor mais específico");
        this.xpto = xpto;  
    }  
}

Dependendo da ordem dos construtores (por exemplo) ou de outros fatores (não exatamente especificados no javadoc do método getDeclaredConstructors), você pode instanciar a classe com o construtor mais genérico ou mais específico.

Se isso será um problema ou não para o seu caso eu não sei dizer.

D

AbelBueno, obrigado pela observação. Eu já tinha pensado nisso, mas pra mim isso não vai ser problema. Vou assumir este risco…

Obrigado!

Criado 19 de outubro de 2011
Ultima resposta 20 de out. de 2011
Respostas 6
Participantes 3