java: Как мне выполнить динамическое преобразование переменной из одного типа в другой?

85

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

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

 String a = (String) 5;

Вот чего я хочу:

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

Возможно ли это, и если да, то как? Благодаря!

Обновить

Я пытаюсь заполнить класс HashMapполученным мной.

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

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и у меня проблема с типом.

ufk
источник
В этом нет никакого смысла. Вы хотите, чтобы имя переменной было типом для преобразования строки в строку? Какая?
cletus
3
Я не знаю ответа, но боюсь, что это может превратиться в ад ... Просто сам изучаю Java, но я бы избегал ситуаций, требующих подобного подхода. Я почти уверен, что все, что вы делаете, можно реализовать лучше ... только мои 2 цента.
Sejanus
Хорошо, я предоставлю больше информации о том, чего я пытаюсь достичь.
ufk
также обновил мой ответ ниже!
user85421

Ответы:

14

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

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


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

String a = (theType) 5;

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

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

Акун
источник
Я надеюсь, что опубликованное мною обновление объяснит, что я пытаюсь сделать. Я исхожу из фона php, поэтому, возможно, это невозможно достичь в java.
ufk
Собственно, в Java нельзя быть таким динамичным, смотрите и мое обновление.
akuhn
См. Ответ BalusC ниже, это длина (и боль), до которой вам нужно идти ...
akuhn
Я знаю, что это поздно, но я думаю, что он имел ввиду [thetype] a = (thetype) some_object; что бессмысленно, потому что вы можете просто выполнить Object a = some_object нормально
Джордж Ксавье
120

Да, можно использовать Reflection

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

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

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

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

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

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

Обновить

в соответствии с вашим последним обновлением реальная проблема в том, что у вас есть Integerфайл, HashMapкоторый должен быть назначен на Double. Что вы можете сделать в этом случае, так это проверить тип поля и использовать 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);
...

(не уверен, нравится ли мне идея неправильного типа в Map)

user85421
источник
22

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

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

BalusC
источник
@BalusC - Мне интересен код ObjectConverter, не могли бы вы описать варианты его использования?
srini.venigalla
Это полезно в случаях, когда предпочтение отдается конфигурации, а исходный тип не соответствует целевому типу. Я использовал его 2-3 года назад в своих (чисто для хобби) фреймворках ORM и MVC. Также см. Начальный текст статьи в блоге.
BalusC
12

Вы можете сделать это с помощью 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);
    }
}
Джаред Рассел
источник
1
Что, если тип записи вообще не является супертипом типа поля? Тогда вам действительно нужно будет преобразовать программно.
BalusC
7

Вы можете написать простой метод 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());          
    }    
}

}
Ходжи
источник
Да, я думаю, что это то, что хочет спрашивающий. Он / она просто получает Class <?> И хочет преобразовать экземпляр в класс «?». По умолчанию Java не поддерживает это. Но с помощью <T> мы можем это сделать.
ruiruige1991
@ ruiruige1991 Это неправильно. T в этом случае является общим. Дженерики ничего не делают во время выполнения. (T) blah будет просто (Object) blah во время выполнения из-за стирания типа. Короче говоря, дженерики -> во время компиляции и не действуют во время выполнения. Поскольку динамический -> среда выполнения и универсальные -> время компиляции, универсальные шаблоны бесполезны.
Джордж Ксавье
5

Это работает, и есть даже общий шаблон для вашего подхода: шаблон адаптера . Но, конечно, (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
}

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

Андреас Долк
источник
2

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

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

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

Майкл Боргвардт
источник
@name: относительно редактирования, которое вы продолжаете предлагать: обратите внимание, что я говорю не о примитивных значениях, а о классах-оболочках (обозначенных заглавными буквами и стилем как кодом), и приведение между ними определенно невозможно.
Майкл Боргвардт
1

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

Банди-Т
источник
1

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

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

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

Object fromString (String value, Class targetClass)

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

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

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

а) Член-член = targetClass.getMethod (method.getName (), STRING_CLASS_ARRAY); б) Член-член = targetClass.getConstructor (STRING_CLASS_ARRAY);

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

У этого есть некоторые недостатки:

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

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

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

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

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

Ура.

Даррелл Тиг
источник
1

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

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

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.

Акапулько
источник
0

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

У меня много родственных классов, каждый из которых реализует определенный метод doSomething() . Чтобы получить доступ к этому методу, мне сначала нужно было бы иметь экземпляр этого класса, но я создал суперкласс для всех моих родственных классов, и теперь я могу получить доступ к методу из суперкласса.

Ниже я покажу два альтернативных способа «динамического кастинга».

// 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;
}

и мой текущий метод,

// Method 2.
mSuperFragment = (MySuperFragment) getFragmentManager().findFragmentByTag(MyHelper.getName(mUnitNum));
mSuperFragment.sortNames(sortOptionNum);
Анонсаж
источник
Вам не нужно когда-либо выполнять динамическое приведение. Ваша формула наличия суперкласса с методом doSomething () и подклассов, реализующих метод doSomething (), является одной из основных целей ООП, полиморфизма. Объект foo = новое целое число ("1"); foo.toString () возвращает 1. Несмотря на то, что он назначен объекту, это целое число и, следовательно, ведет себя соответствующим образом. Запуск метода toString запустит реализацию Integer. Полиморфизм.
Джордж Ксавье
0

Просто подумал, что опубликую что-то, что я нашел весьма полезным и могло быть возможно для тех, кто испытывает аналогичные потребности.

Следующий метод был методом, который я написал для своего приложения JavaFX, чтобы избежать необходимости выполнять приведение, а также избегать записи экземпляра if object x для операторов объекта b каждый раз, когда возвращается контроллер.

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

Объявление метода для получения контроллера было

public <T> T getController()

Используя тип U, переданный в мой метод через объект класса, он может быть направлен контроллеру метода get, чтобы сообщить ему, какой тип объекта нужно вернуть. Необязательный объект возвращается, если указан неправильный класс и возникает исключение, и в этом случае будет возвращен пустой необязательный объект, который мы можем проверить.

Так выглядел окончательный вызов метода (при наличии возвращаемого необязательного объекта требуется Consumer

getController(LoadController.class).ifPresent(controller->controller.onNotifyComplete());
Эладиан
источник
0

Попробуйте это для динамической трансляции. Это сработает!!!

    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));
Сунил ММ
источник