Cómo pasar un tipo como parámetro de método en Java

85

En Java, ¿cómo se puede pasar un tipo como parámetro (o declarar como variable)?

No quiero pasar una instancia del tipo, sino el tipo en sí (por ejemplo, int, String, etc.).

En C #, puedo hacer esto:

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

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

¿Hay alguna forma en Java sin pasar una instancia de tipo t?
¿O tengo que usar mis propias constantes int o enumeración?
¿O hay un mejor camino?

Editar: aquí está el requisito para foo:
basado en el tipo t, genera una cadena xml corta diferente.
El código en if / else será muy pequeño (una o dos líneas) y usará algunas variables de clase privadas.


fuente
Puede pasar el tipo de clase como private void foo (Clase c) y usar como foo (String.class)
Michael Bavin

Respuestas:

109

Podrías pasar una entrada Class<T>.

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

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

Actualización : la forma OOP depende del requisito funcional. Lo mejor sería definir una interfaz foo()e implementar dos implementaciones concretas foo()y luego simplemente recurrir foo()a la implementación que tenga a mano. Otra forma puede ser por la Map<Class<?>, Action>que podrías llamar actions.get(cls). Esto es fácilmente para ser combinado con una interfaz y implementaciones concretas: actions.get(cls).foo().

BalusC
fuente
Agregaré detalles del requisito a la pregunta.
Decidí ir con la versión simple por ahora, ya que los tipos en cuestión siempre serán primitivos (int, string, etc.). Sin embargo, definitivamente tendré en cuenta la forma oop. Gracias.
Stringno es un primitivo en Java;)
BalusC
@Padawan: todavía no es suficiente para continuar. La respuesta de BalusC usando Map es suficiente. Tienes razón en aceptarlo.
Duffymo
También se puede utilizar Class<?> clsen algo más que en la comparación. Si bien no puede escribir: cls value = (cls) aCollection.iterator().next();puede llamar a cls.cast (aCollection.iterator (). Next ()); Clase javadoc
dlamblin
17

Tenía una pregunta similar, así que elaboré una respuesta completa ejecutable a continuación. Lo que tenía que hacer era pasar una clase (C) a un objeto (O) de una clase no relacionada y hacer que ese objeto (O) me emitiera nuevos objetos de clase (C) cuando los pedí.

El siguiente ejemplo muestra cómo se hace esto. Hay una clase MagicGun que carga con cualquier subtipo de la clase Proyectil (Pebble, Bullet o NuclearMissle). Lo interesante es que lo cargas con subtipos de Proyectil, pero no con objetos reales de ese tipo. MagicGun crea el objeto real cuando llega el momento de disparar.

La salida

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

El código

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;
}
JohnnyLambada
fuente
2
Entiendo que estás intentando hacer una demostración de pasar un tipo a un método, y me gusta el ejemplo en general, pero la pregunta simple que surge es por qué no cargarías la pistola mágica con nuevas instancias de proyectiles en lugar de complicar cosas como ¿esta? Tampoco suprima las advertencias con las que las arregle List<Class<? extends Projectile>> projectiles = new ArrayList<Class<? extends Projectile>>(). La clase Proyectil debe ser una interfaz, y la serie no es necesaria para su ejemplo,
dlamblin
1
La única razón por la que se me ocurrió es que leer el nombre de la clase podría ser útil. pastebin.com/v9UyPtWT Pero entonces, podrías usar una enumeración. pastebin.com/Nw939Js1
dlamblin
10

Oh, pero eso es un código feo, no orientado a objetos. En el momento en que veas "if / else" y "typeof", deberías estar pensando en polimorfismo. Este es el camino equivocado. Creo que los genéricos son tus amigos aquí.

¿Con cuántos tipos planeas lidiar?

ACTUALIZAR:

Si solo está hablando de String e int, aquí hay una forma en que puede hacerlo. Comience con la interfaz XmlGenerator (suficiente con "foo"):

package generics;

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

Y la implementación concreta 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(">");
    }
}

Si ejecuto esto, obtengo el siguiente resultado:

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

No sé si esto es lo que tenías en mente.

duffymo
fuente
Ahora mismo solo int y string. ¿Cuál sería la mejor forma de hacer esto?
4
"Siempre que te encuentres escribiendo un código de la forma" si el objeto es del tipo T1, haz algo, pero si es del tipo T2, haz otra cosa, "abofetea". javapractices.com/topic/TopicAction.do?Id=31
Grupo
@The Feast: gracias por el enlace. Creo que entiendo lo que se dice, pero aquí no tengo una instancia de ningún objeto. Quiero que foo genere una cadena diferente para un int vs una cadena. ¿Tendría que crear dos instancias de objetos derivados de algún otro objeto para hacerlo de la manera oop? ¿Podría ser excesivo para este caso?
Decidí ir con la versión simple por ahora, ya que los tipos en cuestión siempre serán primitivos (int, string, etc.). Sin embargo, definitivamente tendré en cuenta la forma oop. Gracias.
1
@Padawan, lo siento, no quise ser condescendiente, ¡el comentario de duffymo me recordó esa cita, eso es todo!
Piscina
9

Deberías pasar un Class...

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

private void bar()
{
   foo(String.class);
}
Mark Elliot
fuente
¿Por qué debería aprobar una clase y no un tipo? ¿Hay alguna razón por la que las firmas como method_foo(Type fooableType)sean menos útiles que method_foo(Class<?> fooableClass)?
roberto tomás
5

Si desea pasar el tipo, el equivalente en Java sería

java.lang.Class

Si desea utilizar un método débilmente tipado, simplemente debería utilizar

java.lang.Object

y el operador correspondiente

instanceof

p.ej

private void foo(Object o) {

  if(o instanceof String) {

  }

}//foo

Sin embargo, en Java hay tipos primitivos, que no son clases (es decir, int de su ejemplo), por lo que debe tener cuidado.

La verdadera pregunta es qué es lo que realmente quiere lograr aquí, de lo contrario, es difícil de responder:

¿O hay un mejor camino?

Dieter
fuente
0

Puede pasar una instancia de java.lang.Class que represente el tipo, es decir

private void foo(Class cls)
ghallio
fuente