在方法之间共享原始数据?

时间:2010-09-03 04:47:37

标签: java

假设我们有一个公共方法“方法”,它在计算中使用私有的“subMethod1”和私有的“subMethod2”。这些subMethods除了将“方法”分解为更易读,更封装的子部分以及避免方法中的代码重复之外别无其他目的。

我们需要每个子方法对2个或更多原始数据类型进行操作,这些原始数据类型是在方法顶部定义的局部变量。我们不能通过引用将它们传递给子方法,因为java不允许通过引用传递基元。

所以我们的解决方案是重构,并将方法顶部定义的局部变量更改为成员变量,这当然可以由方法及其子方法读取和写入。问题在于,现在我们有成员变量并不存在实际存在对象状态,但只有在执行方法时才有效存在。当然,实际上它们存在于对象的整个生命周期中,但它们只在方法执行期间使用过,并且它们可能在其他时候消失而不会影响对象的行为(请记住方法初始化)成员在其方法体开头的默认值)。

嗯,你说,只需将基元包装在对象中,将它们保存在方法中,然后让“方法”将这些对象传递给它的子方法,在那里它们可以改变它的值,因为它是通过引用传递的对象是。问题是在模拟循环中使用“方法”,它将运行数百万次。对象包装器显着降低了速度,性能是关键。

所以我的问题是:有没有任何解决方案可以给我带来与我丑陋的“使用成员在方法之间共享原始数据”解决方案相同的性能,但是也会有良好的OO设计?包含方法,子方法和原始数据的内部类是否合适?

谢谢, 约拿

编辑:这是一个煮熟的代码示例,其唯一目的是演示使用对象包装器导致的减速,如上所述:

public class TestSharedMembers {

protected int m17Multiples;
protected int m23Multiples;

public int methodPrimitive(int i) {

    m17Multiples = 0;
    m23Multiples = 0;

    calc17Primitive(i);
    calc23Primitive(i);

    return m17Multiples + m23Multiples;
}

private void calc17Primitive(int i) {
    if (i % 17 == 0) 
        m17Multiples++;
}
private void calc23Primitive(int i) {
    if (i % 23 == 0) 
        m23Multiples++;
}



/// using object wrappers
class IntWrapper {
    private int mInt;
    public IntWrapper(int i) {
        mInt = i;
    }
    public int getInt() {
        return mInt;
    }
    public void increment() {
        mInt++;
    }
}
public int methodObject(int i) {

    IntWrapper o17 = new IntWrapper(0);
    IntWrapper o23 = new IntWrapper(0);

    calc17Object(i, o17);
    calc23Object(i, o23);

    return o17.getInt() + o23.getInt();
}

private void calc17Object(int i, IntWrapper o) {
    if (i % 17 == 0) 
        o.increment();
}
private void calc23Object(int i, IntWrapper o) {
    if (i % 23 == 0) 
        o.increment();
}


public static void main(String[] args) {
    TestSharedMembers t = new TestSharedMembers();
    final int NUM_ITERS = 20000000;

    double start = System.currentTimeMillis();
    int total = 0;
    for (int i=0; i<NUM_ITERS; i++) {
        total += t.methodPrimitive(i);
    }
    double stop = System.currentTimeMillis();
    System.out.println(total);
    System.out.println(stop - start);

    start = System.currentTimeMillis();
    total = 0;
    for (int i=0; i<NUM_ITERS; i++) {
        total += t.methodObject(i);
    }
    stop = System.currentTimeMillis();
    System.out.println(total);
    System.out.println(stop - start);

}
}

编辑2:另一个测试Jon的理论的例子。

public class TestSharedMembers {

protected int m17Multiples;
protected int m23Multiples;

public int methodPrimitive(int i) {

    m17Multiples = 0;
    m23Multiples = 0;

    calc17Primitive(i);
    calc23Primitive(i);

    return m17Multiples + m23Multiples;
}

private void calc17Primitive(int i) {
    if (i % 17 == 0) 
        m17Multiples++;
    if (i % 19 == 0) 
        m17Multiples++;
    if (i % 5 == 0) 
        m17Multiples++;
    if (i % 43 == 0) 
        m17Multiples++;
    if (i % 41 == 0) 
        m17Multiples++;
    if (i % 91 == 0) 
        m17Multiples++;
}
private void calc23Primitive(int i) {
    if (i % 23 == 0) 
        m23Multiples++;
    if (i % 17 == 0) 
        m23Multiples++;
    if (i % 19 == 0) 
        m23Multiples++;
    if (i % 5 == 0) 
        m23Multiples++;
    if (i % 43 == 0) 
        m23Multiples++;
    if (i % 41 == 0) 
        m23Multiples++;
    if (i % 91 == 0) 
        m23Multiples++;
}



/// using object wrappers
class IntWrapper {
    private int mInt1;
    private int mInt2;
    public IntWrapper(int i1, int i2) {
        mInt1 = i1;
        mInt2 = i2;
    }
    public int getInt1() {
        return mInt1;
    }
    public int getInt2() {
        return mInt2;
    }
    public void increment1() {
        mInt1++;
    }
    public void increment2() {
        mInt2++;
    }
}
public int methodObject(int i) {

    IntWrapper o = new IntWrapper(0,0);

    calc17Object(i, o);
    calc23Object(i, o);

    return o.getInt1() + o.getInt2();
}

private void calc17Object(int i, IntWrapper o) {
    if (i % 17 == 0) 
        o.increment1();
    if (i % 19 == 0) 
        o.increment1();
    if (i % 5 == 0) 
        o.increment1();
    if (i % 43 == 0) 
        o.increment1();
    if (i % 41 == 0) 
        o.increment1();
    if (i % 91 == 0) 
        o.increment1();
}
private void calc23Object(int i, IntWrapper o) {
    if (i % 23 == 0) 
        o.increment2();
    if (i % 17 == 0) 
        o.increment1();
    if (i % 19 == 0) 
        o.increment1();
    if (i % 5 == 0) 
        o.increment1();
    if (i % 43 == 0) 
        o.increment1();
    if (i % 41 == 0) 
        o.increment1();
    if (i % 91 == 0) 
        o.increment1();
}


public static void main(String[] args) {
    TestSharedMembers t = new TestSharedMembers();
    final int NUM_ITERS = 20000000;

    double start = System.currentTimeMillis();
    int total = 0;
    for (int i=0; i<NUM_ITERS; i++) {
        total += t.methodPrimitive(i);
    }
    double stop = System.currentTimeMillis();
    System.out.println(total);
    System.out.println(stop - start);

    start = System.currentTimeMillis();
    total = 0;
    for (int i=0; i<NUM_ITERS; i++) {
        total += t.methodObject(i);
    }
    stop = System.currentTimeMillis();
    System.out.println(total);
    System.out.println(stop - start);

}
}

5 个答案:

答案 0 :(得分:2)

我会看一下创建一个嵌套类型,它封装了所有这些当前局部变量,是的。然后,您是否将子方法移动到该类型,或者只是将方法保留在它们所在的位置,并将对此状态类的实例的引用传递给辅助方法取决于您。如果将子方法声明在状态类中,您可能会发现对子方法进行单元测试会稍微简单一些,但显然这两种解决方案实际上是等效的。

子方法是否也需要访问现有类中的状态?如果是这样,我可能会将子方法保留在现有类中 - 我怀疑这将使代码更清晰。我不是真正的“内部”类的粉丝(对外部类的隐式引用) - 我通常使我的嵌套类静态,并明确地做所有事情。

(注意“通过引用传递对象”是对Java中发生的事情的错误陈述。实际上,引用是通过值传递的,这是微妙的不同.Java没有任何传递引用语义。)

答案 1 :(得分:1)

您是否可以将此方法本身设为具有原始字段的对象,而不是将单个基元包装或将基元作为字段添加到该类中?

如果您当前定义method()的类是Foo,而不是将它用于Foo的原语添加到另一个类(也许是内部类){{1}并且,在您之前调用FooMethod之前,请改为method()new FooMethod().method()中的原始字段实际上将成为局部变量,因为一旦方法调用完成,FooMethod对象可以自由地进行GC。

答案 2 :(得分:1)

public class ShareLocal {

private final static class methods
{

    private int sharedInt;
    public void method(int init)
    {
    sharedInt=init; 
    ... 
    }
    private void meth1()
    {
    ... 
    }
    private void meth2()
    {
    ... 
    }
}
private ShareLocal.methods methodCall=new ShareLocal.methods();
void simulationLoop()
{
for(int i=0;i<10;i++)   
  methodCall.method(i);
}


}

我认为你提到的内心课是最好的方法。

答案 3 :(得分:0)

如果您真的认为由于包装对象造成的性能损失是一个问题,那么我建议不要提取上述2个子方法。大方法是难闻的气味,但如果你需要它进行模拟,那么“漂亮”的面向对象设计可能是第二优先。

无论如何,在成员变量中保存中间状态。首先,因为你已经提到过,第二,因为它也会减慢代码速度,第三,因为如果你计划让多个线程执行这些方法,你就会因竞争条件而迷失。

答案 4 :(得分:0)

也许你可以再次解释为什么让田地消失对你来说如此重要?如果性能与您说的一样重要,为什么要将其添加为“要求”?看来你已经有了一个避免了对象创建成本的解决方案。

如果您想要更优化的解决方案,请使用参数并返回值。

public class TestSharedMembers { 
  public int methodPrimitive(int i) { 
    return calc17Primitive(i) + calc23Primitive(i); 
  } 
  private int calc17Primitive(int i) { 
    return i % 17 == 0 ? 1 : 0;
  } 
  private void calc23Primitive(int i) { 
    return i % 23 == 0? 1 : 0;
  } 
}