Java: как я могу сделать динамическое приведение переменной от одного типа к другому? - PullRequest
70 голосов
/ 24 января 2010

Я хотел бы выполнить динамическое приведение для переменной Java, тип преобразования хранится в другой переменной.

это обычный кастинг:

 String a = (String) 5;

вот что я хочу:

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

это возможно? и если да, то как? спасибо!

обновление

Я пытаюсь заполнить класс полученной хэш-картой.

это конструктор:

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());         
        }
    }

}

проблема здесь в том, что некоторые переменные классов имеют тип Double, и если получено число 3, оно видит его как Integer, и у меня возникает проблема типа

Ответы [ 13 ]

99 голосов
/ 24 января 2010

Да, это возможно, используя Отражение

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

но это не имеет особого смысла, поскольку полученный объект должен быть сохранен в переменной типа Object. Если вам нужна переменная определенного класса, вы можете просто привести ее к этому классу.

Если вы хотите получить данный класс, укажите номер, например:

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);

но все равно нет смысла делать это, вы можете просто привести к Номеру.

Приведение объекта НЕ меняет ничего; это просто то, как компилятор обрабатывает это.
Единственная причина, по которой можно сделать что-то подобное, - это проверить, является ли объект экземпляром данного класса или любого его подкласса, но это было бы лучше сделать, используя instanceof или Class.isInstance().

Обновление

в соответствии с вашим последним обновлением реальная проблема заключается в том, что у вас есть целое число в вашей хэш-карте, которое должно быть присвоено двойному. В этом случае вы можете проверить тип поля и использовать xxxValue() методы Number

.
...
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);
...

(не уверен, нравится ли мне идея иметь неправильный тип на карте)

20 голосов
/ 24 января 2010

Для этого вам нужно написать что-то вроде ObjectConverter.Это выполнимо, если у вас есть и объект, который вы хотите преобразовать, и вы знаете целевой класс, в который вы хотите преобразовать.В данном конкретном случае вы можете получить целевой класс по Field#getDeclaringClass().

. Вы можете найти здесь пример такого ObjectConverter.Это должно дать вам основную идею.Если вы хотите больше возможностей преобразования, просто добавьте к нему больше методов с нужным аргументом и типом возврата.

13 голосов
/ 24 января 2010

Что касается вашего обновления, единственный способ решить эту проблему на Java - это написать код, который охватывает все случаи с множеством выражений if и else и instanceof. То, что вы пытаетесь сделать, выглядит так, как будто они используются для программирования на динамических языках. В статических языках то, что вы пытаетесь сделать, почти невозможно, и вы, вероятно, выбрали бы совершенно другой подход к тому, что вы пытаетесь сделать. Статические языки не так гибки, как динамические:)

Хорошими примерами наилучшей практики Java являются ответ BalusC (т.е. ObjectConverter) и ответ Andreas_D (т.е. Adapter) ниже.


Это не имеет смысла, в

String a = (theType) 5;

тип a статически связан с String, поэтому нет смысла иметь динамическое приведение к этому статическому типу.

PS: Первая строка вашего примера может быть записана как Class<String> stringClass = String.class;, но вы не можете использовать stringClass для приведения переменных.

11 голосов
/ 24 января 2010

Вы можете сделать это с помощью метода Class.cast(), который динамически преобразует предоставленный параметр в тип имеющегося у вас экземпляра класса. Чтобы получить экземпляр класса определенного поля, вы используете метод getType() для рассматриваемого поля. Я привел пример ниже, но учтите, что он пропускает всю обработку ошибок и не должен использоваться без изменений.

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);
    }
}
6 голосов
/ 04 мая 2016

Вы можете написать простой метод CastMethod, как показано ниже.

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

В вашем методе вы должны использовать его как

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());          
    }    
}

}
4 голосов
/ 24 января 2010

Это работает, и есть даже общий шаблон для вашего подхода: Шаблон адаптера . Но, конечно, (1) он не работает для приведения java-примитивов к объектам и (2) класс должен быть адаптируемым (обычно путем реализации пользовательского интерфейса).

С этим шаблоном вы можете сделать что-то вроде:

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

и метод getAdapter в классе 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
}

Для вас особая идея - это невозможно. Вы не можете использовать строковое значение для приведения.

2 голосов
/ 24 января 2010

Ваша проблема не в отсутствии "динамического приведения". Приведение Integer к Double вообще невозможно. Кажется, вы хотите дать Java объект одного типа, поле, возможно, несовместимого типа, и заставить его каким-то образом автоматически выяснить, как преобразовывать типы.

Подобные вещи - анафема для строго типизированного языка, такого как Java, и IMO по очень веским причинам.

Что вы на самом деле пытаетесь сделать? Все это использование отражения выглядит довольно подозрительно.

1 голос
/ 07 ноября 2012

Итак, это старый пост, однако я думаю, что могу что-то внести в него.

Вы всегда можете сделать что-то вроде этого:

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"));  
  }  

Конечно, это не совсем динамическое приведение, как в других языках (например, Python), потому что java - это статически типизированный язык. Тем не менее, это может решить некоторые крайние случаи, когда вам действительно нужно загружать некоторые данные разными способами, в зависимости от некоторого идентификатора. Кроме того, часть, в которой вы получаете конструктор с параметром String, возможно, станет более гибкой, если этот параметр будет передан из того же источника данных. То есть из файла вы получаете сигнатуру конструктора, которую вы хотите использовать, и список значений, которые нужно использовать, таким образом вы соединяете, скажем, первый параметр - String, с первым объектом, который преобразуется в String, затем объект является целым числом и т. д., но когда-то во время выполнения вашей программы вы теперь получаете объект File, затем Double и т. д.

Таким образом, вы можете учесть эти случаи и сделать несколько «динамический» кастинг на лету.

Надеюсь, это кому-нибудь поможет, так как это продолжает появляться в поиске Google.

1 голос
/ 27 июня 2011

Для чего стоит, большинство языков сценариев (например, Perl) и нестатических языков времени компиляции (например, Pick) поддерживают автоматическое преобразование динамических строк в (относительно произвольные) объектов во время выполнения. Это МОЖЕТ быть выполнено и в Java без потери безопасности типов, и хорошие языки статической типизации обеспечивают БЕЗ неприятных побочных эффектов некоторых других языков, которые делают плохие вещи с динамическим приведением типов. Пример Perl, который делает сомнительную математику:

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

В Java это лучше сделать (ИМХО) с помощью метода, который я называю «перекрестное приведение». При перекрестном приведении отражение используется в ленивом кеше конструкторов и методов, которые динамически обнаруживаются с помощью следующего статического метода:

Object fromString (String value, Class targetClass)

К сожалению, никакие встроенные методы Java, такие как Class.cast (), не будут делать это для String в BigDecimal или String в Integer или любого другого преобразования, где нет поддерживаемой иерархии классов. С моей стороны, цель состоит в том, чтобы обеспечить полностью динамичный способ достижения этого - для которого я не думаю, что предыдущая ссылка является правильным подходом - необходимость кодировать каждое преобразование. Проще говоря, реализация - это просто приведение из строки, если это допустимо / возможно.

Таким образом, решение является простым отражением в поиске публичных членов:

STRING_CLASS_ARRAY = (новый класс [] {String.class});

a) Член-участник = targetClass.getMethod (method.getName (), STRING_CLASS_ARRAY); б) Member member = targetClass.getConstructor (STRING_CLASS_ARRAY);

Вы обнаружите, что все примитивы (Integer, Long и т. Д.) И все основы (BigInteger, BigDecimal и т. Д.) И даже java.regex.Pattern охватываются этим подходом. Я использовал это со значительным успехом в производственных проектах, где имеется огромное количество произвольных входных значений String, где требовалась более строгая проверка. В этом подходе, если нет метода или когда метод вызывается, генерируется исключение (потому что это недопустимое значение, такое как нечисловой ввод в BigDecimal или недопустимое RegEx для шаблона), что обеспечивает проверку, специфичную для логике целевого класса присуща

Есть некоторые недостатки:

1) Вы должны хорошо понимать рефлексию (это немного сложно и не для новичков). 2) Некоторые классы Java и действительно сторонние библиотеки (неожиданно) не написаны должным образом. То есть существуют методы, которые принимают в качестве входных данных один строковый аргумент и возвращают экземпляр целевого класса, но это не то, что вы думаете ... Рассмотрим класс Integer:

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

Приведенный выше метод на самом деле не имеет ничего общего с целыми числами как объектами, обертывающими примитивы int. Reflection найдет это в качестве возможного кандидата для неправильного создания Integer из String по сравнению с членами-декодерами, valueof и конструктором, которые подходят для большинства произвольных преобразований String, когда вы действительно не можете контролировать свои входные данные, а просто хотите знать, если это возможно целое число.

Чтобы исправить вышесказанное, поиск методов, которые генерируют исключения, является хорошим началом, потому что недопустимые входные значения, которые создают экземпляры таких объектов , должны генерировать исключение. К сожалению, реализации различаются в зависимости от того, объявлены ли исключения как проверенные или нет. Например, Integer.valueOf (String) генерирует проверенное исключение NumberFormatException, но исключения Pattern.compile () не обнаруживаются при поиске отражения. Опять же, не провал этого динамического подхода «перекрестного преобразования», я думаю, что это очень нестандартная реализация для объявлений исключений в методах создания объектов.

Если кому-то захочется больше подробностей о том, как было реализовано вышеизложенное, дайте мне знать, но я думаю, что это решение гораздо более гибкое / расширяемое и с меньшим количеством кода без потери хороших частей безопасности типов. Конечно, всегда лучше «знать ваши данные», но, как многие из нас находят, мы иногда являемся только получателями неуправляемого контента и должны делать все возможное, чтобы правильно его использовать.

Приветствие.

1 голос
/ 24 января 2010

Не делай этого. Просто используйте правильно параметризованный конструктор. Набор и типы параметров соединения в любом случае фиксированы, поэтому нет смысла делать все это динамически.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...