Übergeben eines Typs als Methodenparameter in Java

85

Wie können Sie in Java einen Typ als Parameter übergeben (oder als Variable deklarieren)?

Ich möchte keine Instanz des Typs übergeben, sondern den Typ selbst (z. B. int, String usw.).

In C # kann ich das tun:

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

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

Gibt es in Java einen Weg, ohne eine Instanz vom Typ t zu übergeben?
Oder muss ich meine eigenen int-Konstanten oder Aufzählungen verwenden?
Oder gibt es einen besseren Weg?

Bearbeiten: Hier ist die Anforderung für foo:
Basierend auf Typ t wird eine andere kurze XML-Zeichenfolge generiert.
Der Code im if / else ist sehr klein (ein oder zwei Zeilen) und verwendet einige private Klassenvariablen.


quelle
Sie können den Klassentyp wie private void foo (Klasse c) übergeben und wie foo (String.class) verwenden
Michael Bavin

Antworten:

109

Sie könnten ein Class<T>in übergeben.

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

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

Update : Der OOP-Weg hängt von den funktionalen Anforderungen ab. Am besten wäre es, eine Schnittstelle zu definieren foo()und zwei konkrete Implementierungen zu implementieren foo()und dann einfach foo()die Implementierung aufzurufen, die Sie zur Hand haben. Ein anderer Weg könnte ein sein, über Map<Class<?>, Action>den Sie anrufen können actions.get(cls). Dies lässt sich leicht mit einer Schnittstelle und konkreten Implementierungen kombinieren : actions.get(cls).foo().

BalusC
quelle
Ich werde der Frage Details der Anforderung hinzufügen.
Ich habe mich vorerst für die einfache Version entschieden, da die fraglichen Typen immer primitiv sind (int, string usw.). Ich werde jedoch auf jeden Fall den oop-Weg im Auge behalten. Vielen Dank.
Stringist kein
Grundelement
@ Padawan - immer noch nicht genug, um weiterzumachen. Die Antwort von BalusC mit Map ist ausreichend. Du hast Recht, es zu akzeptieren.
Duffymo
Sie können auch Class<?> clsin etwas anderem als Vergleich verwenden. Während Sie nicht schreiben können: cls value = (cls) aCollection.iterator().next();Sie können cls.cast (aCollection.iterator (). Next ()) aufrufen; Klasse Javadoc
Dlamblin
17

Ich hatte eine ähnliche Frage, daher habe ich unten eine vollständige ausführbare Antwort ausgearbeitet. Ich musste eine Klasse (C) an ein Objekt (O) einer nicht verwandten Klasse übergeben und dieses Objekt (O) neue Objekte der Klasse (C) an mich zurücksenden lassen, wenn ich danach fragte.

Das folgende Beispiel zeigt, wie dies gemacht wird. Es gibt eine MagicGun-Klasse, die Sie mit einem beliebigen Subtyp der Projectile-Klasse (Pebble, Bullet oder NuclearMissle) laden. Das Interessante ist, dass Sie es mit Untertypen von Projectile laden, aber nicht mit tatsächlichen Objekten dieses Typs. Die MagicGun erstellt das eigentliche Objekt, wenn es Zeit zum Schießen ist.

Die Ausgabe

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

Der Code

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;
}
Johnny Lambada
quelle
2
Ich verstehe, dass Sie versuchen, einen Typ an eine Methode zu übergeben, und ich mag das Beispiel im Allgemeinen, aber die einfache Frage, die sich daraus ergibt, ist, warum Sie die magische Waffe nicht einfach mit neuen Instanzen von Projektilen beladen würden, anstatt Dinge wie zu komplizieren Dies? Unterdrücken Sie auch nicht die Warnungen, mit denen sie behoben wurden List<Class<? extends Projectile>> projectiles = new ArrayList<Class<? extends Projectile>>(). Die Projectile-Klasse sollte eine Schnittstelle sein, und die Seriennummer ist für Ihr Beispiel nicht erforderlich
dlamblin
1
Der einzige Grund, den ich mir einfallen lassen könnte, ist, dass das Lesen des Klassennamens nützlich sein könnte. pastebin.com/v9UyPtWT Aber dann könnten Sie eine Aufzählung verwenden. pastebin.com/Nw939Js1
dlamblin
10

Oh, aber das ist hässlicher, nicht objektorientierter Code. In dem Moment, in dem Sie "if / else" und "typeof" sehen, sollten Sie an Polymorphismus denken. Dies ist der falsche Weg. Ich denke, Generika sind dein Freund hier.

Mit wie vielen Typen planen Sie umzugehen?

AKTUALISIEREN:

Wenn Sie nur über String und int sprechen, können Sie dies auf folgende Weise tun. Beginnen Sie mit der Schnittstelle XmlGenerator (genug mit "foo"):

package generics;

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

Und die konkrete Implementierung 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(">");
    }
}

Wenn ich dies ausführe, erhalte ich das folgende Ergebnis:

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

Ich weiß nicht, ob Sie das im Sinn hatten.

Duffymo
quelle
Im Moment nur int und string. Was wäre der beste Weg, dies zu tun?
4
"Immer wenn Sie Code des Formulars schreiben", wenn das Objekt vom Typ T1 ist, dann tun Sie etwas, aber wenn es vom Typ T2 ist, dann tun Sie etwas anderes, "schlagen Sie sich selbst". javapractices.com/topic/TopicAction.do?Id=31
Pool
@ The Feast: Danke für den Link. Ich glaube, ich verstehe, was gesagt wird, aber hier habe ich keine Instanz eines Objekts. Ich möchte, dass foo eine andere Zeichenfolge für eine int als eine Zeichenfolge generiert. Müsste ich zwei Instanzen von Objekten erstellen, die von einem anderen Objekt abgeleitet sind, um dies auf die richtige Weise zu tun? Es könnte für diesen Fall übertrieben sein?
Ich habe mich vorerst für die einfache Version entschieden, da die fraglichen Typen immer primitiv sind (int, string usw.). Ich werde jedoch auf jeden Fall den oop-Weg im Auge behalten. Vielen Dank.
1
@Padawan, sorry, ich wollte mich nicht herablassen - der Kommentar von Duffymo erinnerte mich an dieses Zitat ist alles!
Pool
9

Sie sollten eine Class...

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

private void bar()
{
   foo(String.class);
}
Mark Elliot
quelle
Warum sollten Sie eine Klasse und keinen Typ bestehen? Gibt es einen Grund, warum Signaturen wie method_foo(Type fooableType)weniger nützlich sind als method_foo(Class<?> fooableClass)?
Roberto Tomás
5

Wenn Sie den Typ übergeben möchten, wäre dies das Äquivalent in Java

java.lang.Class

Wenn Sie eine schwach typisierte Methode verwenden möchten, verwenden Sie einfach

java.lang.Object

und der entsprechende Operator

instanceof

z.B

private void foo(Object o) {

  if(o instanceof String) {

  }

}//foo

In Java gibt es jedoch primitive Typen, die keine Klassen sind (dh int aus Ihrem Beispiel), daher müssen Sie vorsichtig sein.

Die eigentliche Frage ist, was Sie hier eigentlich erreichen wollen, sonst ist es schwer zu beantworten:

Oder gibt es einen besseren Weg?

Dieter
quelle
0

Sie können eine Instanz von java.lang.Class übergeben, die den Typ darstellt, d. H.

private void foo(Class cls)
Ghallio
quelle