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 ответов
относительно вашего обновления, единственный способ решить эту проблему в Java написать код, который охватывает все случаи с большим количеством
if
иelse
иinstanceof
выражения. То, что вы пытаетесь сделать, похоже, используется для программирования с динамическими языками. В статических языках то, что вы пытаетесь сделать, почти невозможно, и, вероятно, для того, что вы пытаетесь сделать, можно выбрать совершенно другой подход. Статические языки просто не так гибки, как динамические:)хорошими примерами лучшей практики Java являются ответ BalusC (ie
ObjectConverter
) и ответ Andreas_D (ieAdapter
) ниже.
это не имеет смысла, в
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 в BigDecimal или String в Integer или любого другого преобразования, где нет поддерживающей иерархии классов. Для моего часть, дело в том, чтобы обеспечить полностью динамический способ достижения этого - для которого я не думаю, что предыдущая ссылка является правильным подходом - кодировать каждое преобразование. Проще говоря, реализация - это просто cast-from-string, если это законно / возможно.
таким образом, решение простое отражение ищет публичных членов либо:
STRING_CLASS_ARRAY = (новый класс [] {String.class});
a) член = targetClass.getMethod (method.метод getname(),STRING_CLASS_ARRAY); b) Member member = targetClass.getConstructor (STRING_CLASS_ARRAY);
вы обнаружите, что все примитивы (целочисленные, длинные и т. д.) и все основы (BigInteger, BigDecimal и т. д.) И даже java.регулярное выражение.Шаблон все покрыты с помощью этого подхода. Я использовал это со значительным успехом в производственных проектах, где есть огромное количество произвольных строковых значений, где необходима более строгая проверка. В этот подход, если нет метода или при вызове метода создается исключение (поскольку это незаконное значение, такое как нечисловой ввод в BigDecimal или незаконное регулярное выражение для шаблона), предоставляет проверку, специфичную для логики целевого класса.
есть некоторые недостатки в этом:
1) вам нужно хорошо понимать отражение (это немного сложно и не для новичков). 2) некоторые классы Java и действительно сторонние библиотеки (сюрприз) неправильно закодировано. То есть существуют методы, которые принимают один строковый аргумент в качестве входного и возвращают экземпляр целевого класса, но это не то, что вы думаете... Рассмотрим класс Integer:
static Integer getInteger(String nm)
Determines the integer value of the system property with the specified name.
вышеуказанный метод действительно не имеет ничего общего с целыми числами как объектами, обертывающими примитивы ints. Отражение найдет это как возможный кандидат для создания целого числа из строки неправильно по сравнению с членами decode, valueof и constructor - которые все подходит для большинства произвольных преобразований строк, где вы действительно не контролируете свои входные данные, но просто хотите знать, возможно ли целое число.
чтобы исправить вышеизложенное, поиск методов, которые вызывают исключения, является хорошим началом, потому что недопустимые входные значения, которые создают экземпляры таких объектов должны бросать исключение. К сожалению, реализации различаются в зависимости от того, объявлены ли исключения как проверенные или нет. Целое число.valueOf (строка) выбрасывает проверено 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 является статически типизированным lang. Однако это может решить некоторые пограничные случаи, когда вам действительно нужно загрузить некоторые данные по-разному, в зависимости от идентификатора. Кроме того, часть, где вы получаете конструктор со строкой параметр, вероятно, можно сделать более гибким, передав этот параметр из того же источника данных. Т. е. из файла вы получаете сигнатуру конструктора, которую хотите использовать, и список используемых значений, таким образом, вы соединяете, скажем, первый параметр-это строка, с первым объектом, бросая его как строку, следующий объект-целое число и т. д., Но что-то по ходу выполнения вашей программы вы получаете сначала объект файла, затем Двойной и т. д.
таким образом, вы можете учитывайте эти случаи и сделайте несколько "динамичный" кастинг на лету.
надеюсь, это поможет кому-нибудь, так как это продолжает появляться в поиске 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, чтобы избежать необходимости бросать, а также избегать записи, если экземпляр object x операторов object 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, чтобы сообщить ему, какой тип объекта вернуть. Необязательный объект возвращается, если указан неправильный класс и возникает исключение, в этом случае возвращается пустой необязательный объект, который мы можем проверить.
это то, как выглядел окончательный вызов метода (если present возвращаемого необязательного объекта принимает Consumer
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));