java: Jak mogę dynamicznie rzutować zmienną z jednego typu na inny?

85

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ą HashMapprzeze 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 Doublei jeśli zostanie odebrana liczba 3, widzi ją jako Integeri mam problem z typem.

ufk
źródło
To nie ma żadnego sensu. Chcesz, aby nazwa zmiennej była typem rzutowania ciągu na łańcuch? Co?
cletus
3
Nie znam odpowiedzi, ale obawiam się, że może to stać się piekłem konserwacji ... Sama ucząc się Javy, ale unikałbym sytuacji, które wymagają takiego podejścia. Jestem prawie pewien, że cokolwiek zrobisz, może zostać wdrożone w lepszy sposób ... tylko moje 2 centy.
Sejanus
ok, podam więcej informacji na temat tego, co próbuję osiągnąć.
ufk
zaktualizowałem również moją odpowiedź poniżej!
user85421

Odpowiedzi:

14

Jeśli chodzi o aktualizację, jedynym sposobem rozwiązania tego problemu w Javie jest napisanie kodu obejmującego wszystkie przypadki z dużą ilością ifi elseiinstanceof wyrażeń . To, co próbujesz zrobić, wygląda tak, jakby było używane do programowania w językach dynamicznych. W językach statycznych to, co próbujesz zrobić, jest prawie niemożliwe i prawdopodobnie wybrano zupełnie inne podejście do tego, co próbujesz zrobić. Języki statyczne nie są po prostu tak elastyczne jak dynamiczne :)

Dobrymi przykładami najlepszych praktyk w Javie są odpowiedzi udzielone przez BalusC (tj. ObjectConverter) I przez Andreas_D (tj. Adapter) Poniżej.


To nie ma sensu w

String a = (theType) 5;

typ ajest statycznie związany, Stringwię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ć stringClassdo rzutowania zmiennych.

akuhn
źródło
Mam nadzieję, że aktualizacja, którą opublikowałem, wyjaśni, co próbuję zrobić. Pochodzę z PHP, więc może nie da się tego osiągnąć w Javie.
ufk
Dokładnie, w Javie nie możesz być tak dynamiczny, zobacz też moją aktualizację.
akuhn
Zobacz odpowiedź BalusC poniżej, to jest długość (i ból), do którego musisz iść ...
akuhn
Wiem, że to późno, ale myślę, że miał na myśli [typ] a = (typ) jakiś_obiekt; co jest bezcelowe, ponieważ możesz po prostu wykonać Object a = some_object w porządku
George Xavier
120

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 Objecttypu. 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ę, Numberna 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ą instanceoflubClass.isInstance() .

Aktualizacja

zgodnie z twoją ostatnią aktualizacją prawdziwym problemem jest to, że masz Integerw swoim, HashMapktóry powinien być przypisany do Double. W takim przypadku możesz sprawdzić typ pola i skorzystać z xxxValue()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)

user85421
źródło
22

Będziesz musiał coś o ObjectConvertertym 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ą przez Field#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.

BalusC
źródło
@BalusC - Uważam, że kod ObjectConverter jest interesujący, czy mógłbyś opisać jego przypadki użycia?
srini.venigalla
Jest to przydatne w przypadkach, gdy preferowana jest konwencja zamiast konfiguracji, a typ źródła nie jest zgodny z typem docelowym. Używałem go 2-3 lata temu w moich (czysto hobbystycznych) frameworkach ORM i MVC. Zobacz także wiodący tekst artykułu na blogu.
BalusC
12

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);
    }
}
Jared Russell
źródło
1
Co się stanie, jeśli typ wpisu w ogóle nie jest nadtypem typu pola? Będziesz wtedy naprawdę musiał konwertować programowo.
BalusC
7

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

}
Hoji
źródło
Tak, myślę, że ta odpowiedź jest tym, czego chce pytający. Otrzymał właśnie klasę <?> I chce przekonwertować instancję na klasę „?”. Domyślnie java tego nie obsługuje. Ale używając <T> możemy to zrobić.
ruiruige1991
@ ruiruige1991 To jest złe. W takim przypadku jest to rodzaj. Generics nic nie robią w czasie wykonywania. (T) bla będzie po prostu (Object) blah podczas działania z powodu wymazania typów. Krótko mówiąc, generics -> czas kompilacji i nie ma wpływu na środowisko uruchomieniowe. Ponieważ dynamiczne -> środowisko uruchomieniowe i generyczne -> czas kompilacji, typy generyczne są bezużyteczne.
George Xavier
5

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.

Andreas Dolk
źródło
2

Twoim problemem nie jest brak „dynamicznego odlewania”. Przesyłanie Integerdo Doublenie 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.

Michael Borgwardt
źródło
@nazwa: dotyczy edycji, którą ciągle sugerujesz: zauważ, że mówię nie o prymitywnych wartościach, ale o klasach opakowujących (oznaczanych przez wielkie litery i stylizację jako kod), a rzutowanie między nimi jest zdecydowanie niemożliwe.
Michael Borgwardt
1

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.

Bandyta
źródło
1

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.

Darrell Teague
źródło
1

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.

Acapulco
źródło
0

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);
Anonsage
źródło
Nigdy nie musisz dynamicznie rzucać. Twoja formuła posiadania nadklasy z metodą doSomething () i podklas implementujących metodę doSomething () jest jednym z głównych celów OOP, czyli polimorfizmu. Obiekt foo = new Integer ("1"); foo.toString () zwraca 1. Mimo że jest przypisana do Object, jest liczbą całkowitą i dlatego tak się zachowuje. Uruchomienie metody toString spowoduje uruchomienie implementacji Integer. Wielopostaciowość.
George Xavier
0

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

getController(LoadController.class).ifPresent(controller->controller.onNotifyComplete());
Eladian
źródło
0

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));
Sunil MM
źródło