如何在Java中将类型作为方法参数传递

103
在Java中,你如何将类型作为参数传递(或声明为变量)?
我不想传递类型的实例,而是类型本身(例如int、String等)。
在C#中,我可以这样做:
private void foo(Type t)
{
    if (t == typeof(String)) { ... }
    else if (t == typeof(int)) { ... }
}

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

在Java中有没有一种不传递类型实例的方法?或者我必须使用自己的int常量或枚举?还是有更好的方法?

编辑:这里是foo的要求:
根据类型t,它会生成一个不同的短的XML字符串。
if/else中的代码将非常少(一两行),并且将使用一些私有类变量。


你可以像这样传递类类型:private void foo(Class c),并且可以像foo(String.class)一样使用。 - Michael Bavin
6个回答

136

你可以传入一个 Class<T>

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

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

更新: 面向对象编程的方式取决于功能需求。最好的方法是定义一个接口,其中定义了foo()方法和两个具体的实现来实现foo()方法,然后仅在手头有的实现上调用foo()方法。另一种方法可能是使用Map<Class<?>, Action>,你可以通过actions.get(cls)进行调用。这可以很容易地与接口和具体实现组合使用:actions.get(cls).foo()


我决定现在采用简单版本,因为所涉及的类型将始终是原始类型(int、string等)。但是,我一定会记住面向对象编程的方式。谢谢。 - user250781
1
String 不是 Java 中的原始类型 ;) - BalusC
@Padawan - 还不够明确。BalusC使用Map的答案已经足够了。你是对的,应该接受它。 - duffymo
另外,您可以在其他地方使用Class <?> cls而不仅仅是比较。虽然您无法编写:cls value =(cls)aCollection.iterator()。next();,但您可以调用cls.cast(aCollection.iterator()。next()); [Class javadoc](http://download.oracle.com/javase/1,5,0/docs/api/java/lang/Class.html) - dlamblin
你能否也为子类型做到这一点?类<?>扩展xxx或其他什么? - Gobliins
显示剩余3条评论

22
我有一个类似的问题,所以我在下面提供了一个完整可运行的答案。我的需求是将一个类(C)传递给一个不相关的类的对象(O),并在我请求时由该对象(O)向我发出类(C)的新对象。
下面的示例显示了如何实现此操作。有一个MagicGun类,你可以用任何Projectile类的子类型(Pebble、Bullet或NuclearMissle)来加载它。有趣的是,你加载的是Projectile的子类型,而不是该类型的实际对象。当射击时,MagicGun创建实际的对象。
You've annoyed the target!
You've holed the target!
You've obliterated the target!
click
click

代码

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

3
我知道你试图演示在方法中传递类型的方式,并且我通常喜欢这个例子,但是这引发了一个简单的问题,为什么不只是使用新的弹药实例来装填魔法枪,而不要像这样复杂化问题?另外,请不要禁止警告信息,用List<Class<? extends Projectile>> projectiles = new ArrayList<Class<? extends Projectile>>()来修复它们。Projectile类应该是一个接口,而对于你的例子来说序列化并不必要。 - dlamblin
1
我能想到的唯一理由是读取类名可能会有用。http://pastebin.com/v9UyPtWT 但是,你也可以使用枚举。http://pastebin.com/Nw939Js1 - dlamblin

15

哦,但那是丑陋的、非面向对象的代码。一旦你看到“if/else”和“typeof”,你应该想到多态性。这是错误的方法。我认为泛型在这里是你的朋友。

你计划处理多少种类型?

更新:

如果你只是谈论 String 和 int,这里有一种可能的方法。从接口 XmlGenerator(不要再用“foo”了)开始:

package generics;

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

具体实现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(">");
    }
}

如果我运行这个程序,我会得到以下结果:
integer: <java.lang.Integer>42<java.lang.Integer>
string : <java.lang.String>foobar<java.lang.String>

我不知道这是否符合你的意愿。

5
每当你发现自己写的代码形式是"如果对象是类型T1,那么做某事,但如果它是类型T2,则做另一件事"时,就给自己一个耳光。 - Pool
@The Feast:感谢提供链接。我想我理解了所说的内容,但在这里我没有任何对象的实例。我希望 foo 为 int 和 string 生成不同的字符串。我是否需要创建两个派生自其他对象的对象实例来以面向对象的方式完成它?对于这种情况,这可能有点过度设计了吗? - user250781
1
@Padawan,抱歉我没有恶意 - duffymo的评论只是让我想起了那句话! - Pool
@The Feast:我没有那个意思。别担心。 - user250781
@duffymo:我尝试了一下,这对其他情况可能有用,但我现在遇到的特定情况并不依赖于或使用任何类型为T的对象实例。也许我还没有以面向对象的方式思考,但或许以后会恍然大悟。感谢你的所有努力。 - user250781
显示剩余5条评论

12

您应该传递一个Class...

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

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

1
为什么你应该传递一个类而不是一个类型?是否有某些原因使得像method_foo(Type fooableType)这样的签名比method_foo(Class<?> fooableClass)更少用? - roberto tomás

6
如果你想要传递类型,那么在Java中的等效方式就是:
java.lang.Class

如果您想使用弱类型方法,那么您只需使用:

java.lang.Object

以及相应的运算符

instanceof

e.g.

private void foo(Object o) {

  if(o instanceof String) {

  }

}//foo

然而,在Java中有原始类型,它们不是类(例如您的示例中的int),因此您需要小心。

真正的问题是您实际上想要在这里实现什么,否则很难回答:

还有更好的方法吗?


0

您可以传递一个表示类型的java.lang.Class实例,即

private void foo(Class cls)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接