Como passar um tipo como parâmetro de método em Java

85

Em Java, como você pode passar um tipo como um parâmetro (ou declarar como uma variável)?

Não quero passar uma instância do tipo, mas o próprio tipo (por exemplo, int, String, etc).

Em C #, posso fazer isso:

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

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

Existe uma maneira em Java sem passar uma instância do tipo t?
Ou eu tenho que usar minhas próprias constantes int ou enum?
Ou há um modo melhor?

Edit: Aqui está o requisito para foo:
Com base no tipo t, ele gera uma string xml curta diferente.
O código em if / else será muito pequeno (uma ou duas linhas) e usará algumas variáveis ​​de classe privada.


fonte
Você pode passar o tipo de classe como private void foo (Classe c) e usar como foo (String.class)
Michael Bavin de

Respostas:

109

Você poderia passar por Class<T>dentro.

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

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

Atualização : o modo OOP depende do requisito funcional. A melhor aposta seria uma definição de interface foo()e duas implementações concretas de implementação foo()e, em seguida, basta chamar foo()a implementação que você tem em mãos. Outra forma pode ser através de uma Map<Class<?>, Action>chamada actions.get(cls). Isto é facilmente para ser combinado com uma interface e implementações concretas: actions.get(cls).foo().

BalusC
fonte
Acrescentarei detalhes do requisito à pergunta.
Decidi ir com a versão simples por enquanto, pois os tipos em questão sempre serão primitivos (int, string, etc). No entanto, com certeza vou manter o modo oop em mente. Obrigado.
Stringnão é um primitivo em Java;)
BalusC
@Padawan - ainda não o suficiente para continuar. A resposta do BalusC usando o Map é suficiente. Você está certo em aceitar isso.
duffymo de
Além disso, você pode usar Class<?> clsem algo diferente de comparação. Embora você não possa escrever: cls value = (cls) aCollection.iterator().next();você pode chamar cls.cast (aCollection.iterator (). Next ()); Classe javadoc
dlamblin
17

Eu tinha uma pergunta semelhante, então criei uma resposta executável completa abaixo. O que eu precisava fazer era passar uma classe (C) para um objeto (O) de uma classe não relacionada e fazer com que esse objeto (O) emitisse novos objetos da classe (C) de volta para mim quando eu os solicitasse.

O exemplo abaixo mostra como isso é feito. Existe uma classe MagicGun que você carrega com qualquer subtipo da classe Projétil (Pebble, Bullet ou NuclearMissle). O interessante é que você o carrega com subtipos de Projétil, mas não com objetos reais desse tipo. O MagicGun cria o objeto real na hora de atirar.

A saída

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

O 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
fonte
2
Eu entendo que você está tentando passar uma demonstração de um tipo para um método, e gosto do exemplo em geral, mas a questão simples que isso levanta é por que você não carregaria a arma mágica com novas instâncias de projéteis em vez de complicar coisas como esta? Também não suprima os avisos, corrija-os com List<Class<? extends Projectile>> projectiles = new ArrayList<Class<? extends Projectile>>(). A classe Projétil deve ser uma interface, e o serial não é necessário para seu exemplo,
dlamblin
1
A única razão que pude pensar é que ler o nome da classe pode ser útil. pastebin.com/v9UyPtWT Mas então, você poderia usar um enum. pastebin.com/Nw939Js1
dlamblin
10

Ah, mas esse é um código feio e não orientado a objetos. No momento em que você vê "if / else" e "typeof", você deve estar pensando em polimorfismo. Este é o caminho errado a seguir. Acho que os genéricos são seus amigos aqui.

Com quantos tipos você planeja lidar?

ATUALIZAR:

Se você está falando apenas sobre String e int, aqui está uma maneira de fazer isso. Comece com a interface XmlGenerator (basta com "foo"):

package generics;

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

E a implementação 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(">");
    }
}

Se eu executar isso, obtenho o seguinte resultado:

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

Não sei se é isso que você tinha em mente.

duffymo
fonte
No momento, apenas int e string. Qual seria a melhor maneira de fazer isso?
4
"Sempre que você estiver escrevendo um código do formulário" se o objeto for do tipo T1, faça algo, mas se for do tipo T2, faça outra coisa, "dê um tapa em si mesmo". javapractices.com/topic/TopicAction.do?Id=31
Pool de
@ The Feast: obrigado pelo link. Acho que entendo o que está sendo dito, mas aqui não tenho uma instância de nenhum objeto. Eu quero que foo gere uma string diferente para um int vs uma string. Eu teria que criar duas instâncias de objetos derivados de algum outro objeto para fazer isso da maneira oop? Pode ser um exagero para este caso?
Decidi ir com a versão simples por enquanto, pois os tipos em questão sempre serão primitivos (int, string, etc). No entanto, com certeza vou manter o modo oop em mente. Obrigado.
1
@Padawan, desculpe, eu não tive a intenção de ser condescendente - o comentário de duffymo me lembrou dessa citação é tudo!
Piscina de
9

Você deveria passar por um Class...

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

private void bar()
{
   foo(String.class);
}
Mark Elliot
fonte
Por que você deve passar em uma classe e não em um tipo? há algum motivo para que assinaturas como method_foo(Type fooableType)sejam menos úteis do que method_foo(Class<?> fooableClass)?
roberto tomás
5

Se você deseja passar o tipo, então o equivalente em Java seria

java.lang.Class

Se você quiser usar um método de digitação fraca, basta usar

java.lang.Object

e o operador correspondente

instanceof

por exemplo

private void foo(Object o) {

  if(o instanceof String) {

  }

}//foo

No entanto, em Java existem tipos primitivos, que não são classes (ou seja, int do seu exemplo), portanto, você precisa ter cuidado.

A verdadeira questão é o que você realmente deseja alcançar aqui, caso contrário, é difícil responder:

Ou há um modo melhor?

Dieter
fonte
0

Você pode passar uma instância de java.lang.Class que representa o tipo, ou seja,

private void foo(Class cls)
ghallio
fonte