java: como posso fazer conversão dinâmica de uma variável de um tipo para outro?


85

Eu gostaria de fazer uma conversão dinâmica para uma variável Java, o tipo de conversão é armazenado em uma variável diferente.

Este é o elenco normal:

 String a = (String) 5;

É isso que eu quero:

 String theType = 'String';
 String a = (theType) 5;

Isso é possível e, em caso afirmativo, como? Obrigado!

Atualizar

Estou tentando preencher uma classe com um HashMapque recebi.

Este é o construtor:

public ConnectParams(HashMap<String,Object> obj) {

    for (Map.Entry<String, Object> entry : obj.entrySet()) {
        try {
            Field f =  this.getClass().getField(entry.getKey());                
            f.set(this, entry.getValue()); /* <= CASTING PROBLEM */
        } catch (NoSuchFieldException ex) {
            log.error("did not find field '" + entry.getKey() + '"');
        } catch (IllegalAccessException ex) {
            log.error(ex.getMessage());         
        }
    }

}

O problema aqui é que algumas das variáveis ​​da classe são do tipo Double, e se o número 3 for recebido, ele o verá como Integere eu tenho problema de tipo.


Isso não faz sentido. Você quer que o nome da variável seja um tipo para converter uma string em uma string? O que?
cletus

3
Não sei uma resposta, mas temo que isso possa se tornar um inferno da manutenção ... Só estou aprendendo Java, mas evito situações que exigem uma abordagem como essa. Tenho certeza de que tudo o que você fizer pode ser implementado de uma maneira melhor ... apenas meus 2 centavos.
Sejanus

ok irei fornecer mais informações sobre o que estou tentando alcançar.
ufk

também atualizei minha resposta abaixo!
user85421

Respostas:


14

Em relação à sua atualização, a única maneira de resolver isso em Java é escrever um código que cubra todos os casos com muitos ife elseeinstanceof expressões . O que você tenta fazer parece estar acostumado a programar com linguagens dinâmicas. Em linguagens estáticas, o que você tenta fazer é quase impossível e provavelmente alguém escolheria uma abordagem totalmente diferente para o que você tenta fazer. As linguagens estáticas não são tão flexíveis quanto as dinâmicas :)

Bons exemplos das melhores práticas de Java são a resposta de BalusC (ie ObjectConverter) e a resposta de Andreas_D (ie Adapter) abaixo.


Isso não faz sentido, em

String a = (theType) 5;

o tipo de aestá estaticamente vinculado a ser, Stringportanto, não faz nenhum sentido ter uma conversão dinâmica para esse tipo estático.

PS: A primeira linha do seu exemplo poderia ser escrita como, Class<String> stringClass = String.class;mas ainda assim, você não pode usar stringClasspara converter variáveis.


Espero que a atualização que postei explique o que estou tentando fazer. Eu venho de um background de php, então talvez isso não seja possível alcançar em java.
UFC

Exatamente, em Java você não pode ser tão dinâmico, veja minha atualização também.
akuhn

Veja a resposta do BalusC abaixo, este é o comprimento (e dor) que você tem que ir ...
akuhn

Eu sei que é tarde, mas acho que ele quis dizer [o tipo] a = (o tipo) algum_objeto; o que é inútil porque você pode simplesmente fazer Object a = some_object fine
George Xavier

120

Sim, é possível usar o Reflection

Object something = "something";
String theType = "java.lang.String";
Class<?> theClass = Class.forName(theType);
Object obj = theClass.cast(something);

mas isso não faz muito sentido, pois o objeto resultante deve ser salvo em uma variável do Objecttipo. Se você precisa que a variável seja de uma determinada classe, você pode apenas lançar para essa classe.

Se você deseja obter uma determinada classe, Numberpor exemplo:

Object something = new Integer(123);
String theType = "java.lang.Number";
Class<? extends Number> theClass = Class.forName(theType).asSubclass(Number.class);
Number obj = theClass.cast(something);

mas ainda não adianta fazer isso, você pode simplesmente lançar para Number.

Casting de um objeto NÃO muda nada; é apenas a forma como o compilador o trata.
A única razão para fazer algo assim é verificar se o objeto é uma instância da classe dada ou de qualquer subclasse dela, mas seria melhor usar instanceofouClass.isInstance() .

Atualizar

de acordo com sua última atualização, o problema real é que você tem um Integerem seu HashMapque deve ser atribuído a a Double. O que você pode fazer neste caso, é verificar o tipo do campo e usar os xxxValue()métodos deNumber

...
Field f =  this.getClass().getField(entry.getKey());
Object value = entry.getValue();
if (Integer.class.isAssignableFrom(f.getType())) {
    value = Integer.valueOf(((Number) entry.getValue()).intValue());
} else if (Double.class.isAssignableFrom(f.getType())) {
    value = Double.valueOf(((Number) entry.getValue()).doubleValue());
} // other cases as needed (Long, Float, ...)
f.set(this, value);
...

(não tenho certeza se gosto da ideia de ter o tipo errado no Map)


22

Você precisará escrever mais ou menos ObjectConverterpara isso. Isso é possível se você tiver o objeto que deseja converter e souber a classe de destino para a qual deseja converter. Neste caso particular, você pode obter a classe alvoField#getDeclaringClass() .

Você pode encontrar aqui um exemplo de tal ObjectConverter. Deve dar a você uma ideia básica. Se você quiser mais possibilidades de conversão, basta adicionar mais métodos com o argumento e tipo de retorno desejados.


@BalusC - Acho o código ObjectConverter interessante, você poderia descrever os casos de uso para ele?
srini.venigalla

É útil nos casos em que a convenção em vez da configuração é preferida e o tipo de origem não corresponde ao tipo de destino. Usei-o há 2-3 anos em meus frameworks ORM e MVC (puros para propósitos de hobby). Veja também o texto principal do artigo do blog.
BalusC de

12

Você pode fazer isso usando o Class.cast()método, que converte dinamicamente o parâmetro fornecido para o tipo de instância de classe que você possui. Para obter a instância da classe de um determinado campo, você usa o getType()método no campo em questão. Dei um exemplo abaixo, mas observe que ele omite todo o tratamento de erros e não deve ser usado sem modificações.

public class Test {

    public String var1;
    public Integer var2;
}

public class Main {

    public static void main(String[] args) throws Exception {
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("var1", "test");
        map.put("var2", 1);

        Test t = new Test();

        for (Map.Entry<String, Object> entry : map.entrySet()) {
            Field f = Test.class.getField(entry.getKey());

            f.set(t, f.getType().cast(entry.getValue()));
        }

        System.out.println(t.var1);
        System.out.println(t.var2);
    }
}

1
E se o tipo de entrada não for um supertipo do tipo de campo? Então, você realmente precisará converter programaticamente.
BalusC

7

Você pode escrever um castMethod simples como o mostrado abaixo.

private <T> T castObject(Class<T> clazz, Object object) {
  return (T) object;
}

Em seu método, você deve usá-lo como

public ConnectParams(HashMap<String,Object> object) {

for (Map.Entry<String, Object> entry : object.entrySet()) {
    try {
        Field f =  this.getClass().getField(entry.getKey());                
        f.set(this, castObject(entry.getValue().getClass(), entry.getValue()); /* <= CASTING PROBLEM */
    } catch (NoSuchFieldException ex) {
        log.error("did not find field '" + entry.getKey() + '"');
    } catch (IllegalAccessException ex) {    
        log.error(ex.getMessage());          
    }    
}

}

Sim, acho que essa resposta é o que o autor da pergunta deseja. Ele / ela acabou de obter uma Classe <?> E deseja converter uma instância para a classe "?". Por padrão, o java não oferece suporte para isso. Mas usando <T> podemos fazer isso.
ruiruige1991

@ ruiruige1991 Isso está errado. Nesse caso, é um genérico. Os genéricos não fazem nada durante o tempo de execução. (T) blá seria apenas (Objeto) blá durante o tempo de execução por causa do apagamento de tipo. Resumindo, genéricos -> tempo de compilação e não tem efeito durante o tempo de execução. Uma vez que dinâmico -> tempo de execução e genéricos -> tempo de compilação, os genéricos são inúteis.
George Xavier de

5

Funciona e há até um padrão comum para sua abordagem: o padrão Adaptador . Mas é claro, (1) isso não funciona para converter primitivos java em objetos e (2) a classe deve ser adaptável (geralmente implementando uma interface personalizada).

Com este padrão, você poderia fazer algo como:

Wolf bigBadWolf = new Wolf();
Sheep sheep = (Sheep) bigBadWolf.getAdapter(Sheep.class);

e o método getAdapter na classe Wolf:

public Object getAdapter(Class clazz) {
  if (clazz.equals(Sheep.class)) {
    // return a Sheep implementation
    return getWolfDressedAsSheep(this);
  }

  if (clazz.equals(String.class)) {
    // return a String
    return this.getName();
  }

  return null; // not adaptable
}

Para você, ideia especial - isso é impossível. Você não pode usar um valor String para lançar.


2

Seu problema não é a falta de "casting dinâmico". Transmitir Integerpara Doublenão é possível. Você parece querer dar ao Java um objeto de um tipo, um campo de um tipo possivelmente incompatível, e fazer com que ele, de alguma forma, descubra automaticamente como converter entre os tipos.

Esse tipo de coisa é um anátema para uma linguagem fortemente tipada como Java e IMO por boas razões.

O que realmente você está tentando fazer? Todo esse uso de reflexão parece muito suspeito.


@name: com relação à edição que você continua sugerindo: observe que não estou falando sobre valores primitivos, mas sobre as classes de invólucro (representadas pela capitalização e o estilo do código), e a conversão entre eles definitivamente não é possível.
Michael Borgwardt

1

Não faça isso. Basta ter um construtor devidamente parametrizado. O conjunto e os tipos de parâmetros de conexão são fixos de qualquer maneira, portanto, não há nenhum ponto em fazer isso tudo dinamicamente.


1

Pelo que vale a pena, a maioria das linguagens de script (como Perl) e linguagens de tempo de compilação não estática (como Pick) oferecem suporte a conversões de objetos dinâmicos em tempo de execução automático para (relativamente arbitrários). Isso PODE ser realizado em Java também sem perder a segurança de tipo e as boas linguagens tipadas estaticamente fornecem SEM os efeitos colaterais desagradáveis ​​de algumas das outras linguagens que fazem coisas más com a conversão dinâmica. Um exemplo Perl que faz algumas contas questionáveis:

print ++($foo = '99');  # prints '100'
print ++($foo = 'a0');  # prints 'a1'

Em Java, isso é melhor realizado (IMHO) usando um método que chamo de "cross-casting". Com a conversão cruzada, a reflexão é usada em um cache lento de construtores e métodos que são descobertos dinamicamente por meio do seguinte método estático:

Object fromString (String value, Class targetClass)

Infelizmente, nenhum método Java integrado, como Class.cast () fará isso para String para BigDecimal ou String para Integer ou qualquer outra conversão onde não há hierarquia de classes de suporte. De minha parte, o objetivo é fornecer uma maneira totalmente dinâmica de conseguir isso - para a qual não acho que a referência anterior seja a abordagem certa - ter que codificar cada conversão. Simplificando, a implementação é apenas para cast-from-string se for legal / possível.

Portanto, a solução é a simples reflexão em busca de membros públicos de:

STRING_CLASS_ARRAY = (nova classe [] {String.class});

a) Membro membro = targetClass.getMethod (method.getName (), STRING_CLASS_ARRAY); b) Membro membro = targetClass.getConstructor (STRING_CLASS_ARRAY);

Você descobrirá que todos os primitivos (Integer, Long, etc) e todos os básicos (BigInteger, BigDecimal, etc) e até mesmo java.regex.Pattern são cobertos por essa abordagem. Eu usei isso com sucesso significativo em projetos de produção onde há uma grande quantidade de entradas de valor de String arbitrárias onde alguma verificação mais estrita era necessária. Nesta abordagem, se não houver método ou quando o método é invocado, uma exceção é lançada (porque é um valor ilegal, como uma entrada não numérica para um BigDecimal ou RegEx ilegal para um Padrão), que fornece a verificação específica para a lógica inerente da classe de destino.

Existem algumas desvantagens para isso:

1) Você precisa entender bem a reflexão (isso é um pouco complicado e não para novatos). 2) Algumas das classes Java e, de fato, bibliotecas de terceiros (surpresa) não são codificadas corretamente. Ou seja, existem métodos que recebem um único argumento de string como entrada e retornam uma instância da classe de destino, mas não é o que você pensa ... Considere a classe Integer:

static Integer getInteger(String nm)
      Determines the integer value of the system property with the specified name.

O método acima realmente não tem nada a ver com inteiros como objetos que envolvem inteiros primitivos. O Reflection descobrirá que isso é um possível candidato para a criação incorreta de um inteiro a partir de uma string em comparação com os membros decodificadores, valueof e constructor - que são adequados para a maioria das conversões de strings arbitrárias em que você realmente não tem controle sobre seus dados de entrada, mas apenas deseja saber se é possível um Inteiro.

Para remediar o acima, procurar métodos que lançam Exceptions é um bom começo porque valores de entrada inválidos que criam instâncias de tais objetos devem lançar uma Exception. Infelizmente, as implementações variam quanto ao fato de as exceções serem declaradas como verificadas ou não. Integer.valueOf (String) lança uma NumberFormatException marcada, por exemplo, mas as exceções Pattern.compile () não são encontradas durante as pesquisas de reflexão. Novamente, não é uma falha dessa abordagem dinâmica de "cross-casting", eu acho que é uma implementação muito diferente do padrão para declarações de exceção em métodos de criação de objeto.

Se alguém quiser mais detalhes sobre como o acima foi implementado, me avise, mas eu acho que essa solução é muito mais flexível / extensível e com menos código sem perder as partes boas da segurança de tipo. Obviamente, é sempre melhor "conhecer seus dados", mas, como muitos de nós descobrimos, às vezes somos apenas destinatários de conteúdo não gerenciado e temos que fazer o melhor que podemos para usá-lo adequadamente.

Felicidades.


1

Então, esse é um post antigo, porém acho que posso contribuir com algo para ele.

Você sempre pode fazer algo assim:

package com.dyna.test;  

import java.io.File;  
import java.lang.reflect.Constructor;  

public class DynamicClass{  

  @SuppressWarnings("unchecked")  
  public Object castDynamicClass(String className, String value){  
    Class<?> dynamicClass;  

    try  
    {  
      //We get the actual .class object associated with the specified name  
      dynamicClass = Class.forName(className);  



    /* We get the constructor that received only 
     a String as a parameter, since the value to be used is a String, but we could
easily change this to be "dynamic" as well, getting the Constructor signature from
the same datasource we get the values from */ 


      Constructor<?> cons =  
        (Constructor<?>) dynamicClass.getConstructor(new Class<?>[]{String.class});  

      /*We generate our object, without knowing until runtime 
 what type it will be, and we place it in an Object as 
 any Java object extends the Object class) */  
      Object object = (Object) cons.newInstance(new Object[]{value});  

      return object;  
    }  
    catch (Exception e)  
    {  
      e.printStackTrace();  
    }  
    return null;  
  }  

  public static void main(String[] args)  
  {   
    DynamicClass dynaClass = new DynamicClass();  

    /* 
 We specify the type of class that should be used to represent 
 the value "3.0", in this case a Double. Both these parameters 
 you can get from a file, or a network stream for example. */  
    System.out.println(dynaClass.castDynamicClass("java.lang.Double", "3.0"));  

    /* 
We specify a different value and type, and it will work as 
 expected, printing 3.0 in the above case and the test path in the one below, as the Double.toString() and 
 File.toString() would do. */  
    System.out.println(dynaClass.castDynamicClass("java.io.File", "C:\\testpath"));  
  }  

Claro, isso não é realmente uma conversão dinâmica, como em outras linguagens (Python, por exemplo), porque java é uma linguagem de tipo estático. No entanto, isso pode resolver alguns casos marginais em que você realmente precisa carregar alguns dados de maneiras diferentes, dependendo de algum identificador. Além disso, a parte em que você obtém um construtor com um parâmetro String provavelmente poderia ser mais flexível, tendo esse parâmetro passado da mesma fonte de dados. Ou seja, de um arquivo, você obtém a assinatura do construtor que deseja usar e a lista de valores a serem usados, dessa forma você emparelha, digamos, o primeiro parâmetro é uma String, com o primeiro objeto, convertendo-o como uma String, próximo objeto é um inteiro, etc, mas em algum momento ao longo da execução de seu programa, você obtém agora um objeto File primeiro, depois um Double, etc.

Dessa forma, você pode levar em conta esses casos e fazer um casting um tanto "dinâmico" em tempo real.

Espero que isso ajude alguém, pois isso continua aparecendo nas pesquisas do Google.


0

Recentemente, senti que precisava fazer isso também, mas descobri outra maneira que possivelmente torna meu código mais organizado e usa POO melhor.

Tenho muitas classes irmãs em que cada uma implementa um determinado método doSomething() . Para acessar esse método, eu teria que ter uma instância dessa classe primeiro, mas criei uma superclasse para todas as minhas classes irmãs e agora posso acessar o método a partir da superclasse.

Abaixo eu mostro duas formas alternativas de "casting dinâmico".

// Method 1.
mFragment = getFragmentManager().findFragmentByTag(MyHelper.getName(mUnitNum));
switch (mUnitNum) {
case 0:
    ((MyFragment0) mFragment).sortNames(sortOptionNum);
    break;
case 1:
    ((MyFragment1) mFragment).sortNames(sortOptionNum);
    break;
case 2:
    ((MyFragment2) mFragment).sortNames(sortOptionNum);
    break;
}

e meu método usado atualmente,

// Method 2.
mSuperFragment = (MySuperFragment) getFragmentManager().findFragmentByTag(MyHelper.getName(mUnitNum));
mSuperFragment.sortNames(sortOptionNum);

Você não deve precisar lançar dinamicamente nunca. Sua fórmula de ter uma superclasse com o método doSomething () e subclasses que implementam o método doSomething () é um dos principais propósitos da OOP, polimorfismo. Objeto foo = novo Inteiro ("1"); foo.toString () retorna 1. Embora seja atribuído a Object, é um Integer e, portanto, se comporta como tal. Executar o método toString executará a implementação Integer. Polimorfismo.
George Xavier de

0

Só pensei em postar algo que achei bastante útil e que poderia ser possível para alguém que tem necessidades semelhantes.

O método a seguir foi um método que escrevi para meu aplicativo JavaFX para evitar ter que lançar e também evitar escrever se objeto x instância de instruções de objeto b toda vez que o controlador for retornado.

public <U> Optional<U> getController(Class<U> castKlazz){
    try {
        return Optional.of(fxmlLoader.<U>getController());
    }catch (Exception e){
        e.printStackTrace();
    }
    return Optional.empty();
}

A declaração do método para obter o controlador foi

public <T> T getController()

Usando o tipo U passado para o meu método por meio do objeto de classe, ele poderia ser encaminhado para o controlador get do método para dizer que tipo de objeto retornar. Um objeto opcional é retornado no caso de a classe errada ser fornecida e uma exceção ocorrer, caso em que um opcional vazio será retornado, o qual podemos verificar.

Isso é o que a chamada final para o método parecia (se presente do objeto opcional retornado leva um consumidor

getController(LoadController.class).ifPresent(controller->controller.onNotifyComplete());

0

Experimente isso para o Casting dinâmico. Vai funcionar!!!

    String something = "1234";
    String theType = "java.lang.Integer";
    Class<?> theClass = Class.forName(theType);
    Constructor<?> cons = theClass.getConstructor(String.class);
    Object ob =  cons.newInstance(something);
    System.out.println(ob.equals(1234));
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.