重用代码 - Java

时间:2011-01-18 14:15:21

标签: java function operator-overloading code-reuse

有没有办法在这些函数中重用迭代数组代码:

public static double[] ln(double[] z) {
    // returns  an array that consists of the natural logarithm of the values in array z
    int count = 0;

    for (double i : z){
        z[count] = Math.log(i);
        count += 1;
    }
    return z;
}


public static double[] inverse(double[] z) {
    //  returns  an array that consists of the inverse of the values in array z
    int count = 0;

    for (double i : z){
        z[count] = Math.pow(i,-1);
        count += 1;
    }
    return z;
}

7 个答案:

答案 0 :(得分:7)

是的,使用http://en.wikipedia.org/wiki/Strategy_pattern,但这可能是一种过度杀伤力。重复的迭代和计数++看起来并不那么糟糕。

答案 1 :(得分:3)

您的代码与您的评论不符。

public static double[] ln(double[] z) {
    // returns  an array that consists of the natural logarithm of the values in array z
    double[] r = new double[z.length];
    for(int i=0;i<z.length;i++) r[i] = Math.log(z[i]);
    return r;
}

public static double[] inverse(double[] z) {
    //  returns  an array that consists of the inverse of the values in array z
    double[] r = new double[z.length];
    for(int i=0;i<z.length;i++) r[i] = 1/z[i];
    return r;
}

您可以使用策略模式使循环通用,但这有三个缺点。

  • 代码更难阅读。
  • 代码更长,更复杂。
  • 执行速度慢很多。

Math.pow(x,-1)比1 / x贵很多倍,并且可能产生更多的舍入误差。

答案 2 :(得分:2)

这是重用循环的一种方法。这是一个例子:

public interface MathOperation {
   public double f(double value);
}

public class Invert implements MathOperation {
   public double f(double value) {
     return Math.pow(value, -1);
   }
}

public class Log implements MathOperation {
   public double f(double value) {    
     return Math.log(value);
   }
}

private static void calculateOnArray(double[] doubles, MathOperation operation) {
  int count = 0;

  for (double i : doubles){
    doubles[count] = operation.f(i);
    count += 1;
  }
}

public static double[] ln(double[] z) {
  calculateOnArray(z, new Log());
  return z;
}

public static double[] inverse(double[] z) {
  calculateOnArray(z, new Invert());
  return z;
}

注意 - 这不是命令模式,但也不是真实 strategy pattern的实现。它接近战略,但为了避免进一步的下跌,我保持模式未命名; - )

答案 3 :(得分:1)

没有那么多重用,但这里有两点 - 首先,从不使用i作为数组值,它的常规是它是迭代器,如果你这样做,你会让人迷惑不解。其次,你也可以使用for循环而不是每个循环,这将取消你的手动“计数”变量。

我还会在返回之前创建一个数组副本,你实际上是在改变在这里传递的参数,如果有人看到你的方法签名可能不是他们所期望的那样。我期望一个类似返回类型的方法保持原始参数不变(因为没有必要返回我已经引用的相同值!)

答案 4 :(得分:1)

在支持闭包或lambda表达式的语言中,或者可以传递的块(例如Ruby),你可以简洁地做到这一点。在Java中,您可以通过使用要作为回调调用的方法定义接口来模拟这一点。在您使用它的地方,您可以使用该界面创建一个匿名类。它看起来有点麻烦:

public interface Calculation {
    double calculate(double x);
}

public static double[] calcArray(double[] z, Calculcation calc) {
    int count = 0;

    for (double i : z){
        z[count] = calc.calculate(i);
        count += 1;
    }
    return z;
}

public static double[] ln(double[] z) {
    return calcArray(z, new Calculation() {
        double calculate(double x) {
            return Math.log(x);
        }
    });
}

public static double[] inverse(double[] z) {
    return calcArray(z, new Calculation() {
        double calculate(double x) {
            return Math.pow(x, -1);
        }
    });
}

答案 5 :(得分:1)

如果您使用函数式编程,则可以重复使用它。有很多库有很多这样的东西为你写的。对于这个例子,我只使用了Guava(http://code.google.com/p/guava-libraries/),但像Functional Java(http://functionaljava.org/)这样的库也能正常工作。 / p>

函数式编程的一个主要好处是能够抽象出算法,以便您可以在任何需要的地方重用该算法。对于您的情况,您的算法基本上是这样的:

  1. 对于列表中的每个元素
  2. 对该元素执行操作x
  3. 返回新列表
  4. 根据你想做的事情,诀窍是能够传入一个新的操作代替“x”。在函数式编程的世界中,我们创建了一个名为“functor”的对象,它实际上只是一种将函数封装在对象中的方法。 (如果Java有闭包,这将更容易,但这就是我们所拥有的。)

    无论如何,这里有一些代码可以做你想做的事情:

    类:反向

    
    import com.google.common.base.Function;
    
    public class Inverse implements Function
    {
        @Override
        public Double apply(Double arg0)
        {
            return Math.pow(arg0, -1);
        }
    }
    

    类:对数

    
    import com.google.common.base.Function;
    
    public class Logarithm implements Function
    {
        @Override
        public Double apply(Double arg0)
        {
            return Math.log(arg0);
        }
    }
    

    班级:司机

    
    import static org.junit.Assert.*;
    
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.List;
    
    import org.junit.Test;
    
    import com.google.common.collect.Collections2;
    
    public class Driver
    {
        @Test
        public void testInverse()
        {
            List initialValues = Arrays.asList(new Double[] {1.0, 2.0, 3.0});
    
            List logValues = new ArrayList(Collections2.transform(initialValues, new Inverse()));
    
            assertEquals(3, logValues.size());
    
            assertEquals(Double.valueOf(1.0), logValues.get(0), 0.01);
            assertEquals(Double.valueOf(0.5), logValues.get(1), 0.01);
            assertEquals(Double.valueOf(0.333), logValues.get(2), 0.01);
    
        }
    }
    

    Driver类只包含一个测试用例,但它确实说明了上面定义的Functors的用法。你会注意到在上面的testInverse方法中使用了一个名为Collections2.transform的方法。您可以在此处找到此方法的文档:

    http://guava-libraries.googlecode.com/svn/trunk/javadoc/com/google/common/collect/Collections2.html#transform%28java.util.Collection,%20com.google.common.base.Function%29

    本质上,这个方法的作用是抽象出踩过列表并将一些函数应用于该列表的每个元素的算法 - 你所要做的就是应用要执行的函数。所以,在这种情况下,我向该方法传递一个双打和仿函数列表(反向)。它返回一个新列表,其中包含初始列表中每个值的倒数。如果您希望对每个值进行记录,请将不同的仿函数传递给transform方法。

    这种类型的编程确实带有一点学习曲线,但对于您想要执行的代码重用类型来说,这绝对是非常棒的。通过利用现有的库(如Guava),您可以更轻松地编写代码。注意我写的代码实际上比你写的代码容易一些,因为我在执行数学函数时不需要处理列表/数组 - 我只是在一个元素上执行它; transform函数负责在整个列表中应用该函数。

答案 6 :(得分:1)

是的,正如这里的几个例子所示。但是,如果这是真正的问题而不是极端的简化,那么创建三个具有继承和重载的类来节省编写一个FOR语句似乎是朝着错误的方向前进。您正在编写二十行代码以节省重复一行代码。有什么好处?

如果在现实生活中,迭代数据的过程由于某种原因要复杂得多 - 如果它不仅仅是循环遍历数组,而且我不知道,在数据库中查找一些复杂的过程并且提交一个Web服务请求并执行一个完整的复杂计算页面来查找下一个元素 - 然后我的答案会有所不同。