假设我们有一个公共方法“方法”,它在计算中使用私有的“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);
}
}
答案 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;
}
}