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


Я хотел бы сделать динамическое приведение для переменной 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());         
        }
    }

}

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

13 67

13 ответов:

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

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


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

String a = (theType) 5;

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

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

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

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

обновление

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

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

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

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

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

вы можете сделать это с помощью 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) он не работает для приведения примитивов 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
}

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

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

}

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

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

что вы на самом деле пытаетесь сделать? Все, что использование отражения выглядит довольно рыбный.

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

для чего это стоит, большинство языков сценариев (например, 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.класс});

а) член = targetClass.getMethod(метод.getName (), STRING_CLASS_ARRAY); b) Member member = targetClass.getConstructor (STRING_CLASS_ARRAY);

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

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

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

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

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

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

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

ваше здоровье.

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

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

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-это статически типизированный язык. Однако это может решить некоторые крайние случаи, когда вам действительно нужно загрузить некоторые данные по-разному, в зависимости от некоторого идентификатора. Кроме того, часть, где вы получаете конструктор со строкой параметр, вероятно, можно сделать более гибким, передав этот параметр из того же источника данных. Т. е. из файла вы получаете подпись конструктора, которую вы хотите использовать, и список значений, которые будут использоваться, таким образом, вы соединяете, скажем, первый параметр-это строка, с первым объектом, бросая его как строку, следующий объект-целое число и т. д., Но когда-то по ходу выполнения вашей программы вы получаете сначала объект файла, затем Двойной и т. д.

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

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

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

у меня есть много родственных классов, каждый из которых реализует определенный метод 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);

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

следующий метод был методом, который я написал для своего приложения JavaFX, чтобы избежать необходимости бросать, а также избегать записи, если экземпляр объекта 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 controller, чтобы сказать ему, какой тип объекта для возврата. Необязательный объект возвращается в случае, если указан неверный класс, и возникает исключение, в котором будет возвращен пустой необязательный объект, который мы можем проверить.

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

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

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

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