Jak przekazać typ jako parametr metody w Javie


85

W jaki sposób w Javie można przekazać typ jako parametr (lub zadeklarować jako zmienną)?

Nie chcę przekazywać instancji typu, ale sam typ (np. Int, String itp.).

W C # mogę to zrobić:

private void foo(Type t)
{
    if (t == typeof(String)) { ... }
    else if (t == typeof(int)) { ... }
}

private void bar()
{
    foo(typeof(String));
}

Czy istnieje sposób w Javie bez przekazywania wystąpienia typu t?
Czy muszę używać własnych stałych int lub wyliczenia?
Czy jest jakiś lepszy sposób?

Edycja: Oto wymaganie dla foo:
Na podstawie typu t generuje inny krótki ciąg xml.
Kod w if / else będzie bardzo mały (jeden lub dwa wiersze) i będzie używał pewnych prywatnych zmiennych klasowych.

java  types 

Możesz przekazać typ klasy, jak private void foo (klasa c) i użyć jak foo (String.class)
Michael Bavin

Odpowiedzi:


109

Mógłbyś przekazać Class<T>.

private void foo(Class<?> cls) {
    if (cls == String.class) { ... }
    else if (cls == int.class) { ... }
}

private void bar() {
    foo(String.class);
}

Aktualizacja : sposób OOP zależy od wymagań funkcjonalnych. Najlepszym rozwiązaniem byłoby zdefiniowanie interfejsu foo()i implementacja dwóch konkretnych implementacji, foo()a następnie po prostu wywołanie foo()dostępnej implementacji. Innym sposobem może być Map<Class<?>, Action>połączenie actions.get(cls). Można to łatwo być połączone z interfejsem i konkretnych implementacji: actions.get(cls).foo().


Dodam szczegóły wymagania do pytania.

Na razie zdecydowałem się na prostą wersję, ponieważ typy, o których mowa, zawsze będą prymitywne (int, string, itp.). Jednak na pewno będę o tym pamiętać. Dzięki.

Stringnie jest prymitywem w Javie;)
BalusC

@Padawan - wciąż za mało, żeby kontynuować. Odpowiedź od BalusC przy użyciu mapy jest wystarczająca. Masz rację, przyjmując to.
duffymo,

Możesz także użyć Class<?> clsczegoś innego niż porównanie. Chociaż nie możesz pisać: cls value = (cls) aCollection.iterator().next();możesz wywołać cls.cast (aCollection.iterator (). Next ()); Klasa javadoc
dlamblin

17

Miałem podobne pytanie, więc poniżej opracowałem pełną, możliwą do wykonania odpowiedź. Musiałem przekazać klasę (C) obiektowi (O) niepowiązanej klasy i sprawić, by ten obiekt (O) wyemitował nowe obiekty klasy (C) z powrotem, kiedy o nie poprosiłem.

Poniższy przykład pokazuje, jak to się robi. Istnieje klasa MagicGun, którą ładujesz z dowolnym podtypem klasy pocisku (Pebble, Bullet lub NuclearMissle). Interesujące jest to, że ładujesz go podtypami pocisku, ale nie rzeczywistymi obiektami tego typu. MagicGun tworzy rzeczywisty obiekt, gdy nadchodzi czas na strzelanie.

Wyjście

You've annoyed the target!
You've holed the target!
You've obliterated the target!
click
click

Kod

import java.util.ArrayList;
import java.util.List;

public class PassAClass {
    public static void main(String[] args) {
        MagicGun gun = new MagicGun();
        gun.loadWith(Pebble.class);
        gun.loadWith(Bullet.class);
        gun.loadWith(NuclearMissle.class);
        //gun.loadWith(Object.class);   // Won't compile -- Object is not a Projectile
        for(int i=0; i<5; i++){
            try {
                String effect = gun.shoot().effectOnTarget();
                System.out.printf("You've %s the target!\n", effect);
            } catch (GunIsEmptyException e) {
                System.err.printf("click\n");
            }
        }
    }
}

class MagicGun {
    /**
     * projectiles holds a list of classes that extend Projectile. Because of erasure, it
     * can't hold be a List<? extends Projectile> so we need the SuppressWarning. However
     * the only way to add to it is the "loadWith" method which makes it typesafe. 
     */
    private @SuppressWarnings("rawtypes") List<Class> projectiles = new ArrayList<Class>();
    /**
     * Load the MagicGun with a new Projectile class.
     * @param projectileClass The class of the Projectile to create when it's time to shoot.
     */
    public void loadWith(Class<? extends Projectile> projectileClass){
        projectiles.add(projectileClass);
    }
    /**
     * Shoot the MagicGun with the next Projectile. Projectiles are shot First In First Out.
     * @return A newly created Projectile object.
     * @throws GunIsEmptyException
     */
    public Projectile shoot() throws GunIsEmptyException{
        if (projectiles.isEmpty())
            throw new GunIsEmptyException();
        Projectile projectile = null;
        // We know it must be a Projectile, so the SuppressWarnings is OK
        @SuppressWarnings("unchecked") Class<? extends Projectile> projectileClass = projectiles.get(0);
        projectiles.remove(0);
        try{
            // http://www.java2s.com/Code/Java/Language-Basics/ObjectReflectioncreatenewinstance.htm
            projectile = projectileClass.newInstance();
        } catch (InstantiationException e) {
            System.err.println(e);
        } catch (IllegalAccessException e) {
            System.err.println(e);
        }
        return projectile;
    }
}

abstract class Projectile {
    public abstract String effectOnTarget();
}

class Pebble extends Projectile {
    @Override public String effectOnTarget() {
        return "annoyed";
    }
}

class Bullet extends Projectile {
    @Override public String effectOnTarget() {
        return "holed";
    }
}

class NuclearMissle extends Projectile {
    @Override public String effectOnTarget() {
        return "obliterated";
    }
}

class GunIsEmptyException extends Exception {
    private static final long serialVersionUID = 4574971294051632635L;
}

2
Rozumiem, że próbujesz zaprezentować przekazanie typu do metody i ogólnie podoba mi się przykład, ale proste pytanie, które się z tym wiąże, brzmi: dlaczego nie miałbyś po prostu załadować magicznej broni nowymi instancjami pocisków, zamiast komplikować sprawy takie jak to? Nie pomijaj ostrzeżeń, napraw je za pomocą List<Class<? extends Projectile>> projectiles = new ArrayList<Class<? extends Projectile>>(). Klasa Projectile powinna być interfejsem, a serial nie jest potrzebny w twoim przykładzie,
dlamblin

1
Jedynym powodem, dla którego mogłem wymyślić, jest to, że czytanie nazwy klasy może być przydatne. pastebin.com/v9UyPtWT Ale wtedy możesz użyć enum. pastebin.com/Nw939Js1
dlamblin

10

Och, ale to brzydki, niezorientowany obiektowo kod. W momencie, gdy zobaczysz „if / else” i „typeof”, powinieneś pomyśleć o polimorfizmie. To jest zły sposób. Myślę, że leki generyczne są tutaj twoim przyjacielem.

Z iloma typami planujesz sobie poradzić?

AKTUALIZACJA:

Jeśli mówisz tylko o String i int, oto jeden ze sposobów, w jaki możesz to zrobić. Zacznij od interfejsu XmlGenerator (wystarczy „foo”):

package generics;

public interface XmlGenerator<T>
{
   String getXml(T value);
}

I konkretna implementacja XmlGeneratorImpl:

    package generics;

public class XmlGeneratorImpl<T> implements XmlGenerator<T>
{
    private Class<T> valueType;
    private static final int DEFAULT_CAPACITY = 1024;

    public static void main(String [] args)
    {
        Integer x = 42;
        String y = "foobar";

        XmlGenerator<Integer> intXmlGenerator = new XmlGeneratorImpl<Integer>(Integer.class);
        XmlGenerator<String> stringXmlGenerator = new XmlGeneratorImpl<String>(String.class);

        System.out.println("integer: " + intXmlGenerator.getXml(x));
        System.out.println("string : " + stringXmlGenerator.getXml(y));
    }

    public XmlGeneratorImpl(Class<T> clazz)
    {
        this.valueType = clazz;
    }

    public String getXml(T value)
    {
        StringBuilder builder = new StringBuilder(DEFAULT_CAPACITY);

        appendTag(builder);
        builder.append(value);
        appendTag(builder, false);

        return builder.toString();
    }

    private void appendTag(StringBuilder builder) { this.appendTag(builder, false); }

    private void appendTag(StringBuilder builder, boolean isClosing)
    {
        String valueTypeName = valueType.getName();
        builder.append("<").append(valueTypeName);
        if (isClosing)
        {
            builder.append("/");
        }
        builder.append(">");
    }
}

Jeśli to uruchomię, otrzymam następujący wynik:

integer: <java.lang.Integer>42<java.lang.Integer>
string : <java.lang.String>foobar<java.lang.String>

Nie wiem, czy to właśnie miałeś na myśli.


Obecnie tylko int i string. Jaki byłby najlepszy sposób, aby to zrobić?

4
„Za każdym razem, gdy piszesz kod formularza”, jeśli obiekt jest typu T1, zrób coś, ale jeśli jest typu T2, zrób coś innego, „uderz się”. javapractices.com/topic/TopicAction.do?Id=31
Pool

@ Uczta: dzięki za link. Myślę, że rozumiem, co zostało powiedziane, ale tutaj nie mam wystąpienia żadnego obiektu. Chcę, aby foo wygenerowało inny ciąg dla int vs string. Czy musiałbym utworzyć dwie instancje obiektów pochodzących z jakiegoś innego obiektu, aby zrobić to na plus? To może być przesada w tej sprawie?

Na razie zdecydowałem się na prostą wersję, ponieważ typy, o których mowa, zawsze będą prymitywne (int, string, itp.). Jednak na pewno będę o tym pamiętać. Dzięki.

1
@Padawan, przepraszam, że nie chciałem być protekcjonalny - komentarz duffymo przypomniał mi o tym cytacie!
Basen

9

Powinieneś zdać Class...

private void foo(Class<?> t){
    if(t == String.class){ ... }
    else if(t == int.class){ ... }
}

private void bar()
{
   foo(String.class);
}

Dlaczego powinieneś zdać klasę, a nie typ? czy jest jakiś powód, dla którego podpisy takie jak method_foo(Type fooableType)są mniej przydatne niż method_foo(Class<?> fooableClass)?
roberto tomás

5

Jeśli chcesz przekazać typ, to jego odpowiednik w Javie byłby

java.lang.Class

Jeśli chcesz użyć słabo wpisanej metody, po prostu użyj

java.lang.Object

i odpowiedni operator

instanceof

na przykład

private void foo(Object o) {

  if(o instanceof String) {

  }

}//foo

Jednak w Javie istnieją typy pierwotne, które nie są klasami (np. Int z twojego przykładu), więc musisz być ostrożny.

Prawdziwe pytanie brzmi, co tak naprawdę chcesz tutaj osiągnąć, w przeciwnym razie trudno jest odpowiedzieć:

Czy jest jakiś lepszy sposób?


0

Możesz przekazać instancję java.lang.Class, która reprezentuje typ, tj

private void foo(Class cls)
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.