在Java中,您可以在不使用varargs工具的情况下使用变量参数吗?

时间:2011-02-01 19:57:45

标签: java variadic-functions

在Java中,我想根据用户输入创建一些几何形状。诀窍是我无法更改现有的API,所以我不能使用像

这样的varargs语法
public Shape(Object... attrs) {}

用户输入:

shape.1.triangle.arg1 = 3
shape.1.triangle.arg2 = 4
shape.1.triangle.arg3 = 5
shape.1.triangle.arg4 = "My first triangle"
shape.2.rectangle.arg1 = 4
shape.2.rectangle.arg2 = 7
shape.2.rectangle.arg3 = "Another string label"

应该导致方法调用,如:

Shape s1 = new Triangle(arg1, arg2, arg3, arg4);

或者一般地说:

String shapeType = "triangle";
Object[] args = {arg1, arg2, arg3, arg4};
// This won't work, because newInstance() doesn't take args
Shape s1 = Class.forName(shapeType).newInstance(args); 

String shapeType = "rectangle";
Object[] args = {arg1, arg2, arg3};
// This won't work, because newInstance() doesn't take args
Shape s2 = Class.forName(shapeType).newInstance(args);

问题是Triangle的构造函数不允许使用varargs(...),我无法更改它。具体来说,构造函数是

public Triangle (int a, int b, int c, String label) {}

public Rectangle (int a, int b, String label) {}

那么如何根据用户输入创建正确的形状?

4 个答案:

答案 0 :(得分:4)

用工厂对象包裹三角形和方形类。然后,工厂可以根据您传入的内容确定要创建的内容,并且不必更改底层三角形和矩形对象的签名。

答案 1 :(得分:1)

这是一种有趣的方法。问题不在于Triangle缺少vararg构造函数,而是newInstance()只能调用默认构造函数。

mschaef提出了一个合理的方法。它仍然需要静态意识到每个形状的构造函数形式。然后,这些知识将隐藏在各自的工厂中。这可能是最好的解决方案。问题是你必须为每个形状写一个工厂。

但是,您可以编写使用反射API动态调用正确构造函数的代码。如果你有很多形状,那么它将在一个地方解决所有这些问题。您将获得Class对象,然后调用getDeclaredConstructors()以获取构造函数数组。 getParameterTypes()可以查询构造函数参数类型。您的代码必须根据您拥有的参数找到理想的构造函数。这基本上是编译器对静态类型所做的。

这个解决方案不是特别优雅,但确实有加分: 如果您正确地编写一次,它将始终适用于您可能引入的任何新形状。您可以将代码放在一个整洁的实用程序类中,而不要再查看它。

一般来说,我仍然认为使用反射是一种不受欢迎的选择。

答案 2 :(得分:0)

我认为您需要使用Bridge Pattern,或者至少使用它的变体。您的网桥可以遵循您希望拥有的签名,并决定如何调用基础类。

请注意,如果您的基础库无法执行您需要执行的操作,则无法解决此问题。如果它只需要4个参数来制作一个三角形。你的桥可能会抛出某种不支持的异常。

答案 3 :(得分:0)

如果您想实施原始解决方案(myClass.newInstance(args)) - 您可以这样做(一个建议,确定您可以提出自己的解决方案):
shape.1.triangle.arg1.int = 3
shape.1.triangle.arg2.int = 4
shape.1.triangle.arg3.int = 5
shape.1.triangle.arg4.String = "My first triangle"
shape.2.rectangle.arg1.int = 4
shape.2.rectangle.arg2.int = 7
shape.2.rectangle.arg3.String = "Another string label"

然后,您的代码可以使用class.getConstructor(Class<?> ... parameterType)来获取正确的构造函数,并在newInstance(Object ... args)对象上调用Constructor。 示例:

Class<?> shape = Class.forName("triangle");  
Constructor<?> constructor = shape.getConstructor(Integer.TYPE, Integer.TYPE, Integer.TYPE, String.class);  
constructor.newInstance(Integer.TYPE, Integer.TYPE, Integer.TYPE, String.class);

希望这有用。

另一种方法是使用FactoryBuilder模式。

shape.triangle.factory=TriangleFactory
shape.rectangle.factory=RectangleFactory
shape.1.triangle.arg1 = 3
shape.1.triangle.arg2 = 4
shape.1.triangle.arg3 = 5
shape.1.triangle.arg4 = "My first triangle"
shape.2.rectangle.arg1 = 4
shape.2.rectangle.arg2 = 7
shape.2.rectangle.arg3 = "Another string label"

你需要:

public interface IShapeFactory
{
    public IShape buildShape(Object ... args);
}

public class TriangleFactory implements IShapeFactory
{
    public IShape buildShape(Object ... args)
    {
       return new Triangle(args[0], args[1], args[2], args[3]); // You will need some casting here :)
    }
}

您需要做的所有代码都是实例化工厂,使用参数调用buildShape方法,工厂将执行它需要做的事情。

第二个解决方案对我来说似乎更好,我觉得你更容易,但如果从外部提供形状,你的用户可能更难以实现新的形状。

你的电话。