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


Це не має жодного сенсу. Ви хочете, щоб ім'я змінної було типом для передачі рядка в рядок? Що?
cletus

3
Я не знаю відповіді, але я боюся, що це може стати пеклом технічного обслуговування ... Просто вивчаючи Java самостійно, я б уникав ситуацій, які вимагають такого підходу. Я майже впевнений, що б ви не робили, можна реалізувати кращим чином ... лише мої 2 центи.
Sejanus

добре, я надам більше інформації про те, чого я намагаюся досягти.
ufk

також оновив мою відповідь нижче!
user85421

Відповіді:


14

Що стосується вашого оновлення, єдиним способом вирішити це на Java є написання коду, який охоплює всі випадки з великою кількістю ifі elseі instanceofвиразів. Те, що ви намагаєтесь зробити, виглядає так, ніби використовується для програмування на динамічних мовах. У статичних мовах те, що ви намагаєтеся зробити, майже неможливо, і, мабуть, можна було б вибрати зовсім інший підхід до того, що ви намагаєтесь зробити. Статичні мови просто не такі гнучкі, як динамічні :)

Хорошими прикладами найкращих практик Java є відповідь BalusC (тобто ObjectConverter) та відповідь Andreas_D (тобто 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 fine
George Xavier

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)


22

Для цього вам потрібно буде написати щось на зразок ObjectConverter. Це здійсненно, якщо у вас є і об’єкт, який ви хочете перетворити, і ви знаєте цільовий клас, в який ви хочете перетворити. У цьому конкретному випадку ви можете отримати цільовий клас за Field#getDeclaringClass().

Ви можете знайти тут приклад такого ObjectConverter. Це має дати вам базову ідею. Якщо ви хочете більше можливостей перетворення, просто додайте до нього більше методів із потрібним аргументом і типом повернення.


@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

Ви можете написати такий простий метод, як наведений нижче.

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

}

Так, я думаю, що ця відповідь - це те, що хоче запитувач. Він / вона просто отримує клас <?> І хоче перетворити екземпляр у клас "?". За замовчуванням java цього не підтримує. Але за допомогою <T> ми можемо це зробити.
ruiruige1991

@ ruiruige1991 Це неправильно. Т у цьому випадку є загальним. Дженерики нічого не роблять під час виконання. (T) blah просто буде (Object) бла під час виконання через стирання типу. Коротше кажучи, дженерики -> час компіляції, і не впливає на час роботи. Оскільки динамічний -> час виконання та загальні засоби -> час компіляції, дженерики марні.
Джордж Ксав'єр

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: щодо редагування, яке ви постійно пропонуєте: зауважте, що я говорю не про примітивні значення, а про класи обгортки (що позначаються великими літерами та стилем як код), і перекидання між ними точно неможливе.
Michael Borgwardt

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 охоплюються цим підходом. Я використовував це із значним успіхом у виробничих проектах, де існує величезна кількість довільних вводів значення рядка, де потрібна була більш сувора перевірка. У цьому підході, якщо немає методу або коли метод викликається, викидається виняток (оскільки це нелегальне значення, таке як нечислове введення у BigDecimal або нелегальний RegEx для шаблону), що забезпечує перевірку, специфічну для цільовому класу властива логіка.

У цьому є деякі мінуси:

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

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

Наведений вище метод насправді не має нічого спільного з цілими числами як об’єктами, що обертають примітиви ints. Відображення виявить це можливим кандидатом для створення цілого числа з рядка неправильно порівняно з декодом, значенням та членами конструктора - які всі підходять для більшості довільних перетворень рядків, коли ви дійсно не маєте контролю над введеними даними, а просто хочете знати, чи можна ціле число.

Щоб виправити вищезазначене, пошук методів, що викидають винятки, є гарним початком, оскільки недійсні вхідні значення, які створюють екземпляри таких об’єктів, повинні викидати виняток. На жаль, реалізації різняться залежно від того, оголошено винятки перевіреними чи ні. 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 - це статично введений lang. Однак це може вирішити деякі випадки, коли вам насправді потрібно завантажувати деякі дані різними способами, залежно від певного ідентифікатора. Крім того, частина, де ви отримуєте конструктор із параметром 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. Незважаючи на те, що він присвоєний Object, він є цілим числом, і тому поводиться як такий. Запуск методу toString запустить реалізацію Integer. Поліморфізм.
Джордж Ксав'єр

0

Просто думав, що я опублікую щось, що мені здається цілком корисним і може бути можливим для тих, хто відчуває подібні потреби.

Наступний метод був методом, який я написав для свого додатка JavaFX, щоб уникнути необхідності передачі даних, а також уникати запису, якщо екземпляр 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));
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.