Chciałbym wykonać rzutowanie dynamiczne dla zmiennej Java, typ rzutowania jest przechowywany w innej zmiennej.
To jest zwykły casting:
String a = (String) 5;
To jest to czego chcę:
String theType = 'String';
String a = (theType) 5;
Czy to możliwe, a jeśli tak, to w jaki sposób? Dzięki!
Aktualizacja
Próbuję zapełnić klasę otrzymaną HashMap
przeze mnie.
Oto konstruktor:
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());
}
}
}
Problem polega na tym, że niektóre zmienne klasy są typu Double
i jeśli zostanie odebrana liczba 3, widzi ją jako Integer
i mam problem z typem.
Odpowiedzi:
To nie ma sensu w
String a = (theType) 5;
typ
a
jest statycznie związany,String
więc nie ma sensu dynamiczne rzutowanie na ten typ statyczny.PS: Pierwsza linia twojego przykładu może być zapisana jako,
Class<String> stringClass = String.class;
ale nadal nie możesz używaćstringClass
do rzutowania zmiennych.źródło
Tak, jest to możliwe przy użyciu Reflection
Object something = "something"; String theType = "java.lang.String"; Class<?> theClass = Class.forName(theType); Object obj = theClass.cast(something);
ale to nie ma większego sensu, ponieważ wynikowy obiekt musi być zapisany w zmiennej
Object
typu. Jeśli chcesz, aby zmienna należała do danej klasy, możesz po prostu rzutować na tę klasę.Jeśli chcesz uzyskać daną klasę,
Number
na przykład: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);
ale nadal nie ma sensu tego robić, możesz po prostu przesyłać do
Number
.Rzucenie obiektu niczego NIE zmienia; jest to po prostu sposób, w jaki traktuje to kompilator.
Jedynym powodem do zrobienia czegoś takiego jest sprawdzenie, czy obiekt jest instancją danej klasy lub jej podklasy, ale lepiej byłoby to zrobić za pomocą
instanceof
lubClass.isInstance()
.Aktualizacja
zgodnie z twoją ostatnią aktualizacją prawdziwym problemem jest to, że masz
Integer
w swoim,HashMap
który powinien być przypisany doDouble
. W takim przypadku możesz sprawdzić typ pola i skorzystać zxxxValue()
metodNumber
... 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); ...
(nie jestem pewien, czy podoba mi się pomysł wprowadzenia niewłaściwego typu
Map
)źródło
Będziesz musiał coś o
ObjectConverter
tym napisać . Jest to możliwe, jeśli masz zarówno obiekt, który chcesz przekonwertować, jak i znasz klasę docelową, na którą chcesz przekonwertować. W tym konkretnym przypadku możesz uzyskać klasę docelową przezField#getDeclaringClass()
.Możesz znaleźć tutaj przykład takiego pliku
ObjectConverter
. Powinien dać ci podstawowy pomysł. Jeśli chcesz więcej możliwości konwersji, po prostu dodaj do niego więcej metod z żądanym argumentem i zwracanym typem.źródło
Możesz to zrobić za pomocą
Class.cast()
metody, która dynamicznie rzutuje podany parametr na typ instancji klasy, którą posiadasz. Aby uzyskać instancję klasy określonego pola, należy użyćgetType()
metody w tym polu. Podałem przykład poniżej, ale pamiętaj, że pomija on wszelką obsługę błędów i nie powinien być używany bez modyfikacji.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); } }
źródło
Możesz napisać prostą metodę castMethod, taką jak ta poniżej.
private <T> T castObject(Class<T> clazz, Object object) { return (T) object; }
W swojej metodzie powinieneś używać tego jak
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()); } } }
źródło
To działa i istnieje nawet wspólny wzorzec dla twojego podejścia: wzorzec adaptera . Ale oczywiście (1) nie działa przy rzutowaniu prymitywów Java na obiekty i (2) klasa musi być adaptowalna (zwykle poprzez implementację niestandardowego interfejsu).
Z tym wzorem możesz zrobić coś takiego:
Wolf bigBadWolf = new Wolf(); Sheep sheep = (Sheep) bigBadWolf.getAdapter(Sheep.class);
oraz metodę getAdapter w klasie 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 }
Dla Ciebie wyjątkowy pomysł - to niemożliwe. Nie możesz użyć wartości String do rzutowania.
źródło
Twoim problemem nie jest brak „dynamicznego odlewania”. Przesyłanie
Integer
doDouble
nie jest w ogóle możliwe. Wydaje się, że chcesz dać Javie obiekt jednego typu, pole prawdopodobnie niezgodnego typu, i sprawić, by w jakiś sposób automatycznie wymyśliła, jak konwertować między typami.Takie rzeczy są anatemą dla silnie typizowanego języka, takiego jak Java i IMO z bardzo ważnych powodów.
Co tak naprawdę próbujesz zrobić? Całe to użycie odbicia wygląda podejrzanie.
źródło
Nie rób tego. Zamiast tego wystarczy mieć odpowiednio sparametryzowany konstruktor. Zestaw i typy parametrów połączenia są i tak stałe, więc nie ma sensu robić tego wszystkiego dynamicznie.
źródło
Co więcej, większość języków skryptowych (takich jak Perl) i niestatyczne języki kompilacji (takie jak Pick) obsługują automatyczną konwersję dynamicznych ciągów znaków w czasie wykonywania na (względnie dowolne) konwersje obiektów. MOŻNA to osiągnąć również w Javie bez utraty bezpieczeństwa typów, a dobre rzeczy w językach statycznych zapewniają BEZ nieprzyjemnych efektów ubocznych niektórych innych języków, które robią złe rzeczy z dynamicznym rzutowaniem. Przykład Perla, który wykonuje pewne wątpliwe obliczenia:
print ++($foo = '99'); # prints '100' print ++($foo = 'a0'); # prints 'a1'
W Javie lepiej jest to osiągnąć (IMHO), używając metody, którą nazywam „cross-castingiem”. W przypadku rzutowania krzyżowego odbicie jest używane w ładowanej z opóźnieniem pamięci podręcznej konstruktorów i metod, które są wykrywane dynamicznie za pomocą następującej metody statycznej:
Object fromString (String value, Class targetClass)
Niestety, żadne wbudowane metody Java, takie jak Class.cast (), nie zrobią tego w przypadku String to BigDecimal lub String to Integer ani żadnej innej konwersji, w której nie ma obsługującej hierarchii klas. Z mojej strony chodzi o zapewnienie w pełni dynamicznego sposobu osiągnięcia tego celu - do czego nie sądzę, aby wcześniejsze odniesienie było właściwym podejściem - konieczności kodowania każdej konwersji. Mówiąc najprościej, implementacja polega po prostu na rzutowaniu ze łańcucha, jeśli jest to legalne / możliwe.
Tak więc rozwiązaniem jest prosta refleksja, szukanie publicznych Członków:
STRING_CLASS_ARRAY = (nowa klasa [] {String.class});
a) Member member = targetClass.getMethod (method.getName (), STRING_CLASS_ARRAY); b) Member member = targetClass.getConstructor (STRING_CLASS_ARRAY);
Przekonasz się, że wszystkie prymitywy (Integer, Long itp.) I wszystkie podstawy (BigInteger, BigDecimal itp.), A nawet java.regex.Pattern są objęte tym podejściem. Używałem tego z dużym sukcesem w projektach produkcyjnych, w których istnieje ogromna liczba dowolnych danych wejściowych wartości ciągu, w których potrzebne było bardziej ścisłe sprawdzenie. W tym podejściu, jeśli nie ma metody lub gdy metoda jest wywoływana, generowany jest wyjątek (ponieważ jest to niedozwolona wartość, taka jak nieliczbowe dane wejściowe do BigDecimal lub niedozwolone wyrażenie regularne dla wzorca), które zapewnia sprawdzenie specyficzne dla logika nieodłączna klasy docelowej.
Ma to pewne wady:
1) Musisz dobrze zrozumieć refleksję (jest to trochę skomplikowane i nie dla nowicjuszy). 2) Niektóre klasy Java, a nawet biblioteki innych firm są (niespodzianka) nieprawidłowo zakodowane. Oznacza to, że istnieją metody, które pobierają pojedynczy argument łańcuchowy jako dane wejściowe i zwracają wystąpienie klasy docelowej, ale nie jest to to, co myślisz ... Rozważmy klasę Integer:
static Integer getInteger(String nm) Determines the integer value of the system property with the specified name.
Powyższa metoda tak naprawdę nie ma nic wspólnego z liczbami całkowitymi jako obiektami zawijającymi prymitywy int. Refleksja uzna to za możliwego kandydata do niepoprawnego utworzenia liczby całkowitej z ciągu znaków w porównaniu z elementami składowymi dekodowania, valueof i konstruktora - które są odpowiednie dla większości dowolnych konwersji typu String, w których naprawdę nie masz kontroli nad danymi wejściowymi, ale po prostu chcesz wiedzieć, czy jest to możliwe, liczba całkowita.
Aby temu zaradzić, dobrym początkiem jest znalezienie metod, które generują wyjątki, ponieważ nieprawidłowe wartości wejściowe, które tworzą wystąpienia takich obiektów, powinny zgłaszać wyjątek. Niestety implementacje różnią się pod względem tego, czy wyjątki są zadeklarowane jako zaznaczone, czy nie. Integer.valueOf (String) zgłasza na przykład sprawdzony wyjątek NumberFormatException, ale podczas wyszukiwania odbić nie można znaleźć wyjątków Pattern.compile (). Ponownie, myślę, że nie jest to błąd tego dynamicznego podejścia do rzutowania krzyżowego, a raczej bardzo niestandardowej implementacji deklaracji wyjątków w metodach tworzenia obiektów.
Jeśli ktoś chciałby uzyskać więcej szczegółów na temat tego, jak zostało wdrożone powyższe, daj mi znać, ale myślę, że to rozwiązanie jest znacznie bardziej elastyczne / rozszerzalne i zawiera mniej kodu bez utraty dobrych elementów bezpieczeństwa typów. Oczywiście zawsze najlepiej „znać swoje dane”, ale jak wielu z nas się przekonuje, czasami jesteśmy tylko odbiorcami niezarządzanych treści i musimy zrobić wszystko, co w naszej mocy, aby je właściwie wykorzystać.
Twoje zdrowie.
źródło
To jest stary post, ale myślę, że mogę coś do niego wnieść.
Zawsze możesz zrobić coś takiego:
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")); }
Oczywiście nie jest to tak naprawdę rzutowanie dynamiczne, jak w innych językach (na przykład Python), ponieważ java jest językiem wpisywanym statycznie. Może to jednak rozwiązać niektóre skrajne przypadki, w których faktycznie trzeba załadować niektóre dane na różne sposoby, w zależności od identyfikatora. Ponadto część, w której otrzymujesz konstruktor z parametrem String, mogłaby być prawdopodobnie bardziej elastyczna dzięki przekazaniu tego parametru z tego samego źródła danych. To znaczy z pliku otrzymujesz sygnaturę konstruktora, którego chcesz użyć, oraz listę wartości, które mają być użyte, w ten sposób łączysz się w pary, powiedzmy, że pierwszy parametr to String, z pierwszym obiektem, rzutując go jako String, następny obiekt to liczba całkowita itd., ale w trakcie wykonywania programu otrzymujesz najpierw obiekt File, potem Double itd.
W ten sposób możesz uwzględnić te przypadki i wykonać nieco „dynamiczne” rzutowanie w locie.
Mam nadzieję, że to pomoże każdemu, ponieważ pojawia się to w wyszukiwaniach Google.
źródło
Niedawno czułem, że muszę to zrobić, ale potem znalazłem inny sposób, który prawdopodobnie sprawi, że mój kod będzie wyglądał schludniej i będzie lepiej wykorzystywał OOP.
Mam wiele klas rodzeństwa, z których każda implementuje określoną metodę
doSomething()
. Aby uzyskać dostęp do tej metody, musiałbym najpierw mieć instancję tej klasy, ale utworzyłem nadklasę dla wszystkich moich klas rodzeństwa i teraz mogę uzyskać dostęp do metody z nadklasy.Poniżej przedstawiam dwa alternatywne sposoby na „odlewanie dynamiczne”.
// 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; }
i moja obecnie używana metoda,
// Method 2. mSuperFragment = (MySuperFragment) getFragmentManager().findFragmentByTag(MyHelper.getName(mUnitNum)); mSuperFragment.sortNames(sortOptionNum);
źródło
Pomyślałem, że opublikuję coś, co uznałem za przydatne i może być możliwe dla kogoś, kto ma podobne potrzeby.
Poniższa metoda była metodą, którą napisałem dla mojej aplikacji JavaFX, aby uniknąć konieczności rzutowania, a także uniknąć pisania instrukcji obiektu x obiektu b za każdym razem, gdy zwracany jest kontroler.
public <U> Optional<U> getController(Class<U> castKlazz){ try { return Optional.of(fxmlLoader.<U>getController()); }catch (Exception e){ e.printStackTrace(); } return Optional.empty(); }
Deklaracja metody uzyskania kontrolera to
public <T> T getController()
Używając typu U przekazanego do mojej metody za pośrednictwem obiektu klasy, można go przekazać do kontrolera metody get, aby poinformować ją, jaki typ obiektu ma zwrócić. Obiekt opcjonalny jest zwracany w przypadku podania niewłaściwej klasy i wystąpienia wyjątku, w którym zostanie zwrócony pusty element opcjonalny, który możemy sprawdzić.
Tak wyglądało końcowe wywołanie metody (jeśli obecny, zwrócony opcjonalny obiekt pobiera Consumer
źródło
Wypróbuj to do dynamicznego przesyłania. To będzie działać!!!
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));
źródło